コード例 #1
0
ファイル: pkg.py プロジェクト: joehealy/pkg-salt
def _find_install_targets(name=None,
                          version=None,
                          pkgs=None,
                          sources=None,
                          **kwargs):
    '''
    Inspect the arguments to pkg.installed and discover what packages need to
    be installed. Return a dict of desired packages
    '''
    if all((pkgs, sources)):
        return {'name': name,
                'changes': {},
                'result': False,
                'comment': 'Only one of "pkgs" and "sources" is permitted.'}

    cur_pkgs = __salt__['pkg.list_pkgs'](versions_as_list=True)
    if any((pkgs, sources)):
        if pkgs:
            desired = _repack_pkgs(pkgs)
        elif sources:
            desired = __salt__['pkg_resource.pack_sources'](sources)

        if not desired:
            # Badly-formatted SLS
            return {'name': name,
                    'changes': {},
                    'result': False,
                    'comment': 'Invalidly formatted "{0}" parameter. See '
                               'minion log.'.format('pkgs' if pkgs
                                                    else 'sources')}

    else:
        if salt.utils.is_windows():
            pkginfo = _get_package_info(name)
            if not pkginfo:
                return {'name': name,
                        'changes': {},
                        'result': False,
                        'comment': 'Package {0} not found in the '
                                   'repository.'.format(name)}
            if version is None:
                version = _get_latest_pkg_version(pkginfo)
        desired = {name: version}

        cver = cur_pkgs.get(name, [])
        if version and version in cver:
            # The package is installed and is the correct version
            return {'name': name,
                    'changes': {},
                    'result': True,
                    'comment': ('Version {0} of package "{1}" is already '
                                'installed').format(version, name)}

        # if cver is not an empty string, the package is already installed
        elif cver and version is None:
            # The package is installed
            return {'name': name,
                    'changes': {},
                    'result': True,
                    'comment': 'Package {0} is already installed'.format(name)}

    version_spec = False
    # Find out which packages will be targeted in the call to pkg.install
    if sources:
        targets = [x for x in desired if x not in cur_pkgs]
    else:
        # Perform platform-specific pre-flight checks
        problems = _preflight_check(desired, **kwargs)
        comments = []
        if problems.get('no_suggest'):
            comments.append(
                'The following package(s) were not found, and no possible '
                'matches were found in the package db: '
                '{0}'.format(', '.join(sorted(problems['no_suggest'])))
            )
        if problems.get('suggest'):
            for pkgname, suggestions in problems['suggest'].iteritems():
                comments.append(
                    'Package {0!r} not found (possible matches: {1})'
                    .format(pkgname, ', '.join(suggestions))
                )
        if comments:
            if len(comments) > 1:
                comments.append('')
            return {'name': name,
                    'changes': {},
                    'result': False,
                    'comment': '. '.join(comments).rstrip()}

        # Check current versions against desired versions
        targets = {}
        problems = []
        for pkgname, pkgver in desired.iteritems():
            cver = cur_pkgs.get(pkgname, [])
            # Package not yet installed, so add to targets
            if not cver:
                targets[pkgname] = pkgver
                continue
            elif not __salt__['pkg_resource.check_extra_requirements'](pkgname,
                                                                       pkgver):
                targets[pkgname] = pkgver
                continue
            # No version specified and pkg is installed, do not add to targets
            elif __salt__['pkg_resource.version_clean'](pkgver) is None:
                continue
            version_spec = True
            match = re.match('^([<>])?(=)?([^<>=]+)$', pkgver)
            if not match:
                msg = 'Invalid version specification "{0}" for package ' \
                      '"{1}".'.format(pkgver, pkgname)
                problems.append(msg)
            else:
                gt_lt, eq, verstr = match.groups()
                comparison = gt_lt or ''
                comparison += eq or ''
                # A comparison operator of "=" is redundant, but possible.
                # Change it to "==" so that the version comparison works
                if comparison in ['=', '']:
                    comparison = '=='
                if not _fulfills_version_spec(cver, comparison, verstr):
                    # Current version did not match desired, add to targets
                    targets[pkgname] = pkgver

        if problems:
            return {'name': name,
                    'changes': {},
                    'result': False,
                    'comment': ' '.join(problems)}

    if not targets:
        # All specified packages are installed
        msg = 'All specified packages are already installed{0}.'.format(
            ' and are at the desired version' if version_spec else '')
        return {'name': name,
                'changes': {},
                'result': True,
                'comment': msg}

    return desired, targets
