Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge pull request #3163 from archtaku/issue2906

Add multiple-package support to pkg.latest state
  • Loading branch information...
commit f6759fb3a07f8e624de33fc4916857606083eed4 2 parents d45d85b + c48a27a
@thatch45 thatch45 authored
View
87 salt/modules/apt.py
@@ -43,7 +43,17 @@ def __init__(opts):
os.environ.update(env_vars)
-def available_version(name):
+def _pkgname_without_arch(name):
+ '''
+ Check for ':arch' appended to pkg name (i.e. 32 bit installed on 64 bit
+ machine is ':i386')
+ '''
+ if name.find(':') >= 0:
+ return name.split(':')[0]
+ return name
+
+
+def available_version(*names):
'''
Return the latest version of the named package available for upgrade or
installation via the available apt repository
@@ -51,38 +61,47 @@ def available_version(name):
CLI Example::
salt '*' pkg.available_version <package name>
+ salt '*' pkg.available_version <package1> <package2> <package3>
'''
- version = ''
- cmd = 'apt-cache -q policy {0} | grep Candidate'.format(name)
-
- out = __salt__['cmd.run_stdout'](cmd)
-
- version_list = out.split()
-
- if len(version_list) >= 2:
- version = version_list[-1]
-
- return version
+ if len(names) == 0:
+ return ''
+ else:
+ ret = {}
+ for name in names:
+ cmd = 'apt-cache -q policy {0} | grep Candidate'.format(name)
+ version = __salt__['cmd.run_stdout'](cmd).split()
+ if len(version) >= 2:
+ version = version[-1]
+ else:
+ version = ''
+ ret[name] = version
+ # Return a string if only one package name passed
+ if len(ret) == 1:
+ return ret[names[0]]
+ return ret
-def version(name):
+def version(*names):
'''
Returns a string representing the package version or an empty string if not
- installed
+ installed. If more than one package name is specified, a dict of
+ name/version pairs is returned.
CLI Example::
salt '*' pkg.version <package name>
+ salt '*' pkg.version <package1> <package2> <package3>
'''
- pkgs = list_pkgs(name)
- # check for ':arch' appended to pkg name (i.e. 32 bit installed on 64 bit
- # machine is ':i386')
- if name.find(':') >= 0:
- name = name.split(':')[0]
- if name in pkgs:
- return pkgs[name]
- else:
+ pkgs = list_pkgs()
+ if len(names) == 0:
return ''
+ elif len(names) == 1:
+ return pkgs.get(_pkgname_without_arch(names[0]), '')
+ else:
+ ret = {}
+ for name in names:
+ ret[name] = pkgs.get(_pkgname_without_arch(name), '')
+ return ret
def refresh_db():
@@ -178,7 +197,8 @@ def install(name=None, refresh=False, fromrepo=None, skip_verify=False,
# from killing the apt and hence hosing the dpkg database
salt.utils.daemonize_if(__opts__, **kwargs)
- if refresh:
+ # Catch both boolean input from state and string input from CLI
+ if refresh is True or refresh == 'True':
refresh_db()
if debconf:
@@ -433,3 +453,24 @@ def upgrade_available(name):
salt '*' pkg.upgrade_available <package name>
'''
return name in _get_upgradable()
+
+
+def compare(version1='', version2=''):
+ '''
+ Compare two version strings. Return -1 if version1 < version2,
+ 0 if version1 == version2, and 1 if version1 > version2. Return None if
+ there was a problem making the comparison.
+
+ CLI Example::
+
+ salt '*' pkg.compare '0.2.4-0ubuntu1' '0.2.4.1-0ubuntu1'
+ '''
+ try:
+ for oper, ret in (('lt', -1), ('eq', 0), ('gt', 1)):
+ cmd = 'dpkg --compare-versions "{0}" {1} ' \
+ '"{2}"'.format(version1, oper, version2)
+ if __salt__['cmd.retcode'](cmd) == 0:
+ return ret
+ except Exception as e:
+ log.error(e)
+ return None
View
13 salt/modules/brew.py
@@ -124,3 +124,16 @@ def upgrade_available(pkg):
salt '*' pkg.upgrade_available <package name>
'''
return pkg in list_upgrades()
+
+
+def compare(version1='', version2=''):
+ '''
+ Compare two version strings. Return -1 if version1 < version2,
+ 0 if version1 == version2, and 1 if version1 > version2. Return None if
+ there was a problem making the comparison.
+
+ CLI Example::
+
+ salt '*' pkg.compare '0.2.4-0' '0.2.4.1-0'
+ '''
+ return __salt__['pkg_resource.compare'](version1, version2)
View
13 salt/modules/ebuild.py
@@ -338,3 +338,16 @@ def depclean(pkg=None):
ret_pkgs.append(pkg)
return ret_pkgs
+
+
+def compare(version1='', version2=''):
+ '''
+ Compare two version strings. Return -1 if version1 < version2,
+ 0 if version1 == version2, and 1 if version1 > version2. Return None if
+ there was a problem making the comparison.
+
+ CLI Example::
+
+ salt '*' pkg.compare '0.2.4-0' '0.2.4.1-0'
+ '''
+ return __salt__['pkg_resource.compare'](version1, version2)
View
13 salt/modules/freebsdpkg.py
@@ -310,3 +310,16 @@ def rehash():
shell = __salt__['cmd.run']('echo $SHELL').split('/')
if shell[len(shell) - 1] in ['csh', 'tcsh']:
__salt__['cmd.run']('rehash')
+
+
+def compare(version1='', version2=''):
+ '''
+ Compare two version strings. Return -1 if version1 < version2,
+ 0 if version1 == version2, and 1 if version1 > version2. Return None if
+ there was a problem making the comparison.
+
+ CLI Example::
+
+ salt '*' pkg.compare '0.2.4-0' '0.2.4.1-0'
+ '''
+ return __salt__['pkg_resource.compare'](version1, version2)
View
13 salt/modules/openbsdpkg.py
@@ -189,3 +189,16 @@ def purge(name):
salt '*' pkg.purge <package name>
'''
return remove(name)
+
+
+def compare(version1='', version2=''):
+ '''
+ Compare two version strings. Return -1 if version1 < version2,
+ 0 if version1 == version2, and 1 if version1 > version2. Return None if
+ there was a problem making the comparison.
+
+ CLI Example::
+
+ salt '*' pkg.compare '0.2.4-0' '0.2.4.1-0'
+ '''
+ return __salt__['pkg_resource.compare'](version1, version2)
View
13 salt/modules/pacman.py
@@ -278,3 +278,16 @@ def purge(name):
__salt__['cmd.retcode'](cmd)
new = list_pkgs()
return _list_removed(old, new)
+
+
+def compare(version1='', version2=''):
+ '''
+ Compare two version strings. Return -1 if version1 < version2,
+ 0 if version1 == version2, and 1 if version1 > version2. Return None if
+ there was a problem making the comparison.
+
+ CLI Example::
+
+ salt '*' pkg.compare '0.2.4-0' '0.2.4.1-0'
+ '''
+ return __salt__['pkg_resource.compare'](version1, version2)
View
24 salt/modules/pkg_resource.py
@@ -8,6 +8,7 @@
import yaml
import pprint
import logging
+import distutils.version
log = logging.getLogger(__name__)
@@ -317,3 +318,26 @@ def find_changes(old=None, new=None):
pkgs[npkg] = {'old': old[npkg],
'new': new[npkg]}
return pkgs
+
+
+def compare(pkg1='', pkg2=''):
+ '''
+ Compares two version strings using distutils.version.LooseVersion. This is
+ a fallback for providers which don't have a version comparison utility
+ built into them. Return -1 if version1 < version2, 0 if version1 ==
+ version2, and 1 if version1 > version2. Return None if there was a problem
+ making the comparison.
+ '''
+ try:
+ if distutils.version.LooseVersion(pkg1) < \
+ distutils.version.LooseVersion(pkg2):
+ return -1
+ elif distutils.version.LooseVersion(pkg1) == \
+ distutils.version.LooseVersion(pkg2):
+ return 0
+ elif distutils.version.LooseVersion(pkg1) > \
+ distutils.version.LooseVersion(pkg2):
+ return 1
+ except Exception as e:
+ log.exception(e)
+ return None
View
13 salt/modules/pkgng.py
@@ -218,3 +218,16 @@ def upgrade():
cmd = 'pkg upgrade -y'
return __salt__['cmd.run'](cmd)
+
+
+def compare(version1='', version2=''):
+ '''
+ Compare two version strings. Return -1 if version1 < version2,
+ 0 if version1 == version2, and 1 if version1 > version2. Return None if
+ there was a problem making the comparison.
+
+ CLI Example::
+
+ salt '*' pkg.compare '0.2.4-0' '0.2.4.1-0'
+ '''
+ return __salt__['pkg_resource.compare'](version1, version2)
View
13 salt/modules/pkgutil.py
@@ -251,3 +251,16 @@ def purge(name, **kwargs):
salt '*' pkgutil.purge <package name>
'''
return remove(name, **kwargs)
+
+
+def compare(version1='', version2=''):
+ '''
+ Compare two version strings. Return -1 if version1 < version2,
+ 0 if version1 == version2, and 1 if version1 > version2. Return None if
+ there was a problem making the comparison.
+
+ CLI Example::
+
+ salt '*' pkg.compare '0.2.4-0' '0.2.4.1-0'
+ '''
+ return __salt__['pkg_resource.compare'](version1, version2)
View
13 salt/modules/solarispkg.py
@@ -362,3 +362,16 @@ def purge(name, **kwargs):
salt '*' pkg.purge <package name>
'''
return remove(name, **kwargs)
+
+
+def compare(version1='', version2=''):
+ '''
+ Compare two version strings. Return -1 if version1 < version2,
+ 0 if version1 == version2, and 1 if version1 > version2. Return None if
+ there was a problem making the comparison.
+
+ CLI Example::
+
+ salt '*' pkg.compare '0.2.4-0' '0.2.4.1-0'
+ '''
+ return __salt__['pkg_resource.compare'](version1, version2)
View
13 salt/modules/win_pkg.py
@@ -441,3 +441,16 @@ def _get_latest_pkg_version(pkginfo):
return pkginfo.keys().pop()
pkgkeys = pkginfo.keys()
return sorted(pkgkeys, cmp=_reverse_cmp_pkg_versions).pop()
+
+
+def compare(version1='', version2=''):
+ '''
+ Compare two version strings. Return -1 if version1 < version2,
+ 0 if version1 == version2, and 1 if version1 > version2. Return None if
+ there was a problem making the comparison.
+
+ CLI Example::
+
+ salt '*' pkg.compare '0.2.4-0' '0.2.4.1-0'
+ '''
+ return __salt__['pkg_resource.compare'](version1, version2)
View
11 salt/modules/yumpkg.py
@@ -781,3 +781,14 @@ def _parse_repo_file(filename):
return (header, repos)
+def compare(version1='', version2=''):
+ '''
+ Compare two version strings. Return -1 if version1 < version2,
+ 0 if version1 == version2, and 1 if version1 > version2. Return None if
+ there was a problem making the comparison.
+
+ CLI Example::
+
+ salt '*' pkg.compare '0.2.4-0' '0.2.4.1-0'
+ '''
+ return __salt__['pkg_resource.compare'](version1, version2)
View
13 salt/modules/yumpkg5.py
@@ -317,3 +317,16 @@ def purge(pkg):
salt '*' pkg.purge <package name>
'''
return remove(pkg)
+
+
+def compare(version1='', version2=''):
+ '''
+ Compare two version strings. Return -1 if version1 < version2,
+ 0 if version1 == version2, and 1 if version1 > version2. Return None if
+ there was a problem making the comparison.
+
+ CLI Example::
+
+ salt '*' pkg.compare '0.2.4-0' '0.2.4.1-0'
+ '''
+ return __salt__['pkg_resource.compare'](version1, version2)
View
13 salt/modules/zypper.py
@@ -258,3 +258,16 @@ def purge(name):
__salt__['cmd.retcode'](cmd)
new = list_pkgs()
return _list_removed(old, new)
+
+
+def compare(version1='', version2=''):
+ '''
+ Compare two version strings. Return -1 if version1 < version2,
+ 0 if version1 == version2, and 1 if version1 > version2. Return None if
+ there was a problem making the comparison.
+
+ CLI Example::
+
+ salt '*' pkg.compare '0.2.4-0' '0.2.4.1-0'
+ '''
+ return __salt__['pkg_resource.compare'](version1, version2)
View
215 salt/states/pkg.py
@@ -15,7 +15,6 @@
# Import python libs
import logging
import os
-from distutils.version import LooseVersion
# Import salt libs
import salt.utils
@@ -204,7 +203,7 @@ def installed(
if len(targets) > 1:
failed = [x for x in targets if x not in installed]
comment = 'The following packages failed to install: ' \
- '{0}'.format(', '.join(failed))
+ '{0}'.format(', '.join(sorted(failed)))
else:
comment = 'Package {0} failed to install'.format(targets[0])
@@ -216,7 +215,7 @@ def installed(
# Success!
if len(targets) > 1:
comment = 'The following pacakages were installed: ' \
- '{0}'.format(', '.join(targets))
+ '{0}'.format(', '.join(sorted(targets)))
else:
comment = 'Package {0} installed'.format(targets[0])
@@ -226,7 +225,13 @@ def installed(
'comment': comment}
-def latest(name, refresh=False, fromrepo=None, skip_verify=False, **kwargs):
+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
@@ -235,74 +240,178 @@ def latest(name, refresh=False, fromrepo=None, skip_verify=False, **kwargs):
available.
name
- The name of the package to maintain at the latest available version
+ 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: (currently supported for apt only)
+
+ pkgs
+ A list of packages to maintain at the latest available version.
+
+ Usage::
+
+ mypkgs:
+ pkg.latest:
+ - pkgs:
+ - foo
+ - bar
+ - baz
'''
rtag = __gen_rtag()
- ret = {'name': name, 'changes': {}, 'result': False, 'comment': ''}
- version = __salt__['pkg.version'](name)
- avail = __salt__['pkg.available_version'](name)
-
- if not version:
- # Net yet installed
- has_newer = True
- elif not avail:
- # Already at latest
- has_newer = False
+ if kwargs.get('sources'):
+ return {'name': name,
+ 'changes': {},
+ 'result': False,
+ 'comment': 'The "sources" parameter is not supported.'}
+ elif pkgs:
+ desired_pkgs = __salt__['pkg_resource.pack_pkgs'](pkgs)
+ if not desired_pkgs:
+ # Badly-formatted SLS
+ return {'name': name,
+ 'changes': {},
+ 'result': False,
+ 'comment': 'Invalidly formatted "pkgs" parameter. See '
+ 'minion log.'}
else:
- try:
- has_newer = LooseVersion(avail) > LooseVersion(version)
- except AttributeError:
- log.debug(
- 'Error comparing versions' ' for "{0}" ({1} > {2})'.format(
- name,
- avail,
- version
- )
- )
- ret['comment'] = 'No version could be retrieved for "{0}"'.format(
- name
- )
- return ret
-
- if has_newer:
+ desired_pkgs = [name]
+
+ cur = __salt__['pkg.version'](*desired_pkgs)
+ avail = __salt__['pkg.available_version'](*desired_pkgs)
+
+ # 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)
+ else:
+ msg = 'Unable to find newest version for "{0}".'.format(pkg)
+ log.error(msg)
+ problems.append(msg)
+ else:
+ if not cur[pkg]:
+ # Not yet installed
+ targets[pkg] = avail[pkg]
+ else:
+ try:
+ if __salt__['pkg.compare'](avail[pkg], cur[pkg]) == 1:
+ targets[pkg] = avail[pkg]
+ except AttributeError:
+ msg = 'Unable to compare versions for "{0}" ' \
+ '({1} > {2})'.format(name, avail, version)
+ log.error(msg)
+ problems.append(msg)
+
+ if problems:
+ return {'name': name,
+ 'changes': {},
+ 'result': False,
+ 'comment': ' '.join(problems)}
+
+ if targets:
if __opts__['test']:
- ret['result'] = None
- ret['comment'] = 'Package {0} is set to be upgraded'.format(name)
- return ret
+ to_be_upgraded = ', '.join(sorted(targets.keys()))
+ return {'name': name,
+ 'changes': {},
+ 'result': None,
+ 'comment': 'The following packages are set to be '
+ 'upgraded: {0}'.format(to_be_upgraded)}
+
+ # Build updated list of pkgs to exclude non-targeted ones
+ targeted_pkgs = targets.keys() if pkgs else None
+
if refresh or os.path.isfile(rtag):
- ret['changes'] = __salt__['pkg.install'](name,
- refresh=True,
- fromrepo=fromrepo,
- skip_verify=skip_verify,
- **kwargs)
+ changes = __salt__['pkg.install'](name,
+ refresh=True,
+ fromrepo=fromrepo,
+ skip_verify=skip_verify,
+ pkgs=targeted_pkgs,
+ **kwargs)
if os.path.isfile(rtag):
os.remove(rtag)
else:
- ret['changes'] = __salt__['pkg.install'](name,
- fromrepo=fromrepo,
- skip_verify=skip_verify,
- **kwargs)
-
- if ret['changes']:
- ret['comment'] = 'Package {0} upgraded to latest'.format(name)
- ret['result'] = True
+ changes = __salt__['pkg.install'](name,
+ fromrepo=fromrepo,
+ skip_verify=skip_verify,
+ pkgs=targeted_pkgs,
+ **kwargs)
+
+ # 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 was 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 changes:
+ # Find failed and successful updates
+ failed = [x for x in targets if changes[x]['new'] != targets[x]]
+ successful = [x for x in targets if x not in failed]
+
+ comments = []
+ if failed:
+ msg = 'The following package(s) failed to update: ' \
+ '{0}.'.format(', '.join(sorted(failed)))
+ comments.append(msg)
+ if successful:
+ msg = 'The following package(s) were successfully updated: ' \
+ '{0}.'.format(', '.join(sorted(successful)))
+ comments.append(msg)
+ if up_to_date:
+ msg = 'The following package(s) were already up-to-date: ' \
+ '{0}.'.format(', '.join(sorted(up_to_date)))
+ comments.append(msg)
+
+ return {'name': name,
+ 'changes': changes,
+ 'result': False if failed else True,
+ 'comment': ' '.join(comments)}
else:
- ret['comment'] = 'Package {0} failed to install'.format(name)
- ret['result'] = False
- return ret
+ if 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:
+ comment += '. The following package(s) were already ' \
+ 'up-to-date: ' \
+ '{0}'.format(', '.join(sorted(up_to_date)))
+ return {'name': name,
+ 'changes': changes,
+ 'result': False,
+ 'comment': comment}
else:
- ret['comment'] = 'Package {0} already at latest'.format(name)
- ret['result'] = True
+ if 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 ret
+ return {'name': name,
+ 'changes': {},
+ 'result': True,
+ 'comment': comment}
def removed(name):
Please sign in to comment.
Something went wrong with that request. Please try again.