You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2275 lines
84 KiB
2275 lines
84 KiB
diff --git a/salt/modules/yumpkg.py b/salt/modules/yumpkg.py
|
|
index 25b26f0..91258a3 100644
|
|
--- a/salt/modules/yumpkg.py
|
|
+++ b/salt/modules/yumpkg.py
|
|
@@ -1,14 +1,19 @@
|
|
# -*- coding: utf-8 -*-
|
|
'''
|
|
-Support for YUM
|
|
+Support for YUM/DNF
|
|
|
|
.. note::
|
|
- This module makes heavy use of the **repoquery** utility, from the
|
|
- yum-utils_ package. This package will be installed as a dependency if salt
|
|
- is installed via EPEL. However, if salt has been installed using pip, or a
|
|
+ This module makes use of the **repoquery** utility, from the yum-utils_
|
|
+ package. This package will be installed as a dependency if salt is
|
|
+ installed via EPEL. However, if salt has been installed using pip, or a
|
|
host is being managed using salt-ssh, then as of version 2014.7.0
|
|
yum-utils_ will be installed automatically to satisfy this dependency.
|
|
|
|
+ DNF is fully supported as of version 2015.5.10 and 2015.8.4 (partial
|
|
+ support for DNF was initially added in 2015.8.0), and DNF is used
|
|
+ automatically in place of YUM in Fedora 22 and newer. For these versions,
|
|
+ repoquery is available from the ``dnf-plugins-core`` package.
|
|
+
|
|
.. _yum-utils: http://yum.baseurl.org/wiki/YumUtils
|
|
|
|
'''
|
|
@@ -16,16 +21,18 @@ Support for YUM
|
|
# Import python libs
|
|
from __future__ import absolute_import
|
|
import copy
|
|
+import fnmatch
|
|
+import itertools
|
|
import logging
|
|
import os
|
|
import re
|
|
+import string
|
|
from distutils.version import LooseVersion as _LooseVersion # pylint: disable=no-name-in-module,import-error
|
|
|
|
# Import 3rd-party libs
|
|
# pylint: disable=import-error,redefined-builtin
|
|
-import salt.ext.six as six
|
|
-from salt.ext.six import string_types
|
|
-from salt.ext.six.moves import shlex_quote as _cmd_quote, range
|
|
+from salt.ext import six
|
|
+from salt.ext.six.moves import zip
|
|
|
|
try:
|
|
import yum
|
|
@@ -39,36 +46,20 @@ try:
|
|
HAS_RPMUTILS = True
|
|
except ImportError:
|
|
HAS_RPMUTILS = False
|
|
-# pylint: enable=import-error
|
|
+# pylint: enable=import-error,redefined-builtin
|
|
|
|
# Import salt libs
|
|
import salt.utils
|
|
+import salt.utils.itertools
|
|
import salt.utils.decorators as decorators
|
|
+import salt.utils.pkg.rpm
|
|
from salt.exceptions import (
|
|
CommandExecutionError, MinionError, SaltInvocationError
|
|
)
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
-__QUERYFORMAT = '%{NAME}_|-%{VERSION}_|-%{RELEASE}_|-%{ARCH}_|-%{REPOID}'
|
|
-
|
|
-# These arches compiled from the rpmUtils.arch python module source
|
|
-__ARCHES_64 = ('x86_64', 'athlon', 'amd64', 'ia32e', 'ia64', 'geode')
|
|
-__ARCHES_32 = ('i386', 'i486', 'i586', 'i686')
|
|
-__ARCHES_PPC = ('ppc', 'ppc64', 'ppc64iseries', 'ppc64pseries')
|
|
-__ARCHES_S390 = ('s390', 's390x')
|
|
-__ARCHES_SPARC = (
|
|
- 'sparc', 'sparcv8', 'sparcv9', 'sparcv9v', 'sparc64', 'sparc64v'
|
|
-)
|
|
-__ARCHES_ALPHA = (
|
|
- 'alpha', 'alphaev4', 'alphaev45', 'alphaev5', 'alphaev56',
|
|
- 'alphapca56', 'alphaev6', 'alphaev67', 'alphaev68', 'alphaev7'
|
|
-)
|
|
-__ARCHES_ARM = ('armv5tel', 'armv5tejl', 'armv6l', 'armv7l')
|
|
-__ARCHES_SH = ('sh3', 'sh4', 'sh4a')
|
|
-
|
|
-__ARCHES = __ARCHES_64 + __ARCHES_32 + __ARCHES_PPC + __ARCHES_S390 + \
|
|
- __ARCHES_ALPHA + __ARCHES_ARM + __ARCHES_SH
|
|
+__HOLD_PATTERN = r'\w+(?:[.-][^-]+)*'
|
|
|
|
# Define the module's virtual name
|
|
__virtualname__ = 'pkg'
|
|
@@ -93,32 +84,63 @@ def __virtual__():
|
|
return False
|
|
|
|
|
|
-def _parse_pkginfo(line):
|
|
- '''
|
|
- A small helper to parse a repoquery; returns a namedtuple
|
|
+def _strip_headers(output, *args):
|
|
+ if not args:
|
|
+ args_lc = ('installed packages',
|
|
+ 'available packages',
|
|
+ 'updated packages',
|
|
+ 'upgraded packages')
|
|
+ else:
|
|
+ args_lc = [x.lower() for x in args]
|
|
+ ret = ''
|
|
+ for line in salt.utils.itertools.split(output, '\n'):
|
|
+ if line.lower() not in args_lc:
|
|
+ ret += line + '\n'
|
|
+ return ret
|
|
+
|
|
+
|
|
+def _get_hold(line, pattern=__HOLD_PATTERN, full=True):
|
|
'''
|
|
- # Importing `collections` here since this function is re-namespaced into
|
|
- # another module
|
|
- import collections
|
|
- pkginfo = collections.namedtuple(
|
|
- 'PkgInfo',
|
|
- ('name', 'version', 'arch', 'repoid')
|
|
- )
|
|
+ Resolve a package name from a line containing the hold expression. If the
|
|
+ regex is not matched, None is returned.
|
|
|
|
- try:
|
|
- name, pkg_version, release, arch, repoid = line.split('_|-')
|
|
- # Handle unpack errors (should never happen with the queryformat we are
|
|
- # using, but can't hurt to be careful).
|
|
- except ValueError:
|
|
- return None
|
|
+ yum ==> 2:vim-enhanced-7.4.629-5.el6.*
|
|
+ dnf ==> vim-enhanced-2:7.4.827-1.fc22.*
|
|
+ '''
|
|
+ if full:
|
|
+ if _yum() == 'dnf':
|
|
+ lock_re = r'({0}-\S+)'.format(pattern)
|
|
+ else:
|
|
+ lock_re = r'(\d+:{0}-\S+)'.format(pattern)
|
|
+ else:
|
|
+ if _yum() == 'dnf':
|
|
+ lock_re = r'({0}-\S+)'.format(pattern)
|
|
+ else:
|
|
+ lock_re = r'\d+:({0}-\S+)'.format(pattern)
|
|
+
|
|
+ match = re.search(lock_re, line)
|
|
+ if match:
|
|
+ if not full:
|
|
+ woarch = match.group(1).rsplit('.', 1)[0]
|
|
+ worel = woarch.rsplit('-', 1)[0]
|
|
+ return worel.rsplit('-', 1)[0]
|
|
+ else:
|
|
+ return match.group(1)
|
|
+ return None
|
|
|
|
- if not _check_32(arch):
|
|
- if arch not in (__grains__['osarch'], 'noarch'):
|
|
- name += '.{0}'.format(arch)
|
|
- if release:
|
|
- pkg_version += '-{0}'.format(release)
|
|
|
|
- return pkginfo(name, pkg_version, arch, repoid)
|
|
+def _yum():
|
|
+ '''
|
|
+ return yum or dnf depending on version
|
|
+ '''
|
|
+ contextkey = 'yum_bin'
|
|
+ if contextkey not in __context__:
|
|
+ if 'fedora' in __grains__['os'].lower() \
|
|
+ and int(__grains__['osrelease']) >= 22:
|
|
+ __context__[contextkey] = 'dnf'
|
|
+ else:
|
|
+ __context__[contextkey] = 'yum'
|
|
+ return __context__[contextkey]
|
|
|
|
|
|
def _repoquery_pkginfo(repoquery_args):
|
|
@@ -126,8 +148,11 @@ def _repoquery_pkginfo(repoquery_args):
|
|
Wrapper to call repoquery and parse out all the tuples
|
|
'''
|
|
ret = []
|
|
- for line in _repoquery(repoquery_args):
|
|
- pkginfo = _parse_pkginfo(line)
|
|
+ for line in _repoquery(repoquery_args, ignore_stderr=True):
|
|
+ pkginfo = salt.utils.pkg.rpm.parse_pkginfo(
|
|
+ line,
|
|
+ osarch=__grains__['osarch']
|
|
+ )
|
|
if pkginfo is not None:
|
|
ret.append(pkginfo)
|
|
return ret
|
|
@@ -138,38 +163,141 @@ def _check_repoquery():
|
|
Check for existence of repoquery and install yum-utils if it is not
|
|
present.
|
|
'''
|
|
- if not salt.utils.which('repoquery'):
|
|
- __salt__['cmd.run'](
|
|
- ['yum', '-y', 'install', 'yum-utils'],
|
|
- python_shell=False,
|
|
- output_loglevel='trace'
|
|
+ contextkey = 'yumpkg.has_repoquery'
|
|
+ if contextkey in __context__:
|
|
+ # We don't really care about the return value, we're just using this
|
|
+ # context key as a marker so that we know that repoquery is available,
|
|
+ # and we don't have to continue to repeat the check if this function is
|
|
+ # called more than once per run. If repoquery is not available, we
|
|
+ # raise an exception.
|
|
+ return
|
|
+
|
|
+ if _yum() == 'dnf':
|
|
+ # For some silly reason the core plugins and their manpages are in
|
|
+ # separate packages. The dnf-plugins-core package contains the manpages
|
|
+ # and depends on python-dnf-plugins-core (which contains the actual
|
|
+ # plugins).
|
|
+ def _check_plugins():
|
|
+ out = __salt__['cmd.run_all'](
|
|
+ ['rpm', '-q', '--queryformat', '%{VERSION}\n',
|
|
+ 'dnf-plugins-core'],
|
|
+ python_shell=False,
|
|
+ ignore_retcode=True
|
|
+ )
|
|
+ if out['retcode'] != 0:
|
|
+ return False
|
|
+ if salt.utils.compare_versions(ver1=out['stdout'],
|
|
+ oper='<',
|
|
+ ver2='0.1.15',
|
|
+ cmp_func=version_cmp):
|
|
+ return False
|
|
+ __context__[contextkey] = True
|
|
+ return True
|
|
+
|
|
+ if not _check_plugins():
|
|
+ __salt__['cmd.run'](
|
|
+ ['dnf', '-y', 'install', 'dnf-plugins-core >= 0.1.15'],
|
|
+ python_shell=False,
|
|
+ output_loglevel='trace'
|
|
+ )
|
|
+ # Check again now that we've installed dnf-plugins-core
|
|
+ if not _check_plugins():
|
|
+ raise CommandExecutionError('Unable to install dnf-plugins-core')
|
|
+ else:
|
|
+ if salt.utils.which('repoquery'):
|
|
+ __context__[contextkey] = True
|
|
+ else:
|
|
+ __salt__['cmd.run'](
|
|
+ ['yum', '-y', 'install', 'yum-utils'],
|
|
+ python_shell=False,
|
|
+ output_loglevel='trace'
|
|
+ )
|
|
+ # Check again now that we've installed yum-utils
|
|
+ if salt.utils.which('repoquery'):
|
|
+ __context__[contextkey] = True
|
|
+ else:
|
|
+ raise CommandExecutionError('Unable to install yum-utils')
|
|
+
|
|
+
|
|
+def _yum_pkginfo(output):
|
|
+ '''
|
|
+ Parse yum/dnf output (which could contain irregular line breaks if package
|
|
+ names are long) retrieving the name, version, etc., and return a list of
|
|
+ pkginfo namedtuples.
|
|
+ '''
|
|
+ cur = {}
|
|
+ keys = itertools.cycle(('name', 'version', 'repoid'))
|
|
+ values = salt.utils.itertools.split(_strip_headers(output))
|
|
+ osarch = __grains__['osarch']
|
|
+ for (key, value) in zip(keys, values):
|
|
+ if key == 'name':
|
|
+ try:
|
|
+ cur['name'], cur['arch'] = value.rsplit('.', 1)
|
|
+ except ValueError:
|
|
+ cur['name'] = value
|
|
+ cur['arch'] = osarch
|
|
+ cur['name'] = salt.utils.pkg.rpm.resolve_name(cur['name'],
|
|
+ cur['arch'],
|
|
+ osarch)
|
|
+ else:
|
|
+ if key == 'repoid':
|
|
+ # Installed packages show a '@' at the beginning
|
|
+ value = value.lstrip('@')
|
|
+ cur[key] = value
|
|
+ if key == 'repoid':
|
|
+ # We're done with this package, create the pkginfo namedtuple
|
|
+ pkginfo = salt.utils.pkg.rpm.pkginfo(**cur)
|
|
+ # Clear the dict for the next package
|
|
+ cur = {}
|
|
+ # Yield the namedtuple
|
|
+ if pkginfo is not None:
|
|
+ yield pkginfo
|
|
+
|
|
+
|
|
+def _check_versionlock():
|
|
+ '''
|
|
+ Ensure that the appropriate versionlock plugin is present
|
|
+ '''
|
|
+ if _yum() == 'dnf':
|
|
+ vl_plugin = 'python-dnf-plugins-extras-versionlock'
|
|
+ else:
|
|
+ vl_plugin = 'yum-versionlock' \
|
|
+ if __grains__.get('osmajorrelease') == '5' \
|
|
+ else 'yum-plugin-versionlock'
|
|
+
|
|
+ if vl_plugin not in list_pkgs():
|
|
+ raise SaltInvocationError(
|
|
+ 'Cannot proceed, {0} is not installed.'.format(vl_plugin)
|
|
)
|
|
- # Check again now that we've installed yum-utils
|
|
- if not salt.utils.which('repoquery'):
|
|
- raise CommandExecutionError('Unable to install yum-utils')
|
|
|
|
|
|
-def _repoquery(repoquery_args, query_format=__QUERYFORMAT):
|
|
+def _repoquery(repoquery_args,
|
|
+ query_format=salt.utils.pkg.rpm.QUERYFORMAT,
|
|
+ ignore_stderr=False):
|
|
'''
|
|
Runs a repoquery command and returns a list of namedtuples
|
|
'''
|
|
_check_repoquery()
|
|
- cmd = 'repoquery --plugins --queryformat {0} {1}'.format(
|
|
- _cmd_quote(query_format), repoquery_args
|
|
- )
|
|
+ if _yum() == 'dnf':
|
|
+ cmd = ['dnf', 'repoquery', '--quiet', '--queryformat', query_format]
|
|
+ else:
|
|
+ cmd = ['repoquery', '--plugins', '--queryformat', query_format]
|
|
+
|
|
+ cmd.extend(repoquery_args)
|
|
call = __salt__['cmd.run_all'](cmd, output_loglevel='trace')
|
|
if call['retcode'] != 0:
|
|
comment = ''
|
|
- if 'stderr' in call:
|
|
+ # When checking for packages some yum modules return data via stderr
|
|
+ # that don't cause non-zero return codes. A perfect example of this is
|
|
+ # when spacewalk is installed but not yet registered. We should ignore
|
|
+ # those when getting pkginfo.
|
|
+ if 'stderr' in call and not salt.utils.is_true(ignore_stderr):
|
|
comment += call['stderr']
|
|
if 'stdout' in call:
|
|
comment += call['stdout']
|
|
- raise CommandExecutionError(
|
|
- '{0}'.format(comment)
|
|
- )
|
|
+ raise CommandExecutionError(comment)
|
|
else:
|
|
- out = call['stdout']
|
|
- return out.splitlines()
|
|
+ return call['stdout'].splitlines()
|
|
|
|
|
|
def _get_repo_options(**kwargs):
|
|
@@ -187,20 +315,38 @@ def _get_repo_options(**kwargs):
|
|
if repo and not fromrepo:
|
|
fromrepo = repo
|
|
|
|
- repo_arg = ''
|
|
+ use_dnf_repoquery = kwargs.get('repoquery', False) and _yum() == 'dnf'
|
|
+ ret = []
|
|
if fromrepo:
|
|
- log.info('Restricting to repo {0!r}'.format(fromrepo))
|
|
- repo_arg = ('--disablerepo={0!r} --enablerepo={1!r}'
|
|
- .format('*', fromrepo))
|
|
+ log.info('Restricting to repo \'%s\'', fromrepo)
|
|
+ if use_dnf_repoquery:
|
|
+ # dnf-plugins-core renamed --repoid to --repo in version 0.1.7, but
|
|
+ # still keeps it as a hidden option for backwards compatibility.
|
|
+ # This is good, because --repo does not work at all (see
|
|
+ # https://bugzilla.redhat.com/show_bug.cgi?id=1299261 for more
|
|
+ # information). Using --repoid here so this will actually work.
|
|
+ ret.append('--repoid={0}'.format(fromrepo))
|
|
+ else:
|
|
+ ret.extend(['--disablerepo=*',
|
|
+ '--enablerepo={0}'.format(fromrepo)])
|
|
else:
|
|
- repo_arg = ''
|
|
if disablerepo:
|
|
- log.info('Disabling repo {0!r}'.format(disablerepo))
|
|
- repo_arg += '--disablerepo={0!r}'.format(disablerepo)
|
|
+ if use_dnf_repoquery:
|
|
+ log.warning(
|
|
+ 'ignoring disablerepo, not supported in dnf repoquery'
|
|
+ )
|
|
+ else:
|
|
+ log.info('Disabling repo \'%s\'', disablerepo)
|
|
+ ret.append('--disablerepo={0}'.format(disablerepo))
|
|
if enablerepo:
|
|
- log.info('Enabling repo {0!r}'.format(enablerepo))
|
|
- repo_arg += '--enablerepo={0!r}'.format(enablerepo)
|
|
- return repo_arg
|
|
+ if use_dnf_repoquery:
|
|
+ log.warning(
|
|
+ 'ignoring enablerepo, not supported in dnf repoquery'
|
|
+ )
|
|
+ else:
|
|
+ log.info('Enabling repo \'%s\'', enablerepo)
|
|
+ ret.append('--enablerepo={0}'.format(enablerepo))
|
|
+ return ret
|
|
|
|
|
|
def _get_excludes_option(**kwargs):
|
|
@@ -208,14 +354,18 @@ def _get_excludes_option(**kwargs):
|
|
Returns a string of '--disableexcludes' option to be used in the yum command,
|
|
based on the kwargs.
|
|
'''
|
|
- disable_excludes_arg = ''
|
|
disable_excludes = kwargs.get('disableexcludes', '')
|
|
|
|
if disable_excludes:
|
|
- log.info('Disabling excludes for {0!r}'.format(disable_excludes))
|
|
- disable_excludes_arg = ('--disableexcludes={0!r}'.format(disable_excludes))
|
|
-
|
|
- return disable_excludes_arg
|
|
+ if kwargs.get('repoquery', False) and _yum() == 'dnf':
|
|
+ log.warning(
|
|
+ 'Ignoring disableexcludes, not supported in dnf repoquery'
|
|
+ )
|
|
+ return []
|
|
+ else:
|
|
+ log.info('Disabling excludes for \'%s\'', disable_excludes)
|
|
+ return ['--disableexcludes=\'{0}\''.format(disable_excludes)]
|
|
+ return []
|
|
|
|
|
|
def _get_branch_option(**kwargs):
|
|
@@ -226,45 +376,11 @@ def _get_branch_option(**kwargs):
|
|
# Get branch option from the kwargs
|
|
branch = kwargs.get('branch', '')
|
|
|
|
- branch_arg = ''
|
|
+ ret = []
|
|
if branch:
|
|
- log.info('Adding branch {0!r}'.format(branch))
|
|
- branch_arg = ('--branch={0!r}'.format(branch))
|
|
- return branch_arg
|
|
-
|
|
-
|
|
-def _check_32(arch):
|
|
- '''
|
|
- Returns True if both the OS arch and the passed arch are 32-bit
|
|
- '''
|
|
- return all(x in __ARCHES_32 for x in (__grains__['osarch'], arch))
|
|
-
|
|
-
|
|
-def _rpm_pkginfo(name):
|
|
- '''
|
|
- Parses RPM metadata and returns a pkginfo namedtuple
|
|
- '''
|
|
- # REPOID is not a valid tag for the rpm command. Remove it and replace it
|
|
- # with 'none'
|
|
- queryformat = __QUERYFORMAT.replace('%{REPOID}', 'none')
|
|
- output = __salt__['cmd.run_stdout'](
|
|
- 'rpm -qp --queryformat {0!r} {1}'.format(_cmd_quote(queryformat), name),
|
|
- output_loglevel='trace',
|
|
- ignore_retcode=True
|
|
- )
|
|
- return _parse_pkginfo(output)
|
|
-
|
|
-
|
|
-def _rpm_installed(name):
|
|
- '''
|
|
- Parses RPM metadata to determine if the RPM target is already installed.
|
|
- Returns the name of the installed package if found, otherwise None.
|
|
- '''
|
|
- pkg = _rpm_pkginfo(name)
|
|
- try:
|
|
- return pkg.name if pkg.name in list_pkgs() else None
|
|
- except AttributeError:
|
|
- return None
|
|
+ log.info('Adding branch \'%s\'', branch)
|
|
+ ret.append('--branch=\'{0}\''.format(branch))
|
|
+ return ret
|
|
|
|
|
|
def _get_yum_config():
|
|
@@ -275,9 +391,9 @@ def _get_yum_config():
|
|
This is currently only used to get the reposdir settings, but could be used
|
|
for other things if needed.
|
|
|
|
- If the yum python library is available, use that, which will give us
|
|
- all of the options, including all of the defaults not specified in the
|
|
- yum config. Additionally, they will all be of the correct object type.
|
|
+ If the yum python library is available, use that, which will give us all of
|
|
+ the options, including all of the defaults not specified in the yum config.
|
|
+ Additionally, they will all be of the correct object type.
|
|
|
|
If the yum library is not available, we try to read the yum.conf
|
|
directly ourselves with a minimal set of "defaults".
|
|
@@ -291,7 +407,7 @@ def _get_yum_config():
|
|
try:
|
|
yb = yum.YumBase()
|
|
yb.preconf.init_plugins = False
|
|
- for name, value in yb.conf.iteritems():
|
|
+ for name, value in six.iteritems(yb.conf):
|
|
conf[name] = value
|
|
except (AttributeError, yum.Errors.ConfigError) as exc:
|
|
raise CommandExecutionError(
|
|
@@ -324,11 +440,16 @@ def _get_yum_config():
|
|
for opt in cp.options('main'):
|
|
if opt in ('reposdir', 'commands', 'excludes'):
|
|
# these options are expected to be lists
|
|
- conf[opt] = [x.strip() for x in cp.get('main', opt).split(',')]
|
|
+ conf[opt] = [x.strip()
|
|
+ for x in cp.get('main', opt).split(',')]
|
|
else:
|
|
conf[opt] = cp.get('main', opt)
|
|
else:
|
|
- log.warning('Could not find [main] section in {0}, using internal defaults'.format(fn))
|
|
+ log.warning(
|
|
+ 'Could not find [main] section in %s, using internal '
|
|
+ 'defaults',
|
|
+ fn
|
|
+ )
|
|
|
|
return conf
|
|
|
|
@@ -350,13 +471,13 @@ def _normalize_basedir(basedir=None):
|
|
|
|
Returns a list of directories.
|
|
'''
|
|
- if basedir is None:
|
|
- basedir = []
|
|
-
|
|
# if we are passed a string (for backward compatibility), convert to a list
|
|
- if isinstance(basedir, basestring):
|
|
+ if isinstance(basedir, six.string_types):
|
|
basedir = [x.strip() for x in basedir.split(',')]
|
|
|
|
+ if basedir is None:
|
|
+ basedir = []
|
|
+
|
|
# nothing specified, so use the reposdir option as the default
|
|
if not basedir:
|
|
basedir = _get_yum_config_value('reposdir')
|
|
@@ -383,11 +504,12 @@ def normalize_name(name):
|
|
'''
|
|
try:
|
|
arch = name.rsplit('.', 1)[-1]
|
|
- if arch not in __ARCHES + ('noarch',):
|
|
+ if arch not in salt.utils.pkg.rpm.ARCHES + ('noarch',):
|
|
return name
|
|
except ValueError:
|
|
return name
|
|
- if arch in (__grains__['osarch'], 'noarch') or _check_32(arch):
|
|
+ if arch in (__grains__['osarch'], 'noarch') \
|
|
+ or salt.utils.pkg.rpm.check_32(arch, osarch=__grains__['osarch']):
|
|
return name[:-(len(arch) + 1)]
|
|
return name
|
|
|
|
@@ -423,25 +545,20 @@ def latest_version(*names, **kwargs):
|
|
# Initialize the return dict with empty strings, and populate namearch_map.
|
|
# namearch_map will provide a means of distinguishing between multiple
|
|
# matches for the same package name, for example a target of 'glibc' on an
|
|
- # x86_64 arch would return both x86_64 and i686 versions when searched
|
|
- # using repoquery:
|
|
- #
|
|
- # $ repoquery --all --pkgnarrow=available glibc
|
|
- # glibc-0:2.12-1.132.el6.i686
|
|
- # glibc-0:2.12-1.132.el6.x86_64
|
|
+ # x86_64 arch would return both x86_64 and i686 versions.
|
|
#
|
|
# Note that the logic in the for loop below would place the osarch into the
|
|
# map for noarch packages, but those cases are accounted for when iterating
|
|
- # through the repoquery results later on. If the repoquery match for that
|
|
- # package is a noarch, then the package is assumed to be noarch, and the
|
|
- # namearch_map is ignored.
|
|
+ # through the 'yum list' results later on. If the match for that package is
|
|
+ # a noarch, then the package is assumed to be noarch, and the namearch_map
|
|
+ # is ignored.
|
|
ret = {}
|
|
namearch_map = {}
|
|
for name in names:
|
|
ret[name] = ''
|
|
try:
|
|
arch = name.rsplit('.', 1)[-1]
|
|
- if arch not in __ARCHES:
|
|
+ if arch not in salt.utils.pkg.rpm.ARCHES:
|
|
arch = __grains__['osarch']
|
|
except ValueError:
|
|
arch = __grains__['osarch']
|
|
@@ -454,19 +571,46 @@ def latest_version(*names, **kwargs):
|
|
if refresh:
|
|
refresh_db(**kwargs)
|
|
|
|
- # Get updates for specified package(s)
|
|
- updates = _repoquery_pkginfo(
|
|
- '{0} {1} --pkgnarrow=available {2}'
|
|
- .format(repo_arg, exclude_arg, ' '.join(names))
|
|
- )
|
|
+ # Get available versions for specified package(s)
|
|
+ cmd = [_yum(), '--quiet']
|
|
+ cmd.extend(repo_arg)
|
|
+ cmd.extend(exclude_arg)
|
|
+ cmd.extend(['list', 'available'])
|
|
+ cmd.extend(names)
|
|
+ out = __salt__['cmd.run_all'](cmd,
|
|
+ output_loglevel='trace',
|
|
+ ignore_retcode=True,
|
|
+ python_shell=False)
|
|
+ if out['retcode'] != 0:
|
|
+ if out['stderr']:
|
|
+ # Check first if this is just a matter of the packages being
|
|
+ # up-to-date.
|
|
+ cur_pkgs = list_pkgs()
|
|
+ if not all([x in cur_pkgs for x in names]):
|
|
+ log.error(
|
|
+ 'Problem encountered getting latest version for the '
|
|
+ 'following package(s): %s. Stderr follows: \n%s',
|
|
+ ', '.join(names),
|
|
+ out['stderr']
|
|
+ )
|
|
+ updates = []
|
|
+ else:
|
|
+ # Sort by version number (highest to lowest) for loop below
|
|
+ updates = sorted(
|
|
+ _yum_pkginfo(out['stdout']),
|
|
+ key=lambda pkginfo: _LooseVersion(pkginfo.version),
|
|
+ reverse=True
|
|
+ )
|
|
|
|
for name in names:
|
|
for pkg in (x for x in updates if x.name == name):
|
|
if pkg.arch == 'noarch' or pkg.arch == namearch_map[name] \
|
|
- or _check_32(pkg.arch):
|
|
+ or salt.utils.pkg.rpm.check_32(pkg.arch):
|
|
ret[name] = pkg.version
|
|
# no need to check another match, if there was one
|
|
break
|
|
+ else:
|
|
+ ret[name] = ''
|
|
|
|
# Return a string if only one package name passed
|
|
if len(names) == 1:
|
|
@@ -533,8 +677,8 @@ def version_cmp(pkg1, pkg2):
|
|
return cmp_result
|
|
except Exception as exc:
|
|
log.warning(
|
|
- 'Failed to compare version \'{0}\' to \'{1}\' using '
|
|
- 'rpmUtils: {2}'.format(pkg1, pkg2, exc)
|
|
+ 'Failed to compare version \'%s\' to \'%s\' using '
|
|
+ 'rpmUtils: %s', pkg1, pkg2, exc
|
|
)
|
|
# Fall back to distutils.version.LooseVersion (should only need to do
|
|
# this for RHEL5, or if an exception is raised when attempting to compare
|
|
@@ -569,10 +713,20 @@ def list_pkgs(versions_as_list=False, **kwargs):
|
|
return ret
|
|
|
|
ret = {}
|
|
- for pkginfo in _repoquery_pkginfo('--all --pkgnarrow=installed'):
|
|
- if pkginfo is None:
|
|
- continue
|
|
- __salt__['pkg_resource.add_pkg'](ret, pkginfo.name, pkginfo.version)
|
|
+ cmd = ['rpm', '-qa', '--queryformat',
|
|
+ salt.utils.pkg.rpm.QUERYFORMAT.replace('%{REPOID}', '(none)\n')]
|
|
+ output = __salt__['cmd.run'](cmd,
|
|
+ python_shell=False,
|
|
+ output_loglevel='trace')
|
|
+ for line in output.splitlines():
|
|
+ pkginfo = salt.utils.pkg.rpm.parse_pkginfo(
|
|
+ line,
|
|
+ osarch=__grains__['osarch']
|
|
+ )
|
|
+ if pkginfo is not None:
|
|
+ __salt__['pkg_resource.add_pkg'](ret,
|
|
+ pkginfo.name,
|
|
+ pkginfo.version)
|
|
|
|
__salt__['pkg_resource.sort_pkglist'](ret)
|
|
__context__['pkg.list_pkgs'] = copy.deepcopy(ret)
|
|
@@ -593,6 +747,14 @@ def list_repo_pkgs(*args, **kwargs):
|
|
can be passed and the results will be filtered to packages matching those
|
|
names. This is recommended as it speeds up the function considerably.
|
|
|
|
+ .. warning::
|
|
+ Running this function on RHEL/CentOS 6 and earlier will be more
|
|
+ resource-intensive, as the version of yum that ships with older
|
|
+ RHEL/CentOS has no yum subcommand for listing packages from a
|
|
+ repository. Thus, a ``yum list installed`` and ``yum list available``
|
|
+ are run, which generates a lot of output, which must then be analyzed
|
|
+ to determine which package information to include in the return data.
|
|
+
|
|
This function can be helpful in discovering the version or repo to specify
|
|
in a :mod:`pkg.installed <salt.states.pkg.installed>` state.
|
|
|
|
@@ -643,17 +805,70 @@ def list_repo_pkgs(*args, **kwargs):
|
|
)
|
|
|
|
ret = {}
|
|
- for repo in repos:
|
|
- repoquery_cmd = '--all --repoid="{0}" --show-duplicates'.format(repo)
|
|
+
|
|
+ def _check_args(args, name):
|
|
+ '''
|
|
+ Do glob matching on args and return True if a match was found.
|
|
+ Otherwise, return False
|
|
+ '''
|
|
for arg in args:
|
|
- repoquery_cmd += ' "{0}"'.format(arg)
|
|
- all_pkgs = _repoquery_pkginfo(repoquery_cmd)
|
|
- for pkg in all_pkgs:
|
|
+ if fnmatch.fnmatch(name, arg):
|
|
+ return True
|
|
+ return False
|
|
+
|
|
+ def _no_repository_packages():
|
|
+ '''
|
|
+ Check yum version, the repository-packages subcommand is only in
|
|
+ 3.4.3 and newer.
|
|
+ '''
|
|
+ if _yum() == 'yum':
|
|
+ yum_version = _LooseVersion(
|
|
+ __salt__['cmd.run'](
|
|
+ ['yum', '--version'],
|
|
+ python_shell=False
|
|
+ ).splitlines()[0].strip()
|
|
+ )
|
|
+ return yum_version < _LooseVersion('3.4.3')
|
|
+ return False
|
|
+
|
|
+ def _parse_output(output, strict=False):
|
|
+ for pkg in _yum_pkginfo(output):
|
|
+ if strict and (pkg.repoid not in repos
|
|
+ or not _check_args(args, pkg.name)):
|
|
+ continue
|
|
repo_dict = ret.setdefault(pkg.repoid, {})
|
|
- version_list = repo_dict.setdefault(pkg.name, [])
|
|
- version_list.append(pkg.version)
|
|
+ version_list = repo_dict.setdefault(pkg.name, set())
|
|
+ version_list.add(pkg.version)
|
|
+
|
|
+ if _no_repository_packages():
|
|
+ cmd_prefix = ['yum', '--quiet', 'list']
|
|
+ for pkg_src in ('installed', 'available'):
|
|
+ # Check installed packages first
|
|
+ out = __salt__['cmd.run_all'](
|
|
+ cmd_prefix + [pkg_src],
|
|
+ output_loglevel='trace',
|
|
+ ignore_retcode=True,
|
|
+ python_shell=False
|
|
+ )
|
|
+ if out['retcode'] == 0:
|
|
+ _parse_output(out['stdout'], strict=True)
|
|
+ else:
|
|
+ for repo in repos:
|
|
+ cmd = [_yum(), '--quiet', 'repository-packages', repo,
|
|
+ 'list', '--showduplicates']
|
|
+ # Can't concatenate because args is a tuple, using list.extend()
|
|
+ cmd.extend(args)
|
|
+
|
|
+ out = __salt__['cmd.run_all'](cmd,
|
|
+ output_loglevel='trace',
|
|
+ ignore_retcode=True,
|
|
+ python_shell=False)
|
|
+ if out['retcode'] != 0 and 'Error:' in out['stdout']:
|
|
+ continue
|
|
+ _parse_output(out['stdout'])
|
|
|
|
for reponame in ret:
|
|
+ # Sort versions newest to oldest
|
|
for pkgname in ret[reponame]:
|
|
sorted_versions = sorted(
|
|
[_LooseVersion(x) for x in ret[reponame][pkgname]],
|
|
@@ -669,7 +884,8 @@ def list_upgrades(refresh=True, **kwargs):
|
|
|
|
The ``fromrepo``, ``enablerepo``, and ``disablerepo`` arguments are
|
|
supported, as used in pkg states, and the ``disableexcludes`` option is
|
|
- also supported.
|
|
+ also supported. However, in Fedora 22 and newer all of these but
|
|
+ ``fromrepo`` is ignored.
|
|
|
|
.. versionadded:: 2014.7.0
|
|
Support for the ``disableexcludes`` option
|
|
@@ -685,10 +901,19 @@ def list_upgrades(refresh=True, **kwargs):
|
|
|
|
if salt.utils.is_true(refresh):
|
|
refresh_db(**kwargs)
|
|
- updates = _repoquery_pkginfo(
|
|
- '{0} {1} --all --pkgnarrow=updates'.format(repo_arg, exclude_arg)
|
|
- )
|
|
- return dict([(x.name, x.version) for x in updates])
|
|
+
|
|
+ cmd = [_yum(), '--quiet']
|
|
+ cmd.extend(repo_arg)
|
|
+ cmd.extend(exclude_arg)
|
|
+ cmd.extend(['list', 'upgrades' if _yum() == 'dnf' else 'updates'])
|
|
+ out = __salt__['cmd.run_all'](cmd,
|
|
+ output_loglevel='trace',
|
|
+ ignore_retcode=True,
|
|
+ python_shell=False)
|
|
+ if out['retcode'] != 0 and 'Error:' in out:
|
|
+ return {}
|
|
+
|
|
+ return dict([(x.name, x.version) for x in _yum_pkginfo(out['stdout'])])
|
|
|
|
|
|
def check_db(*names, **kwargs):
|
|
@@ -705,7 +930,8 @@ def check_db(*names, **kwargs):
|
|
|
|
The ``fromrepo``, ``enablerepo`` and ``disablerepo`` arguments are
|
|
supported, as used in pkg states, and the ``disableexcludes`` option is
|
|
- also supported.
|
|
+ also supported. However, in Fedora 22 and newer all of these but
|
|
+ ``fromrepo`` is ignored.
|
|
|
|
.. versionadded:: 2014.7.0
|
|
Support for the ``disableexcludes`` option
|
|
@@ -718,41 +944,44 @@ def check_db(*names, **kwargs):
|
|
salt '*' pkg.check_db <package1> <package2> <package3> fromrepo=epel-testing
|
|
salt '*' pkg.check_db <package1> <package2> <package3> disableexcludes=main
|
|
'''
|
|
- normalize = kwargs.pop('normalize') if kwargs.get('normalize') else False
|
|
- repo_arg = _get_repo_options(**kwargs)
|
|
- exclude_arg = _get_excludes_option(**kwargs)
|
|
- repoquery_base = \
|
|
- '{0} {1} --all --quiet --whatprovides'.format(repo_arg, exclude_arg)
|
|
+ normalize = kwargs.pop('normalize', True)
|
|
+ repo_arg = _get_repo_options(repoquery=True, **kwargs)
|
|
+ exclude_arg = _get_excludes_option(repoquery=True, **kwargs)
|
|
+
|
|
+ if _yum() == 'dnf':
|
|
+ repoquery_base = repo_arg + ['--whatprovides']
|
|
+ avail_cmd = repo_arg
|
|
+ else:
|
|
+ repoquery_base = repo_arg + exclude_arg
|
|
+ repoquery_base.extend(['--all', '--quiet', '--whatprovides'])
|
|
+ avail_cmd = repo_arg + ['--pkgnarrow=all', '--all']
|
|
|
|
if 'pkg._avail' in __context__:
|
|
avail = __context__['pkg._avail']
|
|
else:
|
|
# get list of available packages
|
|
- avail = []
|
|
- lines = _repoquery(
|
|
- '{0} --pkgnarrow=all --all'.format(repo_arg),
|
|
- query_format='%{NAME}_|-%{ARCH}'
|
|
- )
|
|
+ avail = set()
|
|
+ lines = _repoquery(avail_cmd, query_format='%{NAME}_|-%{ARCH}')
|
|
for line in lines:
|
|
try:
|
|
name, arch = line.split('_|-')
|
|
except ValueError:
|
|
continue
|
|
if normalize:
|
|
- avail.append(normalize_name('.'.join((name, arch))))
|
|
+ avail.add(normalize_name('.'.join((name, arch))))
|
|
else:
|
|
- avail.append('.'.join((name, arch)))
|
|
+ avail.add('.'.join((name, arch)))
|
|
+ avail = sorted(avail)
|
|
__context__['pkg._avail'] = avail
|
|
|
|
ret = {}
|
|
- if names:
|
|
- repoquery_cmd = repoquery_base + ' {0}'.format(" ".join(names))
|
|
- provides = sorted(
|
|
- set(x.name for x in _repoquery_pkginfo(repoquery_cmd))
|
|
- )
|
|
for name in names:
|
|
ret.setdefault(name, {})['found'] = name in avail
|
|
if not ret[name]['found']:
|
|
+ repoquery_cmd = repoquery_base + [name]
|
|
+ provides = sorted(
|
|
+ set(x.name for x in _repoquery_pkginfo(repoquery_cmd))
|
|
+ )
|
|
if name in provides:
|
|
# Package was not in avail but was found by the repoquery_cmd
|
|
ret[name]['found'] = True
|
|
@@ -807,22 +1036,19 @@ def refresh_db(**kwargs):
|
|
exclude_arg = _get_excludes_option(**kwargs)
|
|
branch_arg = _get_branch_option(**kwargs)
|
|
|
|
- clean_cmd = 'yum -q clean expire-cache {repo} {exclude} {branch}'.format(
|
|
- repo=repo_arg,
|
|
- exclude=exclude_arg,
|
|
- branch=branch_arg
|
|
- )
|
|
- update_cmd = 'yum -q check-update {repo} {exclude} {branch}'.format(
|
|
- repo=repo_arg,
|
|
- exclude=exclude_arg,
|
|
- branch=branch_arg
|
|
- )
|
|
+ clean_cmd = [_yum(), '--quiet', 'clean', 'expire-cache']
|
|
+ update_cmd = [_yum(), '--quiet', 'check-update']
|
|
+ for args in (repo_arg, exclude_arg, branch_arg):
|
|
+ if args:
|
|
+ clean_cmd.extend(args)
|
|
+ update_cmd.extend(args)
|
|
|
|
- __salt__['cmd.run'](clean_cmd)
|
|
- return retcodes.get(
|
|
- __salt__['cmd.retcode'](update_cmd, ignore_retcode=True),
|
|
- False
|
|
- )
|
|
+ __salt__['cmd.run'](clean_cmd, python_shell=False)
|
|
+ result = __salt__['cmd.retcode'](update_cmd,
|
|
+ output_loglevel='trace',
|
|
+ ignore_retcode=True,
|
|
+ python_shell=False)
|
|
+ return retcodes.get(result, False)
|
|
|
|
|
|
def clean_metadata(**kwargs):
|
|
@@ -841,102 +1067,6 @@ def clean_metadata(**kwargs):
|
|
return refresh_db(**kwargs)
|
|
|
|
|
|
-def group_install(name,
|
|
- skip=(),
|
|
- include=(),
|
|
- **kwargs):
|
|
- '''
|
|
- .. versionadded:: 2014.1.0
|
|
-
|
|
- Install the passed package group(s). This is basically a wrapper around
|
|
- pkg.install, which performs package group resolution for the user. This
|
|
- function is currently considered experimental, and should be expected to
|
|
- undergo changes.
|
|
-
|
|
- name
|
|
- Package group to install. To install more than one group, either use a
|
|
- comma-separated list or pass the value as a python list.
|
|
-
|
|
- CLI Examples:
|
|
-
|
|
- .. code-block:: bash
|
|
-
|
|
- salt '*' pkg.group_install 'Group 1'
|
|
- salt '*' pkg.group_install 'Group 1,Group 2'
|
|
- salt '*' pkg.group_install '["Group 1", "Group 2"]'
|
|
-
|
|
- skip
|
|
- The name(s), in a list, of any packages that would normally be
|
|
- installed by the package group ("default" packages), which should not
|
|
- be installed. Can be passed either as a comma-separated list or a
|
|
- python list.
|
|
-
|
|
- CLI Examples:
|
|
-
|
|
- .. code-block:: bash
|
|
-
|
|
- salt '*' pkg.group_install 'My Group' skip='foo,bar'
|
|
- salt '*' pkg.group_install 'My Group' skip='["foo", "bar"]'
|
|
-
|
|
- include
|
|
- The name(s), in a list, of any packages which are included in a group,
|
|
- which would not normally be installed ("optional" packages). Note that
|
|
- this will not enforce group membership; if you include packages which
|
|
- are not members of the specified groups, they will still be installed.
|
|
- Can be passed either as a comma-separated list or a python list.
|
|
-
|
|
- CLI Examples:
|
|
-
|
|
- .. code-block:: bash
|
|
-
|
|
- salt '*' pkg.group_install 'My Group' include='foo,bar'
|
|
- salt '*' pkg.group_install 'My Group' include='["foo", "bar"]'
|
|
-
|
|
- .. note::
|
|
-
|
|
- Because this is essentially a wrapper around pkg.install, any argument
|
|
- which can be passed to pkg.install may also be included here, and it
|
|
- will be passed along wholesale.
|
|
- '''
|
|
- groups = name.split(',') if isinstance(name, string_types) else name
|
|
-
|
|
- if not groups:
|
|
- raise SaltInvocationError('no groups specified')
|
|
- elif not isinstance(groups, list):
|
|
- raise SaltInvocationError('\'groups\' must be a list')
|
|
-
|
|
- # pylint: disable=maybe-no-member
|
|
- if isinstance(skip, string_types):
|
|
- skip = skip.split(',')
|
|
- if not isinstance(skip, (list, tuple)):
|
|
- raise SaltInvocationError('\'skip\' must be a list')
|
|
-
|
|
- if isinstance(include, string_types):
|
|
- include = include.split(',')
|
|
- if not isinstance(include, (list, tuple)):
|
|
- raise SaltInvocationError('\'include\' must be a list')
|
|
- # pylint: enable=maybe-no-member
|
|
-
|
|
- targets = []
|
|
- for group in groups:
|
|
- group_detail = group_info(group)
|
|
- targets.extend(group_detail.get('mandatory packages', []))
|
|
- targets.extend(
|
|
- [pkg for pkg in group_detail.get('default packages', [])
|
|
- if pkg not in skip]
|
|
- )
|
|
- if include:
|
|
- targets.extend(include)
|
|
-
|
|
- # Don't install packages that are already installed, install() isn't smart
|
|
- # enough to make this distinction.
|
|
- pkgs = [x for x in targets if x not in list_pkgs()]
|
|
- if not pkgs:
|
|
- return {}
|
|
-
|
|
- return install(pkgs=pkgs, **kwargs)
|
|
-
|
|
-
|
|
def install(name=None,
|
|
refresh=False,
|
|
skip_verify=False,
|
|
@@ -977,7 +1107,7 @@ def install(name=None,
|
|
``yum reinstall`` will only be used if the installed version
|
|
matches the requested version.
|
|
|
|
- Works with sources when the package header of the source can be
|
|
+ Works with ``sources`` when the package header of the source can be
|
|
matched to the name and version of an installed package.
|
|
|
|
.. versionadded:: 2014.7.0
|
|
@@ -1037,19 +1167,17 @@ def install(name=None,
|
|
|
|
salt '*' pkg.install sources='[{"foo": "salt://foo.rpm"}, {"bar": "salt://bar.rpm"}]'
|
|
|
|
- normalize
|
|
- Normalize the package name by removing the architecture. Default is True.
|
|
- This is useful for poorly created packages which might include the
|
|
- architecture as an actual part of the name such as kernel modules
|
|
- which match a specific kernel version.
|
|
-
|
|
- .. versionadded:: 2014.7.0
|
|
+ normalize : True
|
|
+ Normalize the package name by removing the architecture. This is useful
|
|
+ for poorly created packages which might include the architecture as an
|
|
+ actual part of the name such as kernel modules which match a specific
|
|
+ kernel version.
|
|
|
|
- Example:
|
|
+ .. code-block:: bash
|
|
|
|
- .. code-block:: bash
|
|
+ salt -G role:nsd pkg.install gpfs.gplbin-2.6.32-279.31.1.el6.x86_64 normalize=False
|
|
|
|
- salt -G role:nsd pkg.install gpfs.gplbin-2.6.32-279.31.1.el6.x86_64 normalize=False
|
|
+ .. versionadded:: 2014.7.0
|
|
|
|
|
|
Returns a dict containing the new package names and versions::
|
|
@@ -1093,36 +1221,48 @@ def install(name=None,
|
|
else:
|
|
pkg_params_items = []
|
|
for pkg_source in pkg_params:
|
|
- rpm_info = _rpm_pkginfo(pkg_source)
|
|
- if rpm_info is not None:
|
|
- pkg_params_items.append([rpm_info.name, rpm_info.version, pkg_source])
|
|
- else:
|
|
- pkg_params_items.append([pkg_source, None, pkg_source])
|
|
+ pkg_params_items.append([pkg_source])
|
|
|
|
for pkg_item_list in pkg_params_items:
|
|
- pkgname = pkg_item_list[0]
|
|
- version_num = pkg_item_list[1]
|
|
- if version_num is None:
|
|
- if reinstall and pkg_type == 'repository' and pkgname in old:
|
|
- to_reinstall[pkgname] = pkgname
|
|
- else:
|
|
- targets.append(pkgname)
|
|
+ if pkg_type == 'repository':
|
|
+ pkgname, version_num = pkg_item_list
|
|
else:
|
|
- cver = old.get(pkgname, '')
|
|
- arch = ''
|
|
try:
|
|
- namepart, archpart = pkgname.rsplit('.', 1)
|
|
+ pkgname, pkgpath, version_num = pkg_item_list
|
|
except ValueError:
|
|
- pass
|
|
- else:
|
|
- if archpart in __ARCHES:
|
|
- arch = '.' + archpart
|
|
- pkgname = namepart
|
|
+ pkgname = None
|
|
+ pkgpath = pkg_item_list[0]
|
|
+ version_num = None
|
|
|
|
+ if version_num is None:
|
|
+ if pkg_type == 'repository':
|
|
+ if reinstall and pkgname in old:
|
|
+ to_reinstall[pkgname] = pkgname
|
|
+ else:
|
|
+ targets.append(pkgname)
|
|
+ else:
|
|
+ targets.append(pkgpath)
|
|
+ else:
|
|
+ # If we are installing a package file and not one from the repo,
|
|
+ # and version_num is not None, then we can assume that pkgname is
|
|
+ # not None, since the only way version_num is not None is if RPM
|
|
+ # metadata parsing was successful.
|
|
if pkg_type == 'repository':
|
|
- pkgstr = '"{0}-{1}{2}"'.format(pkgname, version_num, arch)
|
|
+ arch = ''
|
|
+ try:
|
|
+ namepart, archpart = pkgname.rsplit('.', 1)
|
|
+ except ValueError:
|
|
+ pass
|
|
+ else:
|
|
+ if archpart in salt.utils.pkg.rpm.ARCHES:
|
|
+ arch = '.' + archpart
|
|
+ pkgname = namepart
|
|
+
|
|
+ pkgstr = '{0}-{1}{2}'.format(pkgname, version_num, arch)
|
|
else:
|
|
- pkgstr = pkg_item_list[2]
|
|
+ pkgstr = pkgpath
|
|
+
|
|
+ cver = old.get(pkgname, '')
|
|
if reinstall and cver \
|
|
and salt.utils.compare_versions(ver1=version_num,
|
|
oper='==',
|
|
@@ -1137,47 +1277,63 @@ def install(name=None,
|
|
else:
|
|
downgrade.append(pkgstr)
|
|
|
|
+ def _add_common_args(cmd):
|
|
+ '''
|
|
+ DRY function to add args common to all yum/dnf commands
|
|
+ '''
|
|
+ for args in (repo_arg, exclude_arg, branch_arg):
|
|
+ if args:
|
|
+ cmd.extend(args)
|
|
+ if skip_verify:
|
|
+ cmd.append('--nogpgcheck')
|
|
+
|
|
if targets:
|
|
- cmd = 'yum -y {repo} {exclude} {branch} {gpgcheck} install {pkg}'.format(
|
|
- repo=repo_arg,
|
|
- exclude=exclude_arg,
|
|
- branch=branch_arg,
|
|
- gpgcheck='--nogpgcheck' if skip_verify else '',
|
|
- pkg=' '.join(targets),
|
|
+ cmd = [_yum(), '-y']
|
|
+ if _yum() == 'dnf':
|
|
+ cmd.extend(['--best', '--allowerasing'])
|
|
+ _add_common_args(cmd)
|
|
+ cmd.append('install')
|
|
+ cmd.extend(targets)
|
|
+ __salt__['cmd.run_all'](
|
|
+ cmd,
|
|
+ output_loglevel='trace',
|
|
+ python_shell=False,
|
|
+ redirect_stderr=True
|
|
)
|
|
- __salt__['cmd.run'](cmd, output_loglevel='trace')
|
|
|
|
if downgrade:
|
|
- cmd = 'yum -y {repo} {exclude} {branch} {gpgcheck} downgrade {pkg}'.format(
|
|
- repo=repo_arg,
|
|
- exclude=exclude_arg,
|
|
- branch=branch_arg,
|
|
- gpgcheck='--nogpgcheck' if skip_verify else '',
|
|
- pkg=' '.join(downgrade),
|
|
+ cmd = [_yum(), '-y']
|
|
+ _add_common_args(cmd)
|
|
+ cmd.append('downgrade')
|
|
+ cmd.extend(downgrade)
|
|
+ __salt__['cmd.run_all'](
|
|
+ cmd,
|
|
+ output_loglevel='trace',
|
|
+ python_shell=False,
|
|
+ redirect_stderr=True
|
|
)
|
|
- __salt__['cmd.run'](cmd, output_loglevel='trace')
|
|
|
|
if to_reinstall:
|
|
- cmd = 'yum -y {repo} {exclude} {branch} {gpgcheck} reinstall {pkg}'.format(
|
|
- repo=repo_arg,
|
|
- exclude=exclude_arg,
|
|
- branch=branch_arg,
|
|
- gpgcheck='--nogpgcheck' if skip_verify else '',
|
|
- pkg=' '.join(six.itervalues(to_reinstall)),
|
|
+ cmd = [_yum(), '-y']
|
|
+ _add_common_args(cmd)
|
|
+ cmd.append('reinstall')
|
|
+ cmd.extend(six.itervalues(to_reinstall))
|
|
+ __salt__['cmd.run_all'](
|
|
+ cmd,
|
|
+ output_loglevel='trace',
|
|
+ python_shell=False,
|
|
+ redirect_stderr=True
|
|
)
|
|
- __salt__['cmd.run'](cmd, output_loglevel='trace')
|
|
|
|
__context__.pop('pkg.list_pkgs', None)
|
|
new = list_pkgs()
|
|
+
|
|
ret = salt.utils.compare_dicts(old, new)
|
|
+
|
|
for pkgname in to_reinstall:
|
|
- if not pkgname not in old:
|
|
+ if pkgname not in ret or pkgname in old:
|
|
ret.update({pkgname: {'old': old.get(pkgname, ''),
|
|
'new': new.get(pkgname, '')}})
|
|
- else:
|
|
- if pkgname not in ret:
|
|
- ret.update({pkgname: {'old': old.get(pkgname, ''),
|
|
- 'new': new.get(pkgname, '')}})
|
|
if ret:
|
|
__context__.pop('pkg._avail', None)
|
|
return ret
|
|
@@ -1228,13 +1384,15 @@ def upgrade(refresh=True, skip_verify=False, **kwargs):
|
|
refresh_db(**kwargs)
|
|
|
|
old = list_pkgs()
|
|
- cmd = 'yum -q -y {repo} {exclude} {branch} {gpgcheck} upgrade'.format(
|
|
- repo=repo_arg,
|
|
- exclude=exclude_arg,
|
|
- branch=branch_arg,
|
|
- gpgcheck='--nogpgcheck' if skip_verify else '')
|
|
-
|
|
- __salt__['cmd.run'](cmd, output_loglevel='trace')
|
|
+ cmd = [_yum(), '--quiet', '-y']
|
|
+ for args in (repo_arg, exclude_arg, branch_arg):
|
|
+ if args:
|
|
+ cmd.extend(args)
|
|
+ if skip_verify:
|
|
+ cmd.append('--nogpgcheck')
|
|
+ cmd.append('upgrade')
|
|
+
|
|
+ __salt__['cmd.run'](cmd, output_loglevel='trace', python_shell=False)
|
|
__context__.pop('pkg.list_pkgs', None)
|
|
new = list_pkgs()
|
|
ret = salt.utils.compare_dicts(old, new)
|
|
@@ -1245,10 +1403,10 @@ def upgrade(refresh=True, skip_verify=False, **kwargs):
|
|
|
|
def remove(name=None, pkgs=None, **kwargs): # pylint: disable=W0613
|
|
'''
|
|
- Remove packages with ``yum -q -y remove``.
|
|
+ Remove packages
|
|
|
|
name
|
|
- The name of the package to be deleted.
|
|
+ The name of the package to be removed
|
|
|
|
|
|
Multiple Package Options:
|
|
@@ -1279,8 +1437,7 @@ def remove(name=None, pkgs=None, **kwargs): # pylint: disable=W0613
|
|
targets = [x for x in pkg_params if x in old]
|
|
if not targets:
|
|
return {}
|
|
- quoted_targets = [_cmd_quote(target) for target in targets]
|
|
- cmd = 'yum -q -y remove {0}'.format(' '.join(quoted_targets))
|
|
+ cmd = [_yum(), '-y', 'remove'] + targets
|
|
__salt__['cmd.run'](cmd, output_loglevel='trace')
|
|
__context__.pop('pkg.list_pkgs', None)
|
|
new = list_pkgs()
|
|
@@ -1296,7 +1453,7 @@ def purge(name=None, pkgs=None, **kwargs): # pylint: disable=W0613
|
|
:mod:`pkg.remove <salt.modules.yumpkg.remove>`.
|
|
|
|
name
|
|
- The name of the package to be deleted.
|
|
+ The name of the package to be purged
|
|
|
|
|
|
Multiple Package Options:
|
|
@@ -1325,7 +1482,15 @@ def hold(name=None, pkgs=None, sources=None, **kwargs): # pylint: disable=W0613
|
|
'''
|
|
.. versionadded:: 2014.7.0
|
|
|
|
- Hold packages with ``yum -q versionlock``.
|
|
+ Version-lock packages
|
|
+
|
|
+ .. note::
|
|
+ Requires the appropriate ``versionlock`` plugin package to be installed:
|
|
+
|
|
+ - On RHEL 5: ``yum-versionlock``
|
|
+ - On RHEL 6 & 7: ``yum-plugin-versionlock``
|
|
+ - On Fedora: ``python-dnf-plugins-extras-versionlock``
|
|
+
|
|
|
|
name
|
|
The name of the package to be held.
|
|
@@ -1345,13 +1510,8 @@ def hold(name=None, pkgs=None, sources=None, **kwargs): # pylint: disable=W0613
|
|
salt '*' pkg.hold <package name>
|
|
salt '*' pkg.hold pkgs='["foo", "bar"]'
|
|
'''
|
|
+ _check_versionlock()
|
|
|
|
- on_redhat_5 = __grains__.get('osmajorrelease', None) == '5'
|
|
- lock_pkg = 'yum-versionlock' if on_redhat_5 else 'yum-plugin-versionlock'
|
|
- if lock_pkg not in list_pkgs():
|
|
- raise SaltInvocationError(
|
|
- 'Packages cannot be held, {0} is not installed.'.format(lock_pkg)
|
|
- )
|
|
if not name and not pkgs and not sources:
|
|
raise SaltInvocationError(
|
|
'One of name, pkgs, or sources must be specified.'
|
|
@@ -1363,29 +1523,18 @@ def hold(name=None, pkgs=None, sources=None, **kwargs): # pylint: disable=W0613
|
|
|
|
targets = []
|
|
if pkgs:
|
|
- for pkg in salt.utils.repack_dictlist(pkgs):
|
|
- ret = check_db(pkg)
|
|
- if not ret[pkg]['found']:
|
|
- raise SaltInvocationError(
|
|
- 'Package {0} not available in repository.'.format(name)
|
|
- )
|
|
targets.extend(pkgs)
|
|
elif sources:
|
|
for source in sources:
|
|
- targets.append(next(iter(source)))
|
|
+ targets.append(next(six.iterkeys(source)))
|
|
else:
|
|
- ret = check_db(name)
|
|
- if not ret[name]['found']:
|
|
- raise SaltInvocationError(
|
|
- 'Package {0} not available in repository.'.format(name)
|
|
- )
|
|
targets.append(name)
|
|
|
|
- current_locks = get_locked_packages(full=False)
|
|
+ current_locks = list_holds(full=False)
|
|
ret = {}
|
|
for target in targets:
|
|
if isinstance(target, dict):
|
|
- target = next(iter(target))
|
|
+ target = next(six.iterkeys(target))
|
|
|
|
ret[target] = {'name': target,
|
|
'changes': {},
|
|
@@ -1398,8 +1547,8 @@ def hold(name=None, pkgs=None, sources=None, **kwargs): # pylint: disable=W0613
|
|
ret[target]['comment'] = ('Package {0} is set to be held.'
|
|
.format(target))
|
|
else:
|
|
- cmd = 'yum -q versionlock {0}'.format(target)
|
|
- out = __salt__['cmd.run_all'](cmd)
|
|
+ cmd = [_yum(), 'versionlock', target]
|
|
+ out = __salt__['cmd.run_all'](cmd, python_shell=False)
|
|
|
|
if out['retcode'] == 0:
|
|
ret[target].update(result=True)
|
|
@@ -1421,10 +1570,18 @@ def unhold(name=None, pkgs=None, sources=None, **kwargs): # pylint: disable=W06
|
|
'''
|
|
.. versionadded:: 2014.7.0
|
|
|
|
- Hold packages with ``yum -q versionlock``.
|
|
+ Remove version locks
|
|
+
|
|
+ .. note::
|
|
+ Requires the appropriate ``versionlock`` plugin package to be installed:
|
|
+
|
|
+ - On RHEL 5: ``yum-versionlock``
|
|
+ - On RHEL 6 & 7: ``yum-plugin-versionlock``
|
|
+ - On Fedora: ``python-dnf-plugins-extras-versionlock``
|
|
+
|
|
|
|
name
|
|
- The name of the package to be deleted.
|
|
+ The name of the package to be unheld
|
|
|
|
Multiple Package Options:
|
|
|
|
@@ -1441,13 +1598,8 @@ def unhold(name=None, pkgs=None, sources=None, **kwargs): # pylint: disable=W06
|
|
salt '*' pkg.unhold <package name>
|
|
salt '*' pkg.unhold pkgs='["foo", "bar"]'
|
|
'''
|
|
+ _check_versionlock()
|
|
|
|
- on_redhat_5 = __grains__.get('osmajorrelease', None) == '5'
|
|
- lock_pkg = 'yum-versionlock' if on_redhat_5 else 'yum-plugin-versionlock'
|
|
- if lock_pkg not in list_pkgs():
|
|
- raise SaltInvocationError(
|
|
- 'Packages cannot be unheld, {0} is not installed.'.format(lock_pkg)
|
|
- )
|
|
if not name and not pkgs and not sources:
|
|
raise SaltInvocationError(
|
|
'One of name, pkgs, or sources must be specified.'
|
|
@@ -1467,30 +1619,43 @@ def unhold(name=None, pkgs=None, sources=None, **kwargs): # pylint: disable=W06
|
|
else:
|
|
targets.append(name)
|
|
|
|
- current_locks = get_locked_packages(full=True)
|
|
+ # Yum's versionlock plugin doesn't support passing just the package name
|
|
+ # when removing a lock, so we need to get the full list and then use
|
|
+ # fnmatch below to find the match.
|
|
+ current_locks = list_holds(full=_yum() == 'yum')
|
|
+
|
|
ret = {}
|
|
for target in targets:
|
|
if isinstance(target, dict):
|
|
- target = next(iter(target))
|
|
+ target = next(six.iterkeys(target))
|
|
|
|
ret[target] = {'name': target,
|
|
'changes': {},
|
|
'result': False,
|
|
'comment': ''}
|
|
|
|
- search_locks = [lock for lock in current_locks
|
|
- if target in lock]
|
|
+ if _yum() == 'dnf':
|
|
+ search_locks = [x for x in current_locks if x == target]
|
|
+ else:
|
|
+ # To accommodate yum versionlock's lack of support for removing
|
|
+ # locks using just the package name, we have to use fnmatch to do
|
|
+ # glob matching on the target name, and then for each matching
|
|
+ # expression double-check that the package name (obtained via
|
|
+ # _get_hold()) matches the targeted package.
|
|
+ search_locks = [
|
|
+ x for x in current_locks
|
|
+ if fnmatch.fnmatch(x, '*{0}*'.format(target))
|
|
+ and target == _get_hold(x, full=False)
|
|
+ ]
|
|
+
|
|
if search_locks:
|
|
- if 'test' in __opts__ and __opts__['test']:
|
|
+ if __opts__['test']:
|
|
ret[target].update(result=None)
|
|
ret[target]['comment'] = ('Package {0} is set to be unheld.'
|
|
.format(target))
|
|
else:
|
|
- quoted_targets = [_cmd_quote(item) for item in search_locks]
|
|
- cmd = 'yum -q versionlock delete {0}'.format(
|
|
- ' '.join(quoted_targets)
|
|
- )
|
|
- out = __salt__['cmd.run_all'](cmd)
|
|
+ cmd = [_yum(), 'versionlock', 'delete'] + search_locks
|
|
+ out = __salt__['cmd.run_all'](cmd, python_shell=False)
|
|
|
|
if out['retcode'] == 0:
|
|
ret[target].update(result=True)
|
|
@@ -1508,45 +1673,47 @@ def unhold(name=None, pkgs=None, sources=None, **kwargs): # pylint: disable=W06
|
|
return ret
|
|
|
|
|
|
-def get_locked_packages(pattern=None, full=True):
|
|
- '''
|
|
- Get packages that are currently locked
|
|
- ``yum -q versionlock list``.
|
|
+def list_holds(pattern=__HOLD_PATTERN, full=True):
|
|
+ r'''
|
|
+ .. versionchanged:: Boron,2015.8.4,2015.5.10
|
|
+ Function renamed from ``pkg.get_locked_pkgs`` to ``pkg.list_holds``.
|
|
+
|
|
+ List information on locked packages
|
|
+
|
|
+ .. note::
|
|
+ Requires the appropriate ``versionlock`` plugin package to be installed:
|
|
+
|
|
+ - On RHEL 5: ``yum-versionlock``
|
|
+ - On RHEL 6 & 7: ``yum-plugin-versionlock``
|
|
+ - On Fedora: ``python-dnf-plugins-extras-versionlock``
|
|
+
|
|
+ pattern : \w+(?:[.-][^-]+)*
|
|
+ Regular expression used to match the package name
|
|
+
|
|
+ full : True
|
|
+ Show the full hold definition including version and epoch. Set to
|
|
+ ``False`` to return just the name of the package(s) being held.
|
|
+
|
|
|
|
CLI Example:
|
|
|
|
.. code-block:: bash
|
|
|
|
- salt '*' pkg.get_locked_packages
|
|
+ salt '*' pkg.list_holds
|
|
+ salt '*' pkg.list_holds full=False
|
|
'''
|
|
- cmd = 'yum -q versionlock list'
|
|
- ret = __salt__['cmd.run'](cmd).split('\n')
|
|
+ _check_versionlock()
|
|
|
|
- if pattern:
|
|
- if full:
|
|
- _pat = r'(\d\:{0}\-\S+)'.format(pattern)
|
|
- else:
|
|
- _pat = r'\d\:({0}\-\S+)'.format(pattern)
|
|
- else:
|
|
- if full:
|
|
- _pat = r'(\d\:\w+(?:[\.\-][^\-]+)*-\S+)'
|
|
- else:
|
|
- _pat = r'\d\:(\w+(?:[\.\-][^\-]+)*-\S+)'
|
|
- pat = re.compile(_pat)
|
|
-
|
|
- current_locks = []
|
|
- for item in ret:
|
|
- match = pat.search(item)
|
|
- if match:
|
|
- if not full:
|
|
- woarch = match.group(1).rsplit('.', 1)[0]
|
|
- worel = woarch.rsplit('-', 1)[0]
|
|
- wover = worel.rsplit('-', 1)[0]
|
|
- _match = wover
|
|
- else:
|
|
- _match = match.group(1)
|
|
- current_locks.append(_match)
|
|
- return current_locks
|
|
+ out = __salt__['cmd.run']([_yum(), 'versionlock', 'list'],
|
|
+ python_shell=False)
|
|
+ ret = []
|
|
+ for line in out.splitlines():
|
|
+ match = _get_hold(line, pattern=pattern, full=full)
|
|
+ if match is not None:
|
|
+ ret.append(match)
|
|
+ return ret
|
|
+
|
|
+get_locked_packages = list_holds
|
|
|
|
|
|
def verify(*names, **kwargs):
|
|
@@ -1582,42 +1749,62 @@ def group_list():
|
|
|
|
salt '*' pkg.group_list
|
|
'''
|
|
- ret = {'installed': [], 'available': [], 'available languages': {}}
|
|
- cmd = 'yum grouplist'
|
|
- out = __salt__['cmd.run_stdout'](cmd, output_loglevel='trace').splitlines()
|
|
+ ret = {'installed': [],
|
|
+ 'available': [],
|
|
+ 'installed environments': [],
|
|
+ 'available environments': [],
|
|
+ 'available languages': {}}
|
|
+
|
|
+ section_map = {
|
|
+ 'installed groups:': 'installed',
|
|
+ 'available groups:': 'available',
|
|
+ 'installed environment groups:': 'installed environments',
|
|
+ 'available environment groups:': 'available environments',
|
|
+ 'available language groups:': 'available languages',
|
|
+ }
|
|
+
|
|
+ out = __salt__['cmd.run_stdout'](
|
|
+ [_yum(), 'grouplist', 'hidden'],
|
|
+ output_loglevel='trace',
|
|
+ python_shell=False
|
|
+ )
|
|
key = None
|
|
- for idx in range(len(out)):
|
|
- if out[idx] == 'Installed Groups:':
|
|
- key = 'installed'
|
|
- continue
|
|
- elif out[idx] == 'Available Groups:':
|
|
- key = 'available'
|
|
- continue
|
|
- elif out[idx] == 'Available Language Groups:':
|
|
- key = 'available languages'
|
|
- continue
|
|
- elif out[idx] == 'Done':
|
|
+ for line in out.splitlines():
|
|
+ line_lc = line.lower()
|
|
+ if line_lc == 'done':
|
|
+ break
|
|
+
|
|
+ section_lookup = section_map.get(line_lc)
|
|
+ if section_lookup is not None and section_lookup != key:
|
|
+ key = section_lookup
|
|
continue
|
|
|
|
+ # Ignore any administrative comments (plugin info, repo info, etc.)
|
|
if key is None:
|
|
continue
|
|
|
|
+ line = line.strip()
|
|
if key != 'available languages':
|
|
- ret[key].append(out[idx].strip())
|
|
+ ret[key].append(line)
|
|
else:
|
|
- line = out[idx].strip()
|
|
- try:
|
|
- name, lang = re.match(r'(.+) \[(.+)\]', line).groups()
|
|
- except AttributeError:
|
|
- pass
|
|
- else:
|
|
+ match = re.match(r'(.+) \[(.+)\]', line)
|
|
+ if match:
|
|
+ name, lang = match.groups()
|
|
ret[key][line] = {'name': name, 'language': lang}
|
|
return ret
|
|
|
|
|
|
-def group_info(name):
|
|
+def group_info(name, expand=False):
|
|
'''
|
|
.. versionadded:: 2014.1.0
|
|
+ .. versionchanged:: Boron,2015.8.4,2015.5.10
|
|
+ The return data has changed. A new key ``type`` has been added to
|
|
+ distinguish environment groups from package groups. Also, keys for the
|
|
+ group name and group ID have been added. The ``mandatory packages``,
|
|
+ ``optional packages``, and ``default packages`` keys have been renamed
|
|
+ to ``mandatory``, ``optional``, and ``default`` for accuracy, as
|
|
+ environment groups include other groups, and not packages. Finally,
|
|
+ this function now properly identifies conditional packages.
|
|
|
|
Lists packages belonging to a certain group
|
|
|
|
@@ -1627,44 +1814,67 @@ def group_info(name):
|
|
|
|
salt '*' pkg.group_info 'Perl Support'
|
|
'''
|
|
- # Not using _repoquery_pkginfo() here because group queries are handled
|
|
- # differently, and ignore the '--queryformat' param
|
|
- ret = {
|
|
- 'mandatory packages': [],
|
|
- 'optional packages': [],
|
|
- 'default packages': [],
|
|
- 'description': ''
|
|
- }
|
|
- cmd_template = 'repoquery --plugins --group --grouppkgs={0} --list {1}'
|
|
-
|
|
- cmd = cmd_template.format('all', _cmd_quote(name))
|
|
- out = __salt__['cmd.run_stdout'](cmd, output_loglevel='trace')
|
|
- all_pkgs = set(out.splitlines())
|
|
-
|
|
- if not all_pkgs:
|
|
- raise CommandExecutionError('Group {0!r} not found'.format(name))
|
|
+ pkgtypes = ('mandatory', 'optional', 'default', 'conditional')
|
|
+ ret = {}
|
|
+ for pkgtype in pkgtypes:
|
|
+ ret[pkgtype] = set()
|
|
|
|
- for pkgtype in ('mandatory', 'optional', 'default'):
|
|
- cmd = cmd_template.format(pkgtype, _cmd_quote(name))
|
|
- packages = set(
|
|
- __salt__['cmd.run_stdout'](
|
|
- cmd, output_loglevel='trace'
|
|
- ).splitlines()
|
|
- )
|
|
- ret['{0} packages'.format(pkgtype)].extend(sorted(packages))
|
|
- all_pkgs -= packages
|
|
+ cmd = [_yum(), '--quiet', 'groupinfo', name]
|
|
+ out = __salt__['cmd.run_stdout'](
|
|
+ cmd,
|
|
+ output_loglevel='trace',
|
|
+ python_shell=False
|
|
+ )
|
|
|
|
- # 'contitional' is not a valid --grouppkgs value. Any pkgs that show up
|
|
- # in '--grouppkgs=all' that aren't in mandatory, optional, or default are
|
|
- # considered to be conditional packages.
|
|
- ret['conditional packages'] = sorted(all_pkgs)
|
|
+ g_info = {}
|
|
+ for line in out.splitlines():
|
|
+ try:
|
|
+ key, value = [x.strip() for x in line.split(':')]
|
|
+ g_info[key.lower()] = value
|
|
+ except ValueError:
|
|
+ continue
|
|
|
|
- cmd = 'repoquery --plugins --group --info {0}'.format(_cmd_quote(name))
|
|
- out = __salt__['cmd.run_stdout'](
|
|
- cmd, output_loglevel='trace'
|
|
+ if 'environment group' in g_info:
|
|
+ ret['type'] = 'environment group'
|
|
+ elif 'group' in g_info:
|
|
+ ret['type'] = 'package group'
|
|
+
|
|
+ ret['group'] = g_info.get('environment group') or g_info.get('group')
|
|
+ ret['id'] = g_info.get('environment-id') or g_info.get('group-id')
|
|
+ if not ret['group'] and not ret['id']:
|
|
+ raise CommandExecutionError('Group \'{0}\' not found'.format(name))
|
|
+
|
|
+ ret['description'] = g_info.get('description', '')
|
|
+
|
|
+ pkgtypes_capturegroup = '(' + '|'.join(pkgtypes) + ')'
|
|
+ for pkgtype in pkgtypes:
|
|
+ target_found = False
|
|
+ for line in out.splitlines():
|
|
+ line = line.strip().lstrip(string.punctuation)
|
|
+ match = re.match(
|
|
+ pkgtypes_capturegroup + r' (?:groups|packages):\s*$',
|
|
+ line.lower()
|
|
)
|
|
- if out:
|
|
- ret['description'] = '\n'.join(out.splitlines()[1:]).strip()
|
|
+ if match:
|
|
+ if target_found:
|
|
+ # We've reached a new section, break from loop
|
|
+ break
|
|
+ else:
|
|
+ if match.group(1) == pkgtype:
|
|
+ # We've reached the targeted section
|
|
+ target_found = True
|
|
+ continue
|
|
+ if target_found:
|
|
+ if expand and ret['type'] == 'environment group':
|
|
+ expanded = group_info(line, expand=True)
|
|
+ # Don't shadow the pkgtype variable from the outer loop
|
|
+ for p_type in pkgtypes:
|
|
+ ret[p_type].update(set(expanded[p_type]))
|
|
+ else:
|
|
+ ret[pkgtype].add(line)
|
|
+
|
|
+ for pkgtype in pkgtypes:
|
|
+ ret[pkgtype] = sorted(ret[pkgtype])
|
|
|
|
return ret
|
|
|
|
@@ -1672,6 +1882,10 @@ def group_info(name):
|
|
def group_diff(name):
|
|
'''
|
|
.. versionadded:: 2014.1.0
|
|
+ .. versionchanged:: Boron,2015.8.4,2015.5.10
|
|
+ Environment groups are now supported. The key names have been renamed,
|
|
+ similar to the changes made in :py:func:`pkg.group_info
|
|
+ <salt.modules.yumpkg.group_info>`.
|
|
|
|
Lists packages belonging to a certain group, and which are installed
|
|
|
|
@@ -1681,24 +1895,117 @@ def group_diff(name):
|
|
|
|
salt '*' pkg.group_diff 'Perl Support'
|
|
'''
|
|
- ret = {
|
|
- 'mandatory packages': {'installed': [], 'not installed': []},
|
|
- 'optional packages': {'installed': [], 'not installed': []},
|
|
- 'default packages': {'installed': [], 'not installed': []},
|
|
- 'conditional packages': {'installed': [], 'not installed': []},
|
|
- }
|
|
+ pkgtypes = ('mandatory', 'optional', 'default', 'conditional')
|
|
+ ret = {}
|
|
+ for pkgtype in pkgtypes:
|
|
+ ret[pkgtype] = {'installed': [], 'not installed': []}
|
|
+
|
|
pkgs = list_pkgs()
|
|
- group_pkgs = group_info(name)
|
|
- for pkgtype in ('mandatory', 'optional', 'default', 'conditional'):
|
|
- for member in group_pkgs.get('{0} packages'.format(pkgtype), []):
|
|
- key = '{0} packages'.format(pkgtype)
|
|
+ group_pkgs = group_info(name, expand=True)
|
|
+ for pkgtype in pkgtypes:
|
|
+ for member in group_pkgs.get(pkgtype, []):
|
|
if member in pkgs:
|
|
- ret[key]['installed'].append(member)
|
|
+ ret[pkgtype]['installed'].append(member)
|
|
else:
|
|
- ret[key]['not installed'].append(member)
|
|
+ ret[pkgtype]['not installed'].append(member)
|
|
return ret
|
|
|
|
|
|
+def group_install(name,
|
|
+ skip=(),
|
|
+ include=(),
|
|
+ **kwargs):
|
|
+ '''
|
|
+ .. versionadded:: 2014.1.0
|
|
+
|
|
+ Install the passed package group(s). This is basically a wrapper around
|
|
+ :py:func:`pkg.install <salt.modules.yumpkg.install>`, which performs
|
|
+ package group resolution for the user. This function is currently
|
|
+ considered experimental, and should be expected to undergo changes.
|
|
+
|
|
+ name
|
|
+ Package group to install. To install more than one group, either use a
|
|
+ comma-separated list or pass the value as a python list.
|
|
+
|
|
+ CLI Examples:
|
|
+
|
|
+ .. code-block:: bash
|
|
+
|
|
+ salt '*' pkg.group_install 'Group 1'
|
|
+ salt '*' pkg.group_install 'Group 1,Group 2'
|
|
+ salt '*' pkg.group_install '["Group 1", "Group 2"]'
|
|
+
|
|
+ skip
|
|
+ Packages that would normally be installed by the package group
|
|
+ ("default" packages), which should not be installed. Can be passed
|
|
+ either as a comma-separated list or a python list.
|
|
+
|
|
+ CLI Examples:
|
|
+
|
|
+ .. code-block:: bash
|
|
+
|
|
+ salt '*' pkg.group_install 'My Group' skip='foo,bar'
|
|
+ salt '*' pkg.group_install 'My Group' skip='["foo", "bar"]'
|
|
+
|
|
+ include
|
|
+ Packages which are included in a group, which would not normally be
|
|
+ installed by a ``yum groupinstall`` ("optional" packages). Note that
|
|
+ this will not enforce group membership; if you include packages which
|
|
+ are not members of the specified groups, they will still be installed.
|
|
+ Can be passed either as a comma-separated list or a python list.
|
|
+
|
|
+ CLI Examples:
|
|
+
|
|
+ .. code-block:: bash
|
|
+
|
|
+ salt '*' pkg.group_install 'My Group' include='foo,bar'
|
|
+ salt '*' pkg.group_install 'My Group' include='["foo", "bar"]'
|
|
+
|
|
+ .. note::
|
|
+
|
|
+ Because this is essentially a wrapper around pkg.install, any argument
|
|
+ which can be passed to pkg.install may also be included here, and it
|
|
+ will be passed along wholesale.
|
|
+ '''
|
|
+ groups = name.split(',') if isinstance(name, six.string_types) else name
|
|
+
|
|
+ if not groups:
|
|
+ raise SaltInvocationError('no groups specified')
|
|
+ elif not isinstance(groups, list):
|
|
+ raise SaltInvocationError('\'groups\' must be a list')
|
|
+
|
|
+ # pylint: disable=maybe-no-member
|
|
+ if isinstance(skip, six.string_types):
|
|
+ skip = skip.split(',')
|
|
+ if not isinstance(skip, (list, tuple)):
|
|
+ raise SaltInvocationError('\'skip\' must be a list')
|
|
+
|
|
+ if isinstance(include, six.string_types):
|
|
+ include = include.split(',')
|
|
+ if not isinstance(include, (list, tuple)):
|
|
+ raise SaltInvocationError('\'include\' must be a list')
|
|
+ # pylint: enable=maybe-no-member
|
|
+
|
|
+ targets = []
|
|
+ for group in groups:
|
|
+ group_detail = group_info(group)
|
|
+ targets.extend(group_detail.get('mandatory packages', []))
|
|
+ targets.extend(
|
|
+ [pkg for pkg in group_detail.get('default packages', [])
|
|
+ if pkg not in skip]
|
|
+ )
|
|
+ if include:
|
|
+ targets.extend(include)
|
|
+
|
|
+ # Don't install packages that are already installed, install() isn't smart
|
|
+ # enough to make this distinction.
|
|
+ pkgs = [x for x in targets if x not in list_pkgs()]
|
|
+ if not pkgs:
|
|
+ return {}
|
|
+
|
|
+ return install(pkgs=pkgs, **kwargs)
|
|
+
|
|
+
|
|
def list_repos(basedir=None):
|
|
'''
|
|
Lists all repos in <basedir> (default: all dirs in `reposdir` yum option).
|
|
@@ -1714,7 +2021,7 @@ def list_repos(basedir=None):
|
|
|
|
basedirs = _normalize_basedir(basedir)
|
|
repos = {}
|
|
- log.debug('Searching for repos in {0}'.format(basedirs))
|
|
+ log.debug('Searching for repos in %s', basedirs)
|
|
for bdir in basedirs:
|
|
if not os.path.exists(bdir):
|
|
continue
|
|
@@ -1732,7 +2039,8 @@ def list_repos(basedir=None):
|
|
|
|
def get_repo(repo, basedir=None, **kwargs): # pylint: disable=W0613
|
|
'''
|
|
- Display a repo from <basedir> (default basedir: all dirs in `reposdir` yum option).
|
|
+ Display a repo from <basedir> (default basedir: all dirs in ``reposdir``
|
|
+ yum option).
|
|
|
|
CLI Examples:
|
|
|
|
@@ -1746,7 +2054,7 @@ def get_repo(repo, basedir=None, **kwargs): # pylint: disable=W0613
|
|
|
|
# Find out what file the repo lives in
|
|
repofile = ''
|
|
- for arepo in repos.keys():
|
|
+ for arepo in six.iterkeys(repos):
|
|
if arepo == repo:
|
|
repofile = repos[arepo]['file']
|
|
|
|
@@ -1788,7 +2096,7 @@ def del_repo(repo, basedir=None, **kwargs): # pylint: disable=W0613
|
|
|
|
# See if the repo is the only one in the file
|
|
onlyrepo = True
|
|
- for arepo in repos.keys():
|
|
+ for arepo in six.iterkeys(repos):
|
|
if arepo == repo:
|
|
continue
|
|
if repos[arepo]['file'] == repofile:
|
|
@@ -1803,7 +2111,7 @@ def del_repo(repo, basedir=None, **kwargs): # pylint: disable=W0613
|
|
# There must be other repos in this file, write the file with them
|
|
header, filerepos = _parse_repo_file(repofile)
|
|
content = header
|
|
- for stanza in filerepos.keys():
|
|
+ for stanza in six.iterkeys(filerepos):
|
|
if stanza == repo:
|
|
continue
|
|
comments = ''
|
|
@@ -1928,32 +2236,32 @@ def mod_repo(repo, basedir=None, **kwargs):
|
|
# Error out if they tried to delete baseurl or mirrorlist improperly
|
|
if 'baseurl' in todelete:
|
|
if 'mirrorlist' not in repo_opts and 'mirrorlist' \
|
|
- not in filerepos[repo].keys():
|
|
+ not in filerepos[repo]:
|
|
raise SaltInvocationError(
|
|
'Cannot delete baseurl without specifying mirrorlist'
|
|
)
|
|
if 'mirrorlist' in todelete:
|
|
if 'baseurl' not in repo_opts and 'baseurl' \
|
|
- not in filerepos[repo].keys():
|
|
+ not in filerepos[repo]:
|
|
raise SaltInvocationError(
|
|
'Cannot delete mirrorlist without specifying baseurl'
|
|
)
|
|
|
|
# Delete anything in the todelete list
|
|
for key in todelete:
|
|
- if key in filerepos[repo].keys():
|
|
+ if key in six.iterkeys(filerepos[repo].copy()):
|
|
del filerepos[repo][key]
|
|
|
|
# Old file or new, write out the repos(s)
|
|
filerepos[repo].update(repo_opts)
|
|
content = header
|
|
- for stanza in filerepos.keys():
|
|
+ for stanza in six.iterkeys(filerepos):
|
|
comments = ''
|
|
- if 'comments' in filerepos[stanza].keys():
|
|
+ if 'comments' in six.iterkeys(filerepos[stanza]):
|
|
comments = '\n'.join(filerepos[stanza]['comments'])
|
|
del filerepos[stanza]['comments']
|
|
content += '\n[{0}]'.format(stanza)
|
|
- for line in filerepos[stanza].keys():
|
|
+ for line in six.iterkeys(filerepos[stanza]):
|
|
content += '\n{0}={1}'.format(line, filerepos[stanza][line])
|
|
content += '\n{0}\n'.format(comments)
|
|
|
|
@@ -1997,8 +2305,8 @@ def _parse_repo_file(filename):
|
|
repos[repo][comps[0].strip()] = '='.join(comps[1:])
|
|
except KeyError:
|
|
log.error(
|
|
- 'Failed to parse line in {0}, offending line was '
|
|
- '\'{1}\''.format(filename, line.rstrip())
|
|
+ 'Failed to parse line in %s, offending line was '
|
|
+ '\'%s\'', filename, line.rstrip()
|
|
)
|
|
|
|
return (header, repos)
|
|
@@ -2077,18 +2385,15 @@ def owner(*paths):
|
|
return ''
|
|
ret = {}
|
|
for path in paths:
|
|
- cmd = 'rpm -qf --queryformat {0} {1!r}'.format(
|
|
- _cmd_quote('%{{NAME}}'),
|
|
- path
|
|
- )
|
|
ret[path] = __salt__['cmd.run_stdout'](
|
|
- cmd.format(path),
|
|
- output_loglevel='trace'
|
|
- )
|
|
+ ['rpm', '-qf', '--queryformat', '%{NAME}', path],
|
|
+ output_loglevel='trace',
|
|
+ python_shell=False
|
|
+ )
|
|
if 'not owned' in ret[path].lower():
|
|
ret[path] = ''
|
|
if len(ret) == 1:
|
|
- return next(ret.itervalues())
|
|
+ return next(six.itervalues(ret))
|
|
return ret
|
|
|
|
|
|
@@ -2123,10 +2428,12 @@ def modified(*packages, **flags):
|
|
Include only files where group has been changed.
|
|
|
|
time
|
|
- Include only files where modification time of the file has been changed.
|
|
+ Include only files where modification time of the file has been
|
|
+ changed.
|
|
|
|
capabilities
|
|
- Include only files where capabilities differ or not. Note: supported only on newer RPM versions.
|
|
+ Include only files where capabilities differ or not. Note: supported
|
|
+ only on newer RPM versions.
|
|
|
|
CLI Examples:
|
|
|
|
@@ -2174,11 +2481,11 @@ def download(*packages):
|
|
for x in cached_pkgs
|
|
if x.startswith('{0}-'.format(pkg))])
|
|
for purge_target in set(to_purge):
|
|
- log.debug('Removing cached package {0}'.format(purge_target))
|
|
+ log.debug('Removing cached package %s', purge_target)
|
|
try:
|
|
os.unlink(purge_target)
|
|
except OSError as exc:
|
|
- log.error('Unable to remove {0}: {1}'.format(purge_target, exc))
|
|
+ log.error('Unable to remove %s: %s', purge_target, exc)
|
|
|
|
__salt__['cmd.run'](
|
|
'yumdownloader -q {0} --destdir={1}'.format(
|
|
@@ -2244,6 +2551,7 @@ def diff(*paths):
|
|
local_pkgs = __salt__['pkg.download'](*pkg_to_paths.keys())
|
|
for pkg, files in pkg_to_paths.items():
|
|
for path in files:
|
|
- ret[path] = __salt__['lowpkg.diff'](local_pkgs[pkg]['path'], path) or 'Unchanged'
|
|
+ ret[path] = __salt__['lowpkg.diff'](
|
|
+ local_pkgs[pkg]['path'], path) or 'Unchanged'
|
|
|
|
return ret
|
|
diff --git a/salt/states/pkg.py b/salt/states/pkg.py
|
|
index 15c669d..3811404 100644
|
|
--- a/salt/states/pkg.py
|
|
+++ b/salt/states/pkg.py
|
|
@@ -327,27 +327,34 @@ def _find_install_targets(name=None,
|
|
if not (name in cur_pkgs and version in (None, cur_pkgs[name]))
|
|
])
|
|
if not_installed:
|
|
- problems = _preflight_check(not_installed, **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 six.iteritems(problems['suggest']):
|
|
+ try:
|
|
+ problems = _preflight_check(not_installed, **kwargs)
|
|
+ except CommandExecutionError:
|
|
+ pass
|
|
+ else:
|
|
+ comments = []
|
|
+ if problems.get('no_suggest'):
|
|
comments.append(
|
|
- 'Package \'{0}\' not found (possible matches: {1})'
|
|
- .format(pkgname, ', '.join(suggestions))
|
|
+ '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 comments:
|
|
- if len(comments) > 1:
|
|
- comments.append('')
|
|
- return {'name': name,
|
|
- 'changes': {},
|
|
- 'result': False,
|
|
- 'comment': '. '.join(comments).rstrip()}
|
|
+ if problems.get('suggest'):
|
|
+ for pkgname, suggestions in \
|
|
+ six.iteritems(problems['suggest']):
|
|
+ comments.append(
|
|
+ 'Package \'{0}\' 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 = {}
|
|
diff --git a/salt/utils/itertools.py b/salt/utils/itertools.py
|
|
new file mode 100644
|
|
index 0000000..f824adb
|
|
--- /dev/null
|
|
+++ b/salt/utils/itertools.py
|
|
@@ -0,0 +1,34 @@
|
|
+# -*- coding: utf-8 -*-
|
|
+'''
|
|
+Helpful generators and other tools
|
|
+'''
|
|
+
|
|
+# Import python libs
|
|
+from __future__ import absolute_import
|
|
+import re
|
|
+
|
|
+
|
|
+def split(orig, sep=None):
|
|
+ '''
|
|
+ Generator function for iterating through large strings, particularly useful
|
|
+ as a replacement for str.splitlines().
|
|
+
|
|
+ See http://stackoverflow.com/a/3865367
|
|
+ '''
|
|
+ exp = re.compile(r'\s+' if sep is None else re.escape(sep))
|
|
+ pos = 0
|
|
+ length = len(orig)
|
|
+ while True:
|
|
+ match = exp.search(orig, pos)
|
|
+ if not match:
|
|
+ if pos < length or sep is not None:
|
|
+ val = orig[pos:]
|
|
+ if val:
|
|
+ # Only yield a value if the slice was not an empty string,
|
|
+ # because if it is then we've reached the end. This keeps
|
|
+ # us from yielding an extra blank value at the end.
|
|
+ yield val
|
|
+ break
|
|
+ if pos < match.start() or sep is not None:
|
|
+ yield orig[pos:match.start()]
|
|
+ pos = match.end()
|
|
diff --git a/salt/utils/pkg/__init__.py b/salt/utils/pkg/__init__.py
|
|
new file mode 100644
|
|
index 0000000..e316343
|
|
--- /dev/null
|
|
+++ b/salt/utils/pkg/__init__.py
|
|
@@ -0,0 +1,4 @@
|
|
+# -*- coding: utf-8 -*-
|
|
+'''
|
|
+Helper modules used by lowpkg modules
|
|
+'''
|
|
diff --git a/salt/utils/pkg/rpm.py b/salt/utils/pkg/rpm.py
|
|
new file mode 100644
|
|
index 0000000..9c01d5b
|
|
--- /dev/null
|
|
+++ b/salt/utils/pkg/rpm.py
|
|
@@ -0,0 +1,102 @@
|
|
+# -*- coding: utf-8 -*-
|
|
+'''
|
|
+Common functions for working with RPM packages
|
|
+'''
|
|
+
|
|
+# Import python libs
|
|
+from __future__ import absolute_import
|
|
+import collections
|
|
+import logging
|
|
+
|
|
+# Import salt libs
|
|
+from salt._compat import subprocess
|
|
+
|
|
+log = logging.getLogger(__name__)
|
|
+
|
|
+# These arches compiled from the rpmUtils.arch python module source
|
|
+ARCHES_64 = ('x86_64', 'athlon', 'amd64', 'ia32e', 'ia64', 'geode')
|
|
+ARCHES_32 = ('i386', 'i486', 'i586', 'i686')
|
|
+ARCHES_PPC = ('ppc', 'ppc64', 'ppc64iseries', 'ppc64pseries')
|
|
+ARCHES_S390 = ('s390', 's390x')
|
|
+ARCHES_SPARC = (
|
|
+ 'sparc', 'sparcv8', 'sparcv9', 'sparcv9v', 'sparc64', 'sparc64v'
|
|
+)
|
|
+ARCHES_ALPHA = (
|
|
+ 'alpha', 'alphaev4', 'alphaev45', 'alphaev5', 'alphaev56',
|
|
+ 'alphapca56', 'alphaev6', 'alphaev67', 'alphaev68', 'alphaev7'
|
|
+)
|
|
+ARCHES_ARM = ('armv5tel', 'armv5tejl', 'armv6l', 'armv7l')
|
|
+ARCHES_SH = ('sh3', 'sh4', 'sh4a')
|
|
+
|
|
+ARCHES = ARCHES_64 + ARCHES_32 + ARCHES_PPC + ARCHES_S390 + \
|
|
+ ARCHES_ALPHA + ARCHES_ARM + ARCHES_SH
|
|
+
|
|
+# EPOCHNUM can't be used until RHEL5 is EOL as it is not present
|
|
+QUERYFORMAT = '%{NAME}_|-%{EPOCH}_|-%{VERSION}_|-%{RELEASE}_|-%{ARCH}_|-%{REPOID}'
|
|
+
|
|
+
|
|
+def get_osarch():
|
|
+ '''
|
|
+ Get the os architecture using rpm --eval
|
|
+ '''
|
|
+ ret = subprocess.Popen(
|
|
+ 'rpm --eval "%{_host_cpu}"',
|
|
+ shell=True,
|
|
+ close_fds=True,
|
|
+ stdout=subprocess.PIPE,
|
|
+ stderr=subprocess.PIPE).communicate()[0]
|
|
+ return ret or 'unknown'
|
|
+
|
|
+
|
|
+def check_32(arch, osarch=None):
|
|
+ '''
|
|
+ Returns True if both the OS arch and the passed arch are 32-bit
|
|
+ '''
|
|
+ if osarch is None:
|
|
+ osarch = get_osarch()
|
|
+ return all(x in ARCHES_32 for x in (osarch, arch))
|
|
+
|
|
+
|
|
+def pkginfo(name, version, arch, repoid):
|
|
+ '''
|
|
+ Build and return a pkginfo namedtuple
|
|
+ '''
|
|
+ pkginfo_tuple = collections.namedtuple(
|
|
+ 'PkgInfo',
|
|
+ ('name', 'version', 'arch', 'repoid')
|
|
+ )
|
|
+ return pkginfo_tuple(name, version, arch, repoid)
|
|
+
|
|
+
|
|
+def resolve_name(name, arch, osarch=None):
|
|
+ '''
|
|
+ Resolve the package name and arch into a unique name referred to by salt.
|
|
+ For example, on a 64-bit OS, a 32-bit package will be pkgname.i386.
|
|
+ '''
|
|
+ if osarch is None:
|
|
+ osarch = get_osarch()
|
|
+
|
|
+ if not check_32(arch, osarch) and arch not in (osarch, 'noarch'):
|
|
+ name += '.{0}'.format(arch)
|
|
+ return name
|
|
+
|
|
+
|
|
+def parse_pkginfo(line, osarch=None):
|
|
+ '''
|
|
+ A small helper to parse an rpm/repoquery command's output. Returns a
|
|
+ pkginfo namedtuple.
|
|
+ '''
|
|
+ try:
|
|
+ name, epoch, version, release, arch, repoid = line.split('_|-')
|
|
+ # Handle unpack errors (should never happen with the queryformat we are
|
|
+ # using, but can't hurt to be careful).
|
|
+ except ValueError:
|
|
+ return None
|
|
+
|
|
+ name = resolve_name(name, arch, osarch)
|
|
+ if release:
|
|
+ version += '-{0}'.format(release)
|
|
+ if epoch not in ('(none)', '0'):
|
|
+ version = ':'.join((epoch, version))
|
|
+
|
|
+ return pkginfo(name, version, arch, repoid)
|
|
diff --git a/salt/modules/debian_ip.py b/salt/modules/debian_ip.py
|
|
index d0d6db1..275cb97 100644
|
|
--- a/salt/modules/debian_ip.py
|
|
+++ b/salt/modules/debian_ip.py
|
|
@@ -819,9 +819,12 @@ def _parse_settings_bond_0(opts, iface, bond_def):
|
|
if 'arp_ip_target' in opts:
|
|
if isinstance(opts['arp_ip_target'], list):
|
|
if 1 <= len(opts['arp_ip_target']) <= 16:
|
|
- bond.update({'arp_ip_target': []})
|
|
+ bond.update({'arp_ip_target': ''})
|
|
for ip in opts['arp_ip_target']: # pylint: disable=C0103
|
|
- bond['arp_ip_target'].append(ip)
|
|
+ if len(bond['arp_ip_target']) > 0:
|
|
+ bond['arp_ip_target'] = bond['arp_ip_target'] + ',' + ip
|
|
+ else:
|
|
+ bond['arp_ip_target'] = ip
|
|
else:
|
|
_raise_error_iface(iface, 'arp_ip_target', valid)
|
|
else:
|
|
@@ -892,9 +895,12 @@ def _parse_settings_bond_2(opts, iface, bond_def):
|
|
if 'arp_ip_target' in opts:
|
|
if isinstance(opts['arp_ip_target'], list):
|
|
if 1 <= len(opts['arp_ip_target']) <= 16:
|
|
- bond.update({'arp_ip_target': []})
|
|
+ bond.update({'arp_ip_target': ''})
|
|
for ip in opts['arp_ip_target']: # pylint: disable=C0103
|
|
- bond['arp_ip_target'].append(ip)
|
|
+ if len(bond['arp_ip_target']) > 0:
|
|
+ bond['arp_ip_target'] = bond['arp_ip_target'] + ',' + ip
|
|
+ else:
|
|
+ bond['arp_ip_target'] = ip
|
|
else:
|
|
_raise_error_iface(iface, 'arp_ip_target', valid)
|
|
else:
|
|
diff --git a/salt/modules/rh_ip.py b/salt/modules/rh_ip.py
|
|
index 2762125..cd362e4 100644
|
|
--- a/salt/modules/rh_ip.py
|
|
+++ b/salt/modules/rh_ip.py
|
|
@@ -276,9 +276,12 @@ def _parse_settings_bond_0(opts, iface, bond_def):
|
|
if 'arp_ip_target' in opts:
|
|
if isinstance(opts['arp_ip_target'], list):
|
|
if 1 <= len(opts['arp_ip_target']) <= 16:
|
|
- bond.update({'arp_ip_target': []})
|
|
+ bond.update({'arp_ip_target': ''})
|
|
for ip in opts['arp_ip_target']: # pylint: disable=C0103
|
|
- bond['arp_ip_target'].append(ip)
|
|
+ if len(bond['arp_ip_target']) > 0:
|
|
+ bond['arp_ip_target'] = bond['arp_ip_target'] + ',' + ip
|
|
+ else:
|
|
+ bond['arp_ip_target'] = ip
|
|
else:
|
|
_raise_error_iface(iface, 'arp_ip_target', valid)
|
|
else:
|
|
@@ -349,9 +352,12 @@ def _parse_settings_bond_2(opts, iface, bond_def):
|
|
if 'arp_ip_target' in opts:
|
|
if isinstance(opts['arp_ip_target'], list):
|
|
if 1 <= len(opts['arp_ip_target']) <= 16:
|
|
- bond.update({'arp_ip_target': []})
|
|
+ bond.update({'arp_ip_target': ''})
|
|
for ip in opts['arp_ip_target']: # pylint: disable=C0103
|
|
- bond['arp_ip_target'].append(ip)
|
|
+ if len(bond['arp_ip_target']) > 0:
|
|
+ bond['arp_ip_target'] = bond['arp_ip_target'] + ',' + ip
|
|
+ else:
|
|
+ bond['arp_ip_target'] = ip
|
|
else:
|
|
_raise_error_iface(iface, 'arp_ip_target', valid)
|
|
else:
|