コード例 #2
0
ファイル: pkg.py プロジェクト: joehealy/pkg-salt
def latest(
        name,
        refresh=False,
        fromrepo=None,
        skip_verify=False,
        pkgs=None,
        **kwargs):
    '''
    Verify that the named package is installed and the latest available
    package. If the package can be updated this state function will update
    the package. Generally it is better for the
    :mod:`installed <salt.states.pkg.installed>` function to be
    used, as :mod:`latest <salt.states.pkg.latest>` will update the package
    whenever a new package is available.

    name
        The name of the package to maintain at the latest available version.
        This parameter is ignored if "pkgs" is used.

    fromrepo
        Specify a repository from which to install

    skip_verify
        Skip the GPG verification check for the package to be installed


    Multiple Package Installation Options:

    (Not yet supported for: Windows, FreeBSD, OpenBSD, MacOS, and Solaris
    pkgutil)

    pkgs
        A list of packages to maintain at the latest available version.

    Usage::

        mypkgs:
          pkg.latest:
            - pkgs:
              - foo
              - bar
              - baz
    '''
    rtag = __gen_rtag()

    if kwargs.get('sources'):
        return {'name': name,
                'changes': {},
                'result': False,
                'comment': 'The "sources" parameter is not supported.'}
    elif pkgs:
        desired_pkgs = _repack_pkgs(pkgs).keys()
        if not desired_pkgs:
            # Badly-formatted SLS
            return {'name': name,
                    'changes': {},
                    'result': False,
                    'comment': 'Invalidly formatted "pkgs" parameter. See '
                               'minion log.'}
    else:
        desired_pkgs = [name]

    if salt.utils.is_true(refresh) or os.path.isfile(rtag):
        refresh = True
    else:
        refresh = False

    cur = __salt__['pkg.version'](*desired_pkgs)
    avail = __salt__['pkg.latest_version'](*desired_pkgs,
                                           fromrepo=fromrepo,
                                           refresh=refresh,
                                           **kwargs)
    # Remove the rtag if it exists, ensuring only one refresh per salt run
    # (unless overridden with refresh=True)
    if os.path.isfile(rtag):
        os.remove(rtag)

    # Repack the cur/avail data if only a single package is being checked
    if isinstance(cur, basestring):
        cur = {desired_pkgs[0]: cur}
    if isinstance(avail, basestring):
        avail = {desired_pkgs[0]: avail}

    targets = {}
    problems = []
    for pkg in desired_pkgs:
        if not avail[pkg]:
            if not cur[pkg]:
                msg = 'No information found for "{0}".'.format(pkg)
                log.error(msg)
                problems.append(msg)
        elif not cur[pkg] \
                or salt.utils.compare_versions(
                    ver1=cur[pkg],
                    oper='<',
                    ver2=avail[pkg],
                    cmp_func=__salt__.get('version_cmp')):
            targets[pkg] = avail[pkg]

    if problems:
        return {'name': name,
                'changes': {},
                'result': False,
                'comment': ' '.join(problems)}

    if targets:
        # Find up-to-date packages
        if not pkgs:
            # There couldn't have been any up-to-date packages if this state
            # only targeted a single package and is being allowed to proceed to
            # the install step.
            up_to_date = []
        else:
            up_to_date = [x for x in pkgs if x not in targets]

        if __opts__['test']:
            to_be_upgraded = ', '.join(sorted(targets.keys()))
            comment = 'The following packages are set to be ' \
                      'installed/upgraded: ' \
                      '{0}.'.format(to_be_upgraded)
            if up_to_date:
                if len(up_to_date) <= 10:
                    comment += ' The following packages are already ' \
                        'up-to-date: {0}.'.format(', '.join(sorted(up_to_date)))
                else:
                    comment += ' {0} packages are already up-to-date.'.format(
                        len(up_to_date))

            return {'name': name,
                    'changes': {},
                    'result': None,
                    'comment': comment}

        # Build updated list of pkgs to exclude non-targeted ones
        targeted_pkgs = targets.keys() if pkgs else None

        # No need to refresh, if a refresh was necessary it would have been
        # performed above when pkg.latest_version was run.
        changes = __salt__['pkg.install'](name,
                                          refresh=False,
                                          fromrepo=fromrepo,
                                          skip_verify=skip_verify,
                                          pkgs=targeted_pkgs,
                                          **kwargs)

        if changes:
            # Find failed and successful updates
            failed = [x for x in targets
                      if not changes.get(x) or changes[x]['new'] != targets[x]]
            successful = [x for x in targets if x not in failed]

            comments = []
            if failed:
                msg = 'The following packages failed to update: ' \
                      '{0}.'.format(', '.join(sorted(failed)))
                comments.append(msg)
            if successful:
                msg = 'The following packages were successfully ' \
                      'installed/upgraded: ' \
                      '{0}.'.format(', '.join(sorted(successful)))
                comments.append(msg)
            if up_to_date:
                if len(up_to_date) <= 10:
                    msg = 'The following packages were already up-to-date: ' \
                        '{0}.'.format(', '.join(sorted(up_to_date)))
                else:
                    msg = '{0} packages were already up-to-date. '.format(
                        len(up_to_date))
                comments.append(msg)

            return {'name': name,
                    'changes': changes,
                    'result': False if failed else True,
                    'comment': ' '.join(comments)}
        else:
            if len(targets) > 10:
                comment = 'All targeted {0} packages failed to update.'\
                    .format(len(targets))
            elif len(targets) > 1:
                comment = 'All targeted packages failed to update: ' \
                          '({0}).'.format(', '.join(sorted(targets.keys())))
            else:
                comment = 'Package {0} failed to ' \
                          'update.'.format(targets.keys()[0])
            if up_to_date:
                if len(up_to_date) <= 10:
                    comment += ' The following packages were already ' \
                        'up-to-date: ' \
                        '{0}'.format(', '.join(sorted(up_to_date)))
                else:
                    comment += '{0} packages were already ' \
                        'up-to-date.'.format(len(up_to_date))
            return {'name': name,
                    'changes': changes,
                    'result': False,
                    'comment': comment}
    else:
        if len(desired_pkgs) > 10:
            comment = 'All {0} packages are up-to-date.'.format(
                len(desired_pkgs))
        elif len(desired_pkgs) > 1:
            comment = 'All packages are up-to-date ' \
                '({0}).'.format(', '.join(sorted(desired_pkgs)))
        else:
            comment = 'Package {0} is already ' \
                'up-to-date.'.format(desired_pkgs[0])

        return {'name': name,
                'changes': {},
                'result': True,
                'comment': comment}
