Merge pull request #30529 from terminalmage/2015.5-2015.8

Merge 2015.5 into 2015.8
This commit is contained in:
Mike Place 2016-01-22 10:19:39 -07:00
commit 1da1bb9afc
3 changed files with 428 additions and 251 deletions

View file

@ -4,4 +4,4 @@ salt.modules.yumpkg
.. automodule:: salt.modules.yumpkg
:members:
:exclude-members: available_version, groupinstall
:exclude-members: available_version, groupinstall, get_locked_packages

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
'''
Support for YUM
Support for YUM/DNF
.. note::
This module makes heavy use of the **repoquery** utility, from the
@ -9,6 +9,11 @@ Support for YUM
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
'''
@ -19,13 +24,13 @@ import copy
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.moves import shlex_quote as _cmd_quote
from salt.ext.six.moves import range # pylint: disable=redefined-builtin
try:
import yum
@ -107,15 +112,68 @@ 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).
pkgs = ['dnf-plugins-core', 'python-dnf-plugins-core']
def _check_plugins():
if __salt__['cmd.retcode'](['rpm', '-q'] + pkgs,
python_shell=False,
ignore_retcode=True) == 0:
__context__[contextkey] = True
return True
return False
if not _check_plugins():
__salt__['cmd.run'](['dnf', '-y', 'install'] + pkgs,
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 _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,
@ -125,15 +183,17 @@ def _repoquery(repoquery_args,
Runs a repoquery command and returns a list of namedtuples
'''
_check_repoquery()
if _yum() == 'dnf':
_query_format = query_format.replace('-%{VERSION}_', '-%{EPOCH}:%{VERSION}_')
cmd = 'dnf repoquery --quiet --queryformat {0} {1}'.format(
_cmd_quote(
query_format.replace('-%{VERSION}_', '-%{EPOCH}:%{VERSION}_')
),
repoquery_args
)
else:
_query_format = query_format
cmd = 'repoquery --plugins --queryformat {0} {1}'.format(
_cmd_quote(_query_format), repoquery_args
)
cmd = 'repoquery --plugins --queryformat {0} {1}'.format(
_cmd_quote(query_format), repoquery_args
)
call = __salt__['cmd.run_all'](cmd, output_loglevel='trace')
if call['retcode'] != 0:
comment = ''
@ -145,17 +205,13 @@ def _repoquery(repoquery_args,
comment += call['stderr']
if 'stdout' in call:
comment += call['stdout']
raise CommandExecutionError(
'{0}'.format(comment)
)
raise CommandExecutionError(comment)
else:
if _yum() == 'dnf':
# Remove the epoch when it is zero to maintain backward compatibility
remove_zero_epoch = call['stdout'].replace('-0:', '-')
out = remove_zero_epoch
return call['stdout'].replace('_|-0:', '_|-').splitlines()
else:
out = call['stdout']
return out.splitlines()
return call['stdout'].splitlines()
def _get_repo_options(**kwargs):
@ -173,30 +229,39 @@ def _get_repo_options(**kwargs):
if repo and not fromrepo:
fromrepo = repo
repo_arg = ''
use_dnf_repoquery = kwargs.get('repoquery', False) and _yum() == 'dnf'
repo_arg = []
if fromrepo:
log.info('Restricting to repo \'{0}\''.format(fromrepo))
repo_arg = ('--disablerepo=\'{0}\' --enablerepo=\'{1}\''
.format('*', 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.
repo_arg.append('--repoid=\'{0}\''.format(fromrepo))
else:
repo_arg.append(
'--disablerepo=\'*\' --enablerepo=\'{0}\''.format(fromrepo)
)
else:
repo_arg = ''
if disablerepo:
if isinstance(disablerepo, list):
for repo_item in disablerepo:
log.info('Disabling repo \'{0}\''.format(repo_item))
repo_arg += '--disablerepo=\'{0}\' '.format(repo_item)
if use_dnf_repoquery:
log.warning(
'ignoring disablerepo, not supported in dnf repoquery'
)
else:
log.info('Disabling repo \'{0}\''.format(disablerepo))
repo_arg += '--disablerepo=\'{0}\''.format(disablerepo)
repo_arg.append('--disablerepo=\'{0}\''.format(disablerepo))
if enablerepo:
if isinstance(enablerepo, list):
for repo_item in enablerepo:
log.info('Enabling repo \'{0}\''.format(repo_item))
repo_arg += '--enablerepo=\'{0}\' '.format(repo_item)
if use_dnf_repoquery:
log.warning(
'ignoring enablerepo, not supported in dnf repoquery'
)
else:
log.info('Enabling repo \'{0}\''.format(enablerepo))
repo_arg += '--enablerepo=\'{0}\''.format(enablerepo)
return repo_arg
repo_arg.append('--enablerepo=\'{0}\''.format(enablerepo))
return ' '.join(repo_arg)
def _get_excludes_option(**kwargs):
@ -204,15 +269,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}\''.format(disable_excludes))
disable_excludes_arg = \
'--disableexcludes=\'{0}\''.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 \'{0}\''.format(disable_excludes))
return '--disableexcludes=\'{0}\''.format(disable_excludes)
return ''
def _get_branch_option(**kwargs):
@ -423,24 +491,61 @@ def latest_version(*names, **kwargs):
if refresh:
refresh_db(**kwargs)
# Get updates for specified package(s)
# Sort by version number (highest to lowest) for loop below
updates = sorted(
_repoquery_pkginfo(
'{0} {1} --pkgnarrow=available {2}'
.format(repo_arg, exclude_arg, ' '.join(names))
),
key=lambda pkginfo: _LooseVersion(pkginfo.version),
reverse=True
)
for name in names:
for pkg in (x for x in updates if x.name == name):
def _query_pkgs(name, pkgs):
'''
Return the newest available match from the _repoquery_pkginfo() output
'''
matches = []
for pkg in (x for x in pkgs if x.name == name):
if pkg.arch == 'noarch' or pkg.arch == namearch_map[name] \
or salt.utils.pkg.rpm.check_32(pkg.arch):
ret[name] = pkg.version
# no need to check another match, if there was one
break
matches.append(pkg.version)
sorted_matches = sorted(
[_LooseVersion(x) for x in matches],
reverse=True
)
try:
return sorted_matches[0].vstring
except IndexError:
return None
if _yum() == 'dnf':
avail_pkgs = _repoquery_pkginfo(
'{0} --available {1}'.format(repo_arg, ' '.join(names))
)
# When using 'dnf repoquery --available', all available versions are
# returned, irrespective of whether or not they are installed. This is
# different from how yum-utils' version of repoquery works.
all_pkgs = list_pkgs(versions_as_list=True)
for name in names:
# Get newest available version of package
newest_avail = _query_pkgs(name, avail_pkgs)
if newest_avail is None:
# No matches, no need to check if pacakge is already installed
continue
# Get newest installed version of package
try:
cver = all_pkgs.get(name, [])[-1]
except IndexError:
cver = None
if cver is None \
or salt.utils.compare_versions(ver1=newest_avail,
oper='>',
ver2=cver,
cmp_func=version_cmp):
ret[name] = newest_avail
else:
avail_pkgs = _repoquery_pkginfo(
'{0} {1} --pkgnarrow=available {2}'.format(
repo_arg,
exclude_arg,
' '.join(names)
)
)
for name in names:
newest_avail = _query_pkgs(name, avail_pkgs)
if newest_avail is not None:
ret[name] = newest_avail
# Return a string if only one package name passed
if len(names) == 1:
@ -628,7 +733,11 @@ def list_repo_pkgs(*args, **kwargs):
ret = {}
for repo in repos:
repoquery_cmd = '--all --repoid="{0}" --show-duplicates'.format(repo)
if _yum() == 'dnf':
# As of 0.1.15, dnf repoquery does not support showing duplicates
repoquery_cmd = '--repoid="{0}"'.format(repo)
else:
repoquery_cmd = '--all --repoid="{0}" --show-duplicates'.format(repo)
for arg in args:
repoquery_cmd += ' "{0}"'.format(arg)
all_pkgs = _repoquery_pkginfo(repoquery_cmd)
@ -653,7 +762,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
@ -669,9 +779,14 @@ 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)
)
if _yum() == 'dnf':
upgrades_cmd = '{0} --upgrades'.format(repo_arg)
else:
upgrades_cmd = '{0} {1} --all --pkgnarrow=updates'.format(
repo_arg, exclude_arg)
updates = _repoquery_pkginfo(upgrades_cmd)
return dict([(x.name, x.version) for x in updates])
@ -715,7 +830,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
@ -729,29 +845,33 @@ def check_db(*names, **kwargs):
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)
repo_arg = _get_repo_options(repoquery=True, **kwargs)
exclude_arg = _get_excludes_option(repoquery=True, **kwargs)
if _yum() == 'dnf':
repoquery_base = '{0} --whatprovides'.format(repo_arg)
avail_cmd = repo_arg
else:
repoquery_base = '{0} {1} --all --quiet --whatprovides'.format(
repo_arg, exclude_arg)
avail_cmd = '{0} --pkgnarrow=all --all'.format(repo_arg)
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 = {}
@ -816,13 +936,16 @@ def refresh_db(**kwargs):
repo_arg = _get_repo_options(**kwargs)
exclude_arg = _get_excludes_option(**kwargs)
branch_arg = _get_branch_option(**kwargs)
yum_cmd = _yum()
clean_cmd = 'yum -q clean expire-cache {repo} {exclude} {branch}'.format(
clean_cmd = '{yum} -q clean expire-cache {repo} {exclude} {branch}'.format(
yum=yum_cmd,
repo=repo_arg,
exclude=exclude_arg,
branch=branch_arg
)
update_cmd = 'yum -q check-update {repo} {exclude} {branch}'.format(
update_cmd = '{yum} -q check-update {repo} {exclude} {branch}'.format(
yum=yum_cmd,
repo=repo_arg,
exclude=exclude_arg,
branch=branch_arg
@ -1076,9 +1199,10 @@ def install(name=None,
else:
downgrade.append(pkgstr)
yum_cmd = _yum()
if targets:
cmd = '{yum_command} -y {repo} {exclude} {branch} {gpgcheck} install {pkg}'.format(
yum_command=_yum(),
cmd = '{yum} -y {repo} {exclude} {branch} {gpgcheck} install {pkg}'.format(
yum=yum_cmd,
repo=repo_arg,
exclude=exclude_arg,
branch=branch_arg,
@ -1088,8 +1212,8 @@ def install(name=None,
__salt__['cmd.run'](cmd, output_loglevel='trace')
if downgrade:
cmd = '{yum_command} -y {repo} {exclude} {branch} {gpgcheck} downgrade {pkg}'.format(
yum_command=_yum(),
cmd = '{yum} -y {repo} {exclude} {branch} {gpgcheck} downgrade {pkg}'.format(
yum=yum_cmd,
repo=repo_arg,
exclude=exclude_arg,
branch=branch_arg,
@ -1099,8 +1223,8 @@ def install(name=None,
__salt__['cmd.run'](cmd, output_loglevel='trace')
if to_reinstall:
cmd = '{yum_command} -y {repo} {exclude} {branch} {gpgcheck} reinstall {pkg}'.format(
yum_command=_yum(),
cmd = '{yum} -y {repo} {exclude} {branch} {gpgcheck} reinstall {pkg}'.format(
yum=yum_cmd,
repo=repo_arg,
exclude=exclude_arg,
branch=branch_arg,
@ -1168,8 +1292,8 @@ def upgrade(refresh=True, skip_verify=False, **kwargs):
refresh_db(**kwargs)
old = list_pkgs()
cmd = '{yum_command} -q -y {repo} {exclude} {branch} {gpgcheck} upgrade'.format(
yum_command=_yum(),
cmd = '{yum} -q -y {repo} {exclude} {branch} {gpgcheck} upgrade'.format(
yum=_yum(),
repo=repo_arg,
exclude=exclude_arg,
branch=branch_arg,
@ -1186,10 +1310,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:
@ -1221,9 +1345,7 @@ def remove(name=None, pkgs=None, **kwargs): # pylint: disable=W0613
if not targets:
return {}
quoted_targets = [_cmd_quote(target) for target in targets]
cmd = '{yum_command} -q -y remove {0}'.format(
' '.join(quoted_targets),
yum_command=_yum())
cmd = _yum() + ' -y remove {0}'.format(' '.join(quoted_targets))
__salt__['cmd.run'](cmd, output_loglevel='trace')
__context__.pop('pkg.list_pkgs', None)
new = list_pkgs()
@ -1239,7 +1361,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:
@ -1268,7 +1390,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.
@ -1288,13 +1418,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.'
@ -1324,7 +1449,7 @@ def hold(name=None, pkgs=None, sources=None, **kwargs): # pylint: disable=W0613
)
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):
@ -1341,9 +1466,7 @@ 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_command} -q versionlock {0}'.format(
target,
yum_command=_yum())
cmd = _yum() + ' versionlock {0}'.format(target)
out = __salt__['cmd.run_all'](cmd)
if out['retcode'] == 0:
@ -1366,10 +1489,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:
@ -1386,13 +1517,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.'
@ -1412,7 +1538,7 @@ def unhold(name=None, pkgs=None, sources=None, **kwargs): # pylint: disable=W06
else:
targets.append(name)
current_locks = get_locked_packages(full=True)
current_locks = list_holds(full=False)
ret = {}
for target in targets:
if isinstance(target, dict):
@ -1432,10 +1558,9 @@ def unhold(name=None, pkgs=None, sources=None, **kwargs): # pylint: disable=W06
.format(target))
else:
quoted_targets = [_cmd_quote(item) for item in search_locks]
cmd = '{yum_command} -q versionlock delete {0}'.format(
' '.join(quoted_targets),
yum_command=_yum()
)
cmd = _yum() + ' versionlock delete {0}'.format(
' '.join(quoted_targets)
)
out = __salt__['cmd.run_all'](cmd)
if out['retcode'] == 0:
@ -1454,45 +1579,71 @@ 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=r'\w+(?:[.-][^-]+)*', 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_command} -q versionlock list'.format(yum_command=_yum())
ret = __salt__['cmd.run'](cmd).split('\n')
_check_versionlock()
if pattern:
if full:
_pat = r'(\d\:{0}\-\S+)'.format(pattern)
# yum ==> 2:vim-enhanced-7.4.629-5.el6.*
# dnf ==> vim-enhanced-2:7.4.827-1.fc22.*
yum_cmd = _yum()
if full:
if yum_cmd == 'dnf':
lock_re = r'({0}-\S+)'.format(pattern)
else:
_pat = r'\d\:({0}\-\S+)'.format(pattern)
lock_re = r'(\d+:{0}-\S+)'.format(pattern)
else:
if full:
_pat = r'(\d\:\w+(?:[\.\-][^\-]+)*-\S+)'
if yum_cmd == 'dnf':
lock_re = r'({0}-\S+)'.format(pattern)
else:
_pat = r'\d\:(\w+(?:[\.\-][^\-]+)*-\S+)'
pat = re.compile(_pat)
lock_re = r'\d+:({0}-\S+)'.format(pattern)
current_locks = []
for item in ret:
pat = re.compile(lock_re)
out = __salt__['cmd.run']([yum_cmd, 'versionlock', 'list'],
python_shell=False)
ret = []
for item in out.splitlines():
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
target = wover
else:
_match = match.group(1)
current_locks.append(_match)
return current_locks
target = match.group(1)
ret.append(target)
return ret
get_locked_packages = salt.utils.alias_function(list_holds, 'get_locked_packages')
def verify(*names, **kwargs):
@ -1528,48 +1679,62 @@ def group_list():
salt '*' pkg.group_list
'''
ret = {'installed': [], 'available': [], 'installed environments': [], 'available environments': [], 'available languages': {}}
cmd = '{yum_command} grouplist'.format(yum_command=_yum())
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].lower() == 'installed groups:':
key = 'installed'
continue
elif out[idx].lower() == 'available groups:':
key = 'available'
continue
elif out[idx].lower() == 'installed environment groups:':
key = 'installed environments'
continue
elif out[idx].lower() == 'available environment groups:':
key = 'available environments'
continue
elif out[idx].lower() == 'available language groups:':
key = 'available languages'
continue
elif out[idx].lower() == '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
@ -1579,44 +1744,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}'
pkgtypes = ('mandatory', 'optional', 'default', 'conditional')
ret = {}
for pkgtype in pkgtypes:
ret[pkgtype] = set()
cmd = cmd_template.format('all', _cmd_quote(name))
out = __salt__['cmd.run_stdout'](cmd, output_loglevel='trace')
all_pkgs = set(out.splitlines())
cmd = [_yum(), '--quiet', 'groupinfo', name]
out = __salt__['cmd.run_stdout'](
cmd,
output_loglevel='trace',
python_shell=False
)
if not 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
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))
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
ret['description'] = g_info.get('description', '')
# '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)
cmd = 'repoquery --plugins --group --info {0}'.format(_cmd_quote(name))
out = __salt__['cmd.run_stdout'](
cmd, output_loglevel='trace'
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
@ -1624,6 +1812,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
@ -1633,21 +1825,19 @@ 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
@ -1781,8 +1971,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:

View file

@ -1821,8 +1821,8 @@ def group_installed(name, skip=None, include=None, **kwargs):
'''
.. versionadded:: 2015.8.0
Ensure that an entire package group is installed. This state is only
supported for the :mod:`yum <salt.modules.yumpkg>` package manager.
Ensure that an entire package group is installed. This state is currently
only supported for the :mod:`yum <salt.modules.yumpkg>` package manager.
skip
Packages that would normally be installed by the package group
@ -1840,7 +1840,6 @@ def group_installed(name, skip=None, include=None, **kwargs):
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.
.. code-block:: yaml
@ -1849,12 +1848,15 @@ def group_installed(name, skip=None, include=None, **kwargs):
- include:
- haproxy
.. note::
.. versionchanged:: Boron
This option can no longer be passed as a comma-separated list, it
must now be passed as a list (as shown in the above example).
.. note::
Because this is essentially a wrapper around :py:func:`pkg.install
<salt.modules.yumpkg.install>`, any argument which can be passed to
pkg.install may also be included here, and it will be passed along
wholesale.
pkg.install may also be included here, and it will be passed on to the
call to :py:func:`pkg.install <salt.modules.yumpkg.install>`.
'''
ret = {'name': name,
'changes': {},
@ -1862,47 +1864,32 @@ def group_installed(name, skip=None, include=None, **kwargs):
'comment': ''}
if 'pkg.group_diff' not in __salt__:
ret['comment'] = 'pkg.group_install not implemented for this platform'
ret['comment'] = 'pkg.group_install not available for this platform'
return ret
if skip is not None:
if isinstance(skip, six.string_types):
skip = skip.split(',')
elif isinstance(skip, (float, six.integer_types)):
skip = [str(skip)]
if skip is None:
skip = []
else:
if not isinstance(skip, list):
ret['comment'] = 'skip must be formatted as a list'
return ret
for idx, item in enumerate(skip):
if isinstance(item, (float, six.integer_types)):
if not isinstance(item, six.string_types):
skip[idx] = str(item)
if not isinstance(skip[idx], six.string_types):
ret['comment'] = 'Invalid \'skip\' item {0}'.format(skip[idx])
return ret
else:
skip = []
if include is not None:
if isinstance(include, six.string_types):
include = include.split(',')
elif isinstance(include, (float, six.integer_types)):
include = [str(include)]
if include is None:
include = []
else:
if not isinstance(include, list):
ret['comment'] = 'include must be formatted as a list'
return ret
for idx, item in enumerate(include):
if isinstance(item, (float, six.integer_types)):
if not isinstance(item, six.string_types):
include[idx] = str(item)
if not isinstance(include[idx], six.string_types):
ret['comment'] = \
'Invalid \'include\' item {0}'.format(include[idx])
return ret
else:
include = []
diff = __salt__['pkg.group_diff'](name)
mandatory = diff['mandatory packages']['installed'] + \
diff['mandatory packages']['not installed']
mandatory = diff['mandatory']['installed'] + \
diff['mandatory']['not installed']
invalid_skip = [x for x in mandatory if x in skip]
if invalid_skip:
@ -1912,8 +1899,8 @@ def group_installed(name, skip=None, include=None, **kwargs):
)
return ret
targets = diff['mandatory packages']['not installed']
targets.extend([x for x in diff['default packages']['not installed']
targets = diff['mandatory']['not installed']
targets.extend([x for x in diff['default']['not installed']
if x not in skip])
targets.extend(include)
@ -1922,9 +1909,9 @@ def group_installed(name, skip=None, include=None, **kwargs):
ret['comment'] = 'Group \'{0}\' is already installed'.format(name)
return ret
partially_installed = diff['mandatory packages']['installed'] \
or diff['default packages']['installed'] \
or diff['optional packages']['installed']
partially_installed = diff['mandatory']['installed'] \
or diff['default']['installed'] \
or diff['optional']['installed']
if __opts__['test']:
ret['result'] = None