diff --git a/.gitignore b/.gitignore index 99c360e..1b973c0 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,4 @@ /salt-2015.5.5.tar.gz /salt-2015.5.8.tar.gz /salt-2015.8.3.tar.gz +/salt-2015.5.9.tar.gz diff --git a/2015.5.9-dnf.patch b/2015.5.9-dnf.patch new file mode 100644 index 0000000..2771c1f --- /dev/null +++ b/2015.5.9-dnf.patch @@ -0,0 +1,979 @@ +diff --git a/doc/ref/modules/all/salt.modules.yumpkg.rst b/doc/ref/modules/all/salt.modules.yumpkg.rst +index b385a7d..cfebeea 100644 +--- a/doc/ref/modules/all/salt.modules.yumpkg.rst ++++ b/doc/ref/modules/all/salt.modules.yumpkg.rst +@@ -4,4 +4,4 @@ salt.modules.yumpkg + + .. automodule:: salt.modules.yumpkg + :members: +- :exclude-members: available_version +\ No newline at end of file ++ :exclude-members: available_version, get_locked_packages +diff --git a/salt/modules/yumpkg.py b/salt/modules/yumpkg.py +index 25b26f0..5a15d4d 100644 +--- a/salt/modules/yumpkg.py ++++ b/salt/modules/yumpkg.py +@@ -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,10 @@ 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. For these ++ versions, repoquery is available from the ``dnf-plugins-core`` package. ++ DNF support has been backported into the Fedora RPMs for 2015.5.9. ++ + .. _yum-utils: http://yum.baseurl.org/wiki/YumUtils + + ''' +@@ -19,13 +23,14 @@ 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 import string_types +-from salt.ext.six.moves import shlex_quote as _cmd_quote, range ++from salt.ext.six.moves import shlex_quote as _cmd_quote + + try: + import yum +@@ -133,20 +138,87 @@ def _repoquery_pkginfo(repoquery_args): + return ret + + ++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 _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, query_format=__QUERYFORMAT): +@@ -154,9 +226,17 @@ def _repoquery(repoquery_args, query_format=__QUERYFORMAT): + 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 {0} {1}'.format( ++ _cmd_quote( ++ query_format.replace('-%{VERSION}_', '-%{EPOCH}:%{VERSION}_') ++ ), ++ repoquery_args ++ ) ++ else: ++ 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 = '' +@@ -164,12 +244,13 @@ def _repoquery(repoquery_args, query_format=__QUERYFORMAT): + 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() ++ if _yum() == 'dnf': ++ # Remove the epoch when it is zero to maintain backward compatibility ++ return call['stdout'].replace('_|-0:', '_|-').splitlines() ++ else: ++ return call['stdout'].splitlines() + + + def _get_repo_options(**kwargs): +@@ -187,20 +268,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!r}'.format(fromrepo)) +- repo_arg = ('--disablerepo={0!r} --enablerepo={1!r}' +- .format('*', fromrepo)) ++ log.info('Restricting to repo \'{0}\''.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: +- 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 \'{0}\''.format(disablerepo)) ++ repo_arg.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 \'{0}\''.format(enablerepo)) ++ repo_arg.append('--enablerepo=\'{0}\''.format(enablerepo)) ++ return ' '.join(repo_arg) + + + def _get_excludes_option(**kwargs): +@@ -208,14 +308,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 \'{0}\''.format(disable_excludes)) ++ return '--disableexcludes=\'{0}\''.format(disable_excludes) ++ return '' + + + def _get_branch_option(**kwargs): +@@ -248,7 +352,7 @@ def _rpm_pkginfo(name): + # with 'none' + queryformat = __QUERYFORMAT.replace('%{REPOID}', 'none') + output = __salt__['cmd.run_stdout']( +- 'rpm -qp --queryformat {0!r} {1}'.format(_cmd_quote(queryformat), name), ++ 'rpm -qp --queryformat {0} {1}'.format(_cmd_quote(queryformat), name), + output_loglevel='trace', + ignore_retcode=True + ) +@@ -454,19 +558,61 @@ 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)) +- ) +- +- 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 _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: +@@ -568,8 +714,13 @@ def list_pkgs(versions_as_list=False, **kwargs): + __salt__['pkg_resource.stringify'](ret) + return ret + ++ if _yum() == 'dnf': ++ list_cmd = '--installed' ++ else: ++ list_cmd = '--all --pkgnarrow=installed' ++ + ret = {} +- for pkginfo in _repoquery_pkginfo('--all --pkgnarrow=installed'): ++ for pkginfo in _repoquery_pkginfo(list_cmd): + if pkginfo is None: + continue + __salt__['pkg_resource.add_pkg'](ret, pkginfo.name, pkginfo.version) +@@ -644,7 +795,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) +@@ -669,7 +824,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,9 +841,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]) + + +@@ -705,7 +866,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 +@@ -719,29 +881,33 @@ def check_db(*names, **kwargs): + salt '*' pkg.check_db 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 = {} +@@ -806,13 +972,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 +@@ -1137,8 +1306,10 @@ def install(name=None, + else: + downgrade.append(pkgstr) + ++ yum_cmd = _yum() + if targets: +- cmd = 'yum -y {repo} {exclude} {branch} {gpgcheck} install {pkg}'.format( ++ cmd = '{yum} -y {repo} {exclude} {branch} {gpgcheck} install {pkg}'.format( ++ yum=yum_cmd, + repo=repo_arg, + exclude=exclude_arg, + branch=branch_arg, +@@ -1148,7 +1319,8 @@ def install(name=None, + __salt__['cmd.run'](cmd, output_loglevel='trace') + + if downgrade: +- cmd = 'yum -y {repo} {exclude} {branch} {gpgcheck} downgrade {pkg}'.format( ++ cmd = '{yum} -y {repo} {exclude} {branch} {gpgcheck} downgrade {pkg}'.format( ++ yum=yum_cmd, + repo=repo_arg, + exclude=exclude_arg, + branch=branch_arg, +@@ -1158,7 +1330,8 @@ def install(name=None, + __salt__['cmd.run'](cmd, output_loglevel='trace') + + if to_reinstall: +- cmd = 'yum -y {repo} {exclude} {branch} {gpgcheck} reinstall {pkg}'.format( ++ cmd = '{yum} -y {repo} {exclude} {branch} {gpgcheck} reinstall {pkg}'.format( ++ yum=yum_cmd, + repo=repo_arg, + exclude=exclude_arg, + branch=branch_arg, +@@ -1228,7 +1401,8 @@ def upgrade(refresh=True, skip_verify=False, **kwargs): + refresh_db(**kwargs) + + old = list_pkgs() +- cmd = 'yum -q -y {repo} {exclude} {branch} {gpgcheck} upgrade'.format( ++ cmd = '{yum} -q -y {repo} {exclude} {branch} {gpgcheck} upgrade'.format( ++ yum=_yum(), + repo=repo_arg, + exclude=exclude_arg, + branch=branch_arg, +@@ -1248,7 +1422,7 @@ def remove(name=None, pkgs=None, **kwargs): # pylint: disable=W0613 + Remove packages with ``yum -q -y remove``. + + name +- The name of the package to be deleted. ++ The name of the package to be removed + + + Multiple Package Options: +@@ -1280,7 +1454,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 -q -y remove {0}'.format(' '.join(quoted_targets)) ++ 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() +@@ -1296,7 +1470,7 @@ def purge(name=None, pkgs=None, **kwargs): # pylint: disable=W0613 + :mod:`pkg.remove `. + + name +- The name of the package to be deleted. ++ The name of the package to be purged + + + Multiple Package Options: +@@ -1325,7 +1499,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 +1527,8 @@ def hold(name=None, pkgs=None, sources=None, **kwargs): # pylint: disable=W0613 + salt '*' pkg.hold + 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.' +@@ -1381,7 +1558,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): +@@ -1398,7 +1575,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 -q versionlock {0}'.format(target) ++ cmd = _yum() + ' versionlock {0}'.format(target) + out = __salt__['cmd.run_all'](cmd) + + if out['retcode'] == 0: +@@ -1421,10 +1598,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 +1626,8 @@ def unhold(name=None, pkgs=None, sources=None, **kwargs): # pylint: disable=W06 + salt '*' pkg.unhold + 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,7 +1647,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): +@@ -1487,7 +1667,7 @@ 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 -q versionlock delete {0}'.format( ++ cmd = _yum() + ' -q versionlock delete {0}'.format( + ' '.join(quoted_targets) + ) + out = __salt__['cmd.run_all'](cmd) +@@ -1508,45 +1688,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 -q versionlock list' +- 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) ++ ++ pat = re.compile(lock_re) + +- current_locks = [] +- for item in ret: ++ 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 = list_holds + + + def verify(*names, **kwargs): +@@ -1582,42 +1788,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 +1853,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 +1921,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 ++ `. + + Lists packages belonging to a certain group, and which are installed + +@@ -1681,21 +1934,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 + + +@@ -1732,7 +1983,9 @@ def list_repos(basedir=None): + + def get_repo(repo, basedir=None, **kwargs): # pylint: disable=W0613 + ''' +- Display a repo from (default basedir: all dirs in `reposdir` yum option). ++ ++ Display a repo from (default basedir: all dirs in ``reposdir`` ++ yum option). + + CLI Examples: + diff --git a/salt.spec b/salt.spec index a53a981..466b047 100644 --- a/salt.spec +++ b/salt.spec @@ -15,7 +15,7 @@ %define _salttesting_ver 2015.7.10 Name: salt -Version: 2015.5.8 +Version: 2015.5.9 Release: 1%{?dist} Summary: A parallel remote execution system @@ -36,7 +36,7 @@ Source10: README.fedora Source11: logrotate.salt Source12: salt.bash -#Patch0: salt-%{version}-tests.patch +Patch0: 2015.5.9-dnf.patch BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) @@ -48,7 +48,12 @@ Requires: dmidecode Requires: pciutils Requires: which + +%if 0%{?fedora} > 21 +Requires: dnf-plugins-core +%else Requires: yum-utils +%endif %if 0%{?with_python26} @@ -190,8 +195,8 @@ of an agent (salt-minion) service. %setup -c %setup -T -D -a 1 -#cd %{name}-%{version} -#%patch0 -p1 +cd %{name}-%{version} +%patch0 -p1 %build @@ -455,6 +460,9 @@ rm -rf %{buildroot} %endif %changelog +* Thu Jan 21 2016 Erik Johnson - 2015.5.9-1 +- Update to bugfix release 2015.5.9, patched with proper dnf support + * Sun Dec 20 2015 Erik Johnson - 2015.5.8-1 - Update to bugfix release 2015.5.8 diff --git a/sources b/sources index 54141ff..a38cab3 100644 --- a/sources +++ b/sources @@ -1,2 +1,2 @@ +b5fe2959dd1a993c3c850e984459af13 salt-2015.5.9.tar.gz 993296e827d4198c0ff0e7cb17d13e45 SaltTesting-2015.7.10.tar.gz -91352440e28bbed556130920acd53c0f salt-2015.5.8.tar.gz