コード例 #3
0
ファイル: pkg.py プロジェクト: ryan-lane/salt
def _find_install_targets(name=None,
                          version=None,
                          pkgs=None,
                          sources=None,
                          skip_suggestions=False,
                          **kwargs):
    '''
    Inspect the arguments to pkg.installed and discover what packages need to
    be installed. Return a dict of desired packages
    '''
    if all((pkgs, sources)):
        return {
            'name': name,
            'changes': {},
            'result': False,
            'comment': 'Only one of "pkgs" and "sources" is permitted.'
        }

    cur_pkgs = __salt__['pkg.list_pkgs'](versions_as_list=True, **kwargs)
    if any((pkgs, sources)):
        if pkgs:
            desired = _repack_pkgs(pkgs)
        elif sources:
            desired = __salt__['pkg_resource.pack_sources'](sources)

        if not desired:
            # Badly-formatted SLS
            return {
                'name':
                name,
                'changes': {},
                'result':
                False,
                'comment':
                'Invalidly formatted {0!r} parameter. See '
                'minion log.'.format('pkgs' if pkgs else 'sources')
            }
        to_unpurge = _find_unpurge_targets(desired)
    else:
        if salt.utils.is_windows():
            pkginfo = _get_package_info(name)
            if not pkginfo:
                return {
                    'name':
                    name,
                    'changes': {},
                    'result':
                    False,
                    'comment':
                    'Package {0} not found in the '
                    'repository.'.format(name)
                }
            if version is None:
                version = _get_latest_pkg_version(pkginfo)
        _normalize_name = __salt__.get('pkg.normalize_name',
                                       lambda pkgname: pkgname)
        desired = {_normalize_name(name): version}
        to_unpurge = _find_unpurge_targets(desired)

        cver = cur_pkgs.get(name, [])
        if name not in to_unpurge:
            if version and version in cver:
                # The package is installed and is the correct version
                return {
                    'name':
                    name,
                    'changes': {},
                    'result':
                    True,
                    'comment':
                    'Version {0} of package {1!r} is already '
                    'installed'.format(version, name)
                }

            # if cver is not an empty string, the package is already installed
            elif cver and version is None:
                # The package is installed
                return {
                    'name': name,
                    'changes': {},
                    'result': True,
                    'comment': 'Package {0} is already '
                    'installed'.format(name)
                }

    version_spec = False
    # Find out which packages will be targeted in the call to pkg.install
    if sources:
        targets = [x for x in desired if x not in cur_pkgs]
    else:
        # Check for alternate package names if strict processing is not
        # enforced.
        # Takes extra time. Disable for improved performance
        if not skip_suggestions:
            # Perform platform-specific pre-flight checks
            problems = _preflight_check(desired, **kwargs)
            comments = []
            if problems.get('no_suggest'):
                comments.append(
                    'The following package(s) were not found, and no possible '
                    'matches were found in the package db: '
                    '{0}'.format(', '.join(sorted(problems['no_suggest']))))
            if problems.get('suggest'):
                for pkgname, suggestions in problems['suggest'].iteritems():
                    comments.append(
                        'Package {0!r} not found (possible matches: {1})'.
                        format(pkgname, ', '.join(suggestions)))
            if comments:
                if len(comments) > 1:
                    comments.append('')
                return {
                    'name': name,
                    'changes': {},
                    'result': False,
                    'comment': '. '.join(comments).rstrip()
                }

        # Check current versions against desired versions
        targets = {}
        problems = []
        for pkgname, pkgver in desired.iteritems():
            cver = cur_pkgs.get(pkgname, [])
            # Package not yet installed, so add to targets
            if not cver:
                targets[pkgname] = pkgver
                continue
            elif not __salt__['pkg_resource.check_extra_requirements'](pkgname,
                                                                       pkgver):
                targets[pkgname] = pkgver
                continue
            # No version specified and pkg is installed, do not add to targets
            elif __salt__['pkg_resource.version_clean'](pkgver) is None:
                continue
            version_spec = True
            match = re.match('^([<>])?(=)?([^<>=]+)$', pkgver)
            if not match:
                msg = 'Invalid version specification {0!r} for package ' \
                      '{1!r}.'.format(pkgver, pkgname)
                problems.append(msg)
            else:
                gt_lt, eq, verstr = match.groups()
                comparison = gt_lt or ''
                comparison += eq or ''
                # A comparison operator of "=" is redundant, but possible.
                # Change it to "==" so that the version comparison works
                if comparison in ['=', '']:
                    comparison = '=='
                if not _fulfills_version_spec(cver, comparison, verstr):
                    # Current version did not match desired, add to targets
                    targets[pkgname] = pkgver

        if problems:
            return {
                'name': name,
                'changes': {},
                'result': False,
                'comment': ' '.join(problems)
            }

    if not any((targets, to_unpurge)):
        # All specified packages are installed
        msg = ('All specified packages are already installed{0}.'.format(
            ' and are at the desired version' if version_spec else ''))
        return {'name': name, 'changes': {}, 'result': True, 'comment': msg}

    return desired, targets, to_unpurge
コード例 #4
0
ファイル: pkg.py プロジェクト: ryan-lane/salt
def latest(name,
           refresh=None,
           fromrepo=None,
           skip_verify=False,
           pkgs=None,
           **kwargs):
    '''
    Verify that the named package is installed and the latest available
    package. If the package can be updated this state function will update
    the package. Generally it is better for the
    :mod:`installed <salt.states.pkg.installed>` function to be
    used, as :mod:`latest <salt.states.pkg.latest>` will update the package
    whenever a new package is available.

    name
        The name of the package to maintain at the latest available version.
        This parameter is ignored if "pkgs" is used.

    fromrepo
        Specify a repository from which to install

    skip_verify
        Skip the GPG verification check for the package to be installed


    Multiple Package Installation Options:

    (Not yet supported for: Windows, FreeBSD, OpenBSD, MacOS, and Solaris
    pkgutil)

    pkgs
        A list of packages to maintain at the latest available version.

    Usage::

        mypkgs:
          pkg.latest:
            - pkgs:
              - foo
              - bar
              - baz
    '''
    rtag = __gen_rtag()
    refresh = bool(
        salt.utils.is_true(refresh)
        or (os.path.isfile(rtag) and refresh is not False))

    if kwargs.get('sources'):
        return {
            'name': name,
            'changes': {},
            'result': False,
            'comment': 'The "sources" parameter is not supported.'
        }
    elif pkgs:
        desired_pkgs = _repack_pkgs(pkgs).keys()
        if not desired_pkgs:
            # Badly-formatted SLS
            return {
                'name':
                name,
                'changes': {},
                'result':
                False,
                'comment':
                'Invalidly formatted "pkgs" parameter. See '
                'minion log.'
            }
    else:
        desired_pkgs = [name]

    cur = __salt__['pkg.version'](*desired_pkgs, **kwargs)
    avail = __salt__['pkg.latest_version'](*desired_pkgs,
                                           fromrepo=fromrepo,
                                           refresh=refresh,
                                           **kwargs)
    # Remove the rtag if it exists, ensuring only one refresh per salt run
    # (unless overridden with refresh=True)
    if os.path.isfile(rtag) and refresh:
        os.remove(rtag)

    # Repack the cur/avail data if only a single package is being checked
    if isinstance(cur, string_types):
        cur = {desired_pkgs[0]: cur}
    if isinstance(avail, string_types):
        avail = {desired_pkgs[0]: avail}

    targets = {}
    problems = []
    for pkg in desired_pkgs:
        if not avail[pkg]:
            if not cur[pkg]:
                msg = 'No information found for {0!r}.'.format(pkg)
                log.error(msg)
                problems.append(msg)
        elif not cur[pkg] \
                or salt.utils.compare_versions(
                    ver1=cur[pkg],
                    oper='<',
                    ver2=avail[pkg],
                    cmp_func=__salt__.get('version_cmp')):
            targets[pkg] = avail[pkg]

    if problems:
        return {
            'name': name,
            'changes': {},
            'result': False,
            'comment': ' '.join(problems)
        }

    if targets:
        # Find up-to-date packages
        if not pkgs:
            # There couldn't have been any up-to-date packages if this state
            # only targeted a single package and is being allowed to proceed to
            # the install step.
            up_to_date = []
        else:
            up_to_date = [x for x in pkgs if x not in targets]

        if __opts__['test']:
            to_be_upgraded = ', '.join(sorted(targets.keys()))
            comment = 'The following packages are set to be ' \
                      'installed/upgraded: ' \
                      '{0}.'.format(to_be_upgraded)
            if up_to_date:
                if len(up_to_date) <= 10:
                    comment += (' The following packages are already '
                                'up-to-date: {0}.').format(', '.join(
                                    sorted(up_to_date)))
                else:
                    comment += ' {0} packages are already up-to-date.'.format(
                        len(up_to_date))

            return {
                'name': name,
                'changes': {},
                'result': None,
                'comment': comment
            }

        # Build updated list of pkgs to exclude non-targeted ones
        targeted_pkgs = targets.keys() if pkgs else None

        try:
            # No need to refresh, if a refresh was necessary it would have been
            # performed above when pkg.latest_version was run.
            changes = __salt__['pkg.install'](name,
                                              refresh=False,
                                              fromrepo=fromrepo,
                                              skip_verify=skip_verify,
                                              pkgs=targeted_pkgs,
                                              **kwargs)
        except CommandExecutionError as exc:
            return {
                'name':
                name,
                'changes': {},
                'result':
                False,
                'comment':
                'An error was encountered while installing '
                'package(s): {0}'.format(exc)
            }

        if changes:
            # Find failed and successful updates
            failed = [
                x for x in targets
                if not changes.get(x) or changes[x]['new'] != targets[x]
            ]
            successful = [x for x in targets if x not in failed]

            comments = []
            if failed:
                msg = 'The following packages failed to update: ' \
                      '{0}.'.format(', '.join(sorted(failed)))
                comments.append(msg)
            if successful:
                msg = 'The following packages were successfully ' \
                      'installed/upgraded: ' \
                      '{0}.'.format(', '.join(sorted(successful)))
                comments.append(msg)
            if up_to_date:
                if len(up_to_date) <= 10:
                    msg = 'The following packages were already up-to-date: ' \
                        '{0}.'.format(', '.join(sorted(up_to_date)))
                else:
                    msg = '{0} packages were already up-to-date. '.format(
                        len(up_to_date))
                comments.append(msg)

            return {
                'name': name,
                'changes': changes,
                'result': False if failed else True,
                'comment': ' '.join(comments)
            }
        else:
            if len(targets) > 10:
                comment = ('{0} targeted packages failed to update. '
                           'See debug log for details.'.format(len(targets)))
            elif len(targets) > 1:
                comment = ('The following targeted packages failed to update. '
                           'See debug log for details: ({0}).'.format(
                               ', '.join(sorted(targets.keys()))))
            else:
                comment = 'Package {0} failed to ' \
                          'update.'.format(targets.keys()[0])
            if up_to_date:
                if len(up_to_date) <= 10:
                    comment += ' The following packages were already ' \
                        'up-to-date: ' \
                        '{0}'.format(', '.join(sorted(up_to_date)))
                else:
                    comment += '{0} packages were already ' \
                        'up-to-date.'.format(len(up_to_date))

            return {
                'name': name,
                'changes': changes,
                'result': False,
                'comment': comment
            }
    else:
        if len(desired_pkgs) > 10:
            comment = 'All {0} packages are up-to-date.'.format(
                len(desired_pkgs))
        elif len(desired_pkgs) > 1:
            comment = 'All packages are up-to-date ' \
                '({0}).'.format(', '.join(sorted(desired_pkgs)))
        else:
            comment = 'Package {0} is already ' \
                'up-to-date.'.format(desired_pkgs[0])

        return {
            'name': name,
            'changes': {},
            'result': True,
            'comment': comment
        }
コード例 #5
0
ファイル: pkg.py プロジェクト: hulu/salt
def _find_install_targets(name=None, version=None, pkgs=None, sources=None, **kwargs):
    """
    Inspect the arguments to pkg.installed and discover what packages need to
    be installed. Return a dict of desired packages
    """
    if all((pkgs, sources)):
        return {
            "name": name,
            "changes": {},
            "result": False,
            "comment": 'Only one of "pkgs" and "sources" is permitted.',
        }

    cur_pkgs = __salt__["pkg.list_pkgs"](versions_as_list=True, **kwargs)
    if any((pkgs, sources)):
        if pkgs:
            desired = _repack_pkgs(pkgs)
        elif sources:
            desired = __salt__["pkg_resource.pack_sources"](sources)

        if not desired:
            # Badly-formatted SLS
            return {
                "name": name,
                "changes": {},
                "result": False,
                "comment": "Invalidly formatted {0!r} parameter. See "
                "minion log.".format("pkgs" if pkgs else "sources"),
            }

    else:
        if salt.utils.is_windows():
            pkginfo = _get_package_info(name)
            if not pkginfo:
                return {
                    "name": name,
                    "changes": {},
                    "result": False,
                    "comment": "Package {0} not found in the " "repository.".format(name),
                }
            if version is None:
                version = _get_latest_pkg_version(pkginfo)
        desired = {name: version}

        cver = cur_pkgs.get(name, [])
        if version and version in cver:
            # The package is installed and is the correct version
            return {
                "name": name,
                "changes": {},
                "result": True,
                "comment": ("Version {0} of package {1!r} is already " "installed").format(version, name),
            }

        # if cver is not an empty string, the package is already installed
        elif cver and version is None:
            # The package is installed
            return {
                "name": name,
                "changes": {},
                "result": True,
                "comment": "Package {0} is already installed".format(name),
            }

    version_spec = False
    # Find out which packages will be targeted in the call to pkg.install
    if sources:
        targets = [x for x in desired if x not in cur_pkgs]
    else:
        # Perform platform-specific pre-flight checks
        problems = _preflight_check(desired, **kwargs)
        comments = []
        if problems.get("no_suggest"):
            comments.append(
                "The following package(s) were not found, and no possible "
                "matches were found in the package db: "
                "{0}".format(", ".join(sorted(problems["no_suggest"])))
            )
        if problems.get("suggest"):
            for pkgname, suggestions in problems["suggest"].iteritems():
                comments.append(
                    "Package {0!r} not found (possible matches: {1})".format(pkgname, ", ".join(suggestions))
                )
        if comments:
            if len(comments) > 1:
                comments.append("")
            return {"name": name, "changes": {}, "result": False, "comment": ". ".join(comments).rstrip()}

        # Check current versions against desired versions
        targets = {}
        problems = []
        for pkgname, pkgver in desired.iteritems():
            cver = cur_pkgs.get(pkgname, [])
            # Package not yet installed, so add to targets
            if not cver:
                targets[pkgname] = pkgver
                continue
            elif not __salt__["pkg_resource.check_extra_requirements"](pkgname, pkgver):
                targets[pkgname] = pkgver
                continue
            # No version specified and pkg is installed, do not add to targets
            elif __salt__["pkg_resource.version_clean"](pkgver) is None:
                continue
            version_spec = True
            match = re.match("^([<>])?(=)?([^<>=]+)$", pkgver)
            if not match:
                msg = "Invalid version specification {0!r} for package " "{1!r}.".format(pkgver, pkgname)
                problems.append(msg)
            else:
                gt_lt, eq, verstr = match.groups()
                comparison = gt_lt or ""
                comparison += eq or ""
                # A comparison operator of "=" is redundant, but possible.
                # Change it to "==" so that the version comparison works
                if comparison in ["=", ""]:
                    comparison = "=="
                if not _fulfills_version_spec(cver, comparison, verstr):
                    # Current version did not match desired, add to targets
                    targets[pkgname] = pkgver

        if problems:
            return {"name": name, "changes": {}, "result": False, "comment": " ".join(problems)}

    if not targets:
        # All specified packages are installed
        msg = "All specified packages are already installed{0}.".format(
            " and are at the desired version" if version_spec else ""
        )
        return {"name": name, "changes": {}, "result": True, "comment": msg}

    return desired, targets