Merge pull request #38621 from terminalmage/pr34442

Add support for wildcard versions in pkg.installed states
This commit is contained in:
Mike Place 2017-02-07 14:41:14 -07:00 committed by GitHub
commit 0f838b2b12
7 changed files with 578 additions and 69 deletions

View File

@ -64,6 +64,38 @@ Execution Module Changes
<salt.modules.systemd>` module have changed. These changes are described
above alongside the information on the new states which have been added to
manage masking of systemd units.
- The :py:func:`pkg.list_repo_pkgs <salt.modules.yumpkg.list_repo_pkgs>`
function for yum/dnf-based distros has had its default output format changed.
In prior releases, results would be organized by repository. Now, the default
for each package will be a simple list of versions. To get the old behavior,
pass ``byrepo=True`` to the function.
- A ``pkg.list_repo_pkgs`` function has been added for both
:py:func:`Debian/Ubuntu <salt.modules.aptpkg.list_repo_pkgs>` and
:py:func:`Arch Linux <salt.modules.pacman.list_repo_pkgs>`-based distros.
Wildcard Versions in :py:func:`pkg.installed <salt.states.pkg.installed>` States
================================================================================
The :py:func:`pkg.installed <salt.states.pkg.installed>` state now supports
wildcards in package versions, for the following platforms:
- Debian/Ubuntu
- RHEL/CentOS
- Arch Linux
This support also extends to any derivatives of these distros, which use the
:mod:`aptpkg <salt.modules.aptpkg>`, :mod:`yumpkg <salt.modules.yumpkg>`, or
:mod:`pacman <salt.modules.pacman>` providers for the ``pkg`` virtual module.
Using wildcards can be useful for packages where the release name is built into
the version in some way, such as for RHEL/CentOS which typically has version
numbers like ``1.2.34-5.el7``. An example of the usage for this would be:
.. code-block:: yaml
mypkg:
pkg.installed:
- version: '1.2.34*'
- The :mod:`system <salt.modules.system>` module changed the returned format
from "HH:MM AM/PM" to "HH:MM:SS AM/PM" for `get_system_time`.

View File

@ -18,6 +18,7 @@ from __future__ import absolute_import
# Import python libs
import copy
import fnmatch
import os
import re
import logging
@ -38,6 +39,7 @@ import salt.config
import salt.syspaths
from salt.modules.cmdmod import _parse_env
import salt.utils
import salt.utils.itertools
import salt.utils.systemd
from salt.exceptions import (
CommandExecutionError, MinionError, SaltInvocationError
@ -716,11 +718,13 @@ def install(name=None,
if reinstall and cver \
and salt.utils.compare_versions(ver1=version_num,
oper='==',
ver2=cver):
ver2=cver,
cmp_func=version_cmp):
to_reinstall[pkgname] = pkgstr
elif not cver or salt.utils.compare_versions(ver1=version_num,
oper='>=',
ver2=cver):
ver2=cver,
cmp_func=version_cmp):
targets.append(pkgstr)
else:
downgrade.append(pkgstr)
@ -1579,6 +1583,75 @@ def _consolidate_repo_sources(sources):
return sources
def list_repo_pkgs(*args, **kwargs): # pylint: disable=unused-import
'''
.. versionadded:: Nitrogen
Returns all available packages. Optionally, package names (and name globs)
can be passed and the results will be filtered to packages matching those
names.
This function can be helpful in discovering the version or repo to specify
in a :mod:`pkg.installed <salt.states.pkg.installed>` state.
The return data will be a dictionary mapping package names to a list of
version numbers, ordered from newest to oldest. For example:
.. code-block:: python
{
'bash': ['4.3-14ubuntu1.1',
'4.3-14ubuntu1'],
'nginx': ['1.10.0-0ubuntu0.16.04.4',
'1.9.15-0ubuntu1']
}
CLI Examples:
.. code-block:: bash
salt '*' pkg.list_repo_pkgs
salt '*' pkg.list_repo_pkgs foo bar baz
'''
out = __salt__['cmd.run_all'](
['apt-cache', 'dump'],
output_loglevel='trace',
ignore_retcode=True,
python_shell=False
)
ret = {}
pkg_name = None
skip_pkg = False
new_pkg = re.compile('^Package: (.+)')
for line in salt.utils.itertools.split(out['stdout'], '\n'):
try:
cur_pkg = new_pkg.match(line).group(1)
except AttributeError:
pass
else:
if cur_pkg != pkg_name:
pkg_name = cur_pkg
if args:
for arg in args:
if fnmatch.fnmatch(pkg_name, arg):
skip_pkg = False
break
else:
# Package doesn't match any of the passed args, skip it
skip_pkg = True
else:
# No args passed, we're getting all packages
skip_pkg = False
continue
if not skip_pkg:
comps = line.strip().split(None, 1)
if comps[0] == 'Version:':
ret.setdefault(pkg_name, []).append(comps[1])
return ret
def list_repos():
'''
Lists all repos in the sources.list (and sources.lists.d) files

View File

@ -13,9 +13,11 @@ A module to wrap pacman calls, since Arch is the best
# Import python libs
from __future__ import absolute_import
import copy
import fnmatch
import logging
import re
import os.path
from distutils.version import LooseVersion as _LooseVersion # pylint: disable=no-name-in-module,import-error
# Import salt libs
import salt.utils
@ -511,6 +513,9 @@ def install(name=None,
{'<package>': {'old': '<old-version>',
'new': '<new-version>'}}
'''
refresh = salt.utils.is_true(refresh)
sysupgrade = salt.utils.is_true(sysupgrade)
try:
pkg_params, pkg_type = __salt__['pkg_resource.parse_targets'](
name, pkgs, sources, **kwargs
@ -539,18 +544,19 @@ def install(name=None,
cmd.extend(['systemd-run', '--scope'])
cmd.append('pacman')
errors = []
if pkg_type == 'file':
cmd.extend(['-U', '--noprogressbar', '--noconfirm'])
cmd.extend(pkg_params)
elif pkg_type == 'repository':
cmd.append('-S')
if salt.utils.is_true(refresh):
if refresh:
cmd.append('-y')
if salt.utils.is_true(sysupgrade):
if sysupgrade:
cmd.append('-u')
cmd.extend(['--noprogressbar', '--noconfirm', '--needed'])
targets = []
problems = []
wildcards = []
for param, version_num in six.iteritems(pkg_params):
if version_num is None:
targets.append(param)
@ -562,38 +568,79 @@ def install(name=None,
prefix += eq or ''
# If no prefix characters were supplied, use '='
prefix = prefix or '='
if '*' in verstr:
if prefix == '=':
wildcards.append((param, verstr))
else:
errors.append(
'Invalid wildcard for {0}{1}{2}'.format(
param, prefix, verstr
)
)
continue
targets.append('{0}{1}{2}'.format(param, prefix, verstr))
else:
msg = ('Invalid version string \'{0}\' for package '
'\'{1}\''.format(version_num, name))
problems.append(msg)
if problems:
for problem in problems:
log.error(problem)
return {}
errors.append(
'Invalid version string \'{0}\' for package '
'\'{1}\''.format(version_num, name)
)
if wildcards:
# Resolve wildcard matches
_available = list_repo_pkgs(*[x[0] for x in wildcards], refresh=refresh)
for pkgname, verstr in wildcards:
candidates = _available.get(pkgname, [])
match = salt.utils.fnmatch_multiple(candidates, verstr)
if match is not None:
targets.append('='.join((pkgname, match)))
else:
errors.append(
'No version matching \'{0}\' found for package \'{1}\' '
'(available: {2})'.format(
verstr,
pkgname,
', '.join(candidates) if candidates else 'none'
)
)
if refresh:
try:
# Prevent a second refresh when we run the install command
cmd.remove('-y')
except ValueError:
# Shouldn't happen since we only add -y when refresh is True,
# but just in case that code above is inadvertently changed,
# don't let this result in a traceback.
pass
if not errors:
cmd.extend(targets)
old = list_pkgs()
out = __salt__['cmd.run_all'](
cmd,
output_loglevel='trace',
python_shell=False
)
old = list_pkgs()
out = __salt__['cmd.run_all'](
cmd,
output_loglevel='trace',
python_shell=False
)
if out['retcode'] != 0 and out['stderr']:
errors = [out['stderr']]
else:
errors = []
if out['retcode'] != 0 and out['stderr']:
errors = [out['stderr']]
else:
errors = []
__context__.pop('pkg.list_pkgs', None)
new = list_pkgs()
ret = salt.utils.compare_dicts(old, new)
__context__.pop('pkg.list_pkgs', None)
new = list_pkgs()
ret = salt.utils.compare_dicts(old, new)
if errors:
try:
changes = ret
except UnboundLocalError:
# We ran into errors before we attempted to install anything, so
# there are no changes.
changes = {}
raise CommandExecutionError(
'Problem encountered installing package(s)',
info={'errors': errors, 'changes': ret}
info={'errors': errors, 'changes': changes}
)
return ret
@ -905,3 +952,126 @@ def owner(*paths):
if len(ret) == 1:
return next(six.itervalues(ret))
return ret
def list_repo_pkgs(*args, **kwargs):
'''
Returns all available packages. Optionally, package names (and name globs)
can be passed and the results will be filtered to packages matching those
names.
This function can be helpful in discovering the version or repo to specify
in a :mod:`pkg.installed <salt.states.pkg.installed>` state.
The return data will be a dictionary mapping package names to a list of
version numbers, ordered from newest to oldest. If ``byrepo`` is set to
``True``, then the return dictionary will contain repository names at the
top level, and each repository will map packages to lists of version
numbers. For example:
.. code-block:: python
# With byrepo=False (default)
{
'bash': ['4.4.005-2'],
'nginx': ['1.10.2-2']
}
# With byrepo=True
{
'core': {
'bash': ['4.4.005-2']
},
'extra': {
'nginx': ['1.10.2-2']
}
}
fromrepo : None
Only include results from the specified repo(s). Multiple repos can be
specified, comma-separated.
byrepo : False
When ``True``, the return data for each package will be organized by
repository.
refresh : False
When ``True``, the package database will be refreshed (i.e. ``pacman
-Sy``) before checking for available versions.
CLI Examples:
.. code-block:: bash
salt '*' pkg.list_repo_pkgs
salt '*' pkg.list_repo_pkgs foo bar baz
salt '*' pkg.list_repo_pkgs 'samba4*' fromrepo=base,updates
salt '*' pkg.list_repo_pkgs 'python2-*' byrepo=True
'''
kwargs = salt.utils.clean_kwargs(**kwargs)
fromrepo = kwargs.pop('fromrepo', '') or ''
byrepo = kwargs.pop('byrepo', False)
refresh = kwargs.pop('refresh', False)
if kwargs:
salt.utils.invalid_kwargs(kwargs)
if fromrepo:
try:
repos = [x.strip() for x in fromrepo.split(',')]
except AttributeError:
repos = [x.strip() for x in str(fromrepo).split(',')]
else:
repos = []
if refresh:
refresh_db()
out = __salt__['cmd.run_all'](
['pacman', '-Sl'],
output_loglevel='trace',
ignore_retcode=True,
python_shell=False
)
ret = {}
for line in salt.utils.itertools.split(out['stdout'], '\n'):
try:
repo, pkg_name, pkg_ver = line.strip().split()[:3]
except ValueError:
continue
if repos and repo not in repos:
continue
if args:
for arg in args:
if fnmatch.fnmatch(pkg_name, arg):
skip_pkg = False
break
else:
# Package doesn't match any of the passed args, skip it
continue
ret.setdefault(repo, {}).setdefault(pkg_name, []).append(pkg_ver)
if byrepo:
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]],
reverse=True
)
ret[reponame][pkgname] = [x.vstring for x in sorted_versions]
return ret
else:
byrepo_ret = {}
for reponame in ret:
for pkgname in ret[reponame]:
byrepo_ret.setdefault(pkgname, []).extend(ret[reponame][pkgname])
for pkgname in byrepo_ret:
sorted_versions = sorted(
[_LooseVersion(x) for x in byrepo_ret[pkgname]],
reverse=True
)
byrepo_ret[pkgname] = [x.vstring for x in sorted_versions]
return byrepo_ret

View File

@ -624,6 +624,10 @@ def list_repo_pkgs(*args, **kwargs):
<salt.modules.yumpkg.hold>` will only show the currently-installed
version, as locking a package will make other versions appear
unavailable to yum/dnf.
.. versionchanged:: Nitrogen
By default, the versions for each package are no longer organized by
repository. To get results organized by repository, use
``byrepo=True``.
Returns all available packages. Optionally, package names (and name globs)
can be passed and the results will be filtered to packages matching those
@ -640,12 +644,31 @@ def list_repo_pkgs(*args, **kwargs):
This function can be helpful in discovering the version or repo to specify
in a :mod:`pkg.installed <salt.states.pkg.installed>` state.
The return data is a dictionary of repo names, with each repo containing a
dictionary in which the keys are package names, and the values are a list
of version numbers. Here is an example of the return data:
The return data will be a dictionary mapping package names to a list of
version numbers, ordered from newest to oldest. If ``byrepo`` is set to
``True``, then the return dictionary will contain repository names at the
top level, and each repository will map packages to lists of version
numbers. For example:
.. code-block:: python
# With byrepo=False (default)
{
'bash': ['4.1.2-15.el6_5.2',
'4.1.2-15.el6_5.1',
'4.1.2-15.el6_4'],
'kernel': ['2.6.32-431.29.2.el6',
'2.6.32-431.23.3.el6',
'2.6.32-431.20.5.el6',
'2.6.32-431.20.3.el6',
'2.6.32-431.17.1.el6',
'2.6.32-431.11.2.el6',
'2.6.32-431.5.1.el6',
'2.6.32-431.3.1.el6',
'2.6.32-431.1.2.0.1.el6',
'2.6.32-431.el6']
}
# With byrepo=True
{
'base': {
'bash': ['4.1.2-15.el6_4'],
@ -669,22 +692,79 @@ def list_repo_pkgs(*args, **kwargs):
Only include results from the specified repo(s). Multiple repos can be
specified, comma-separated.
CLI Example:
enablerepo (ignored if ``fromrepo`` is specified)
Specify a disabled package repository (or repositories) to enable.
(e.g., ``yum --enablerepo='somerepo'``)
.. versionadded:: Nitrogen
disablerepo (ignored if ``fromrepo`` is specified)
Specify an enabled package repository (or repositories) to disable.
(e.g., ``yum --disablerepo='somerepo'``)
.. versionadded:: Nitrogen
byrepo : False
When ``True``, the return data for each package will be organized by
repository.
.. versionadded:: Nitrogen
cacheonly : False
When ``True``, the repo information will be retrieved from the cached
repo metadata. This is equivalent to passing the ``-C`` option to
yum/dnf.
.. versionadded:: Nitrogen
CLI Examples:
.. code-block:: bash
salt '*' pkg.list_repo_pkgs
salt '*' pkg.list_repo_pkgs foo bar baz
salt '*' pkg.list_repo_pkgs 'samba4*' fromrepo=base,updates
salt '*' pkg.list_repo_pkgs 'python2-*' byrepo=True
'''
try:
repos = tuple(x.strip() for x in kwargs.get('fromrepo').split(','))
except AttributeError:
# Search in all enabled repos
repos = tuple(
x for x, y in six.iteritems(list_repos())
if str(y.get('enabled', '1')) == '1'
)
byrepo = kwargs.pop('byrepo', False)
cacheonly = kwargs.pop('cacheonly', False)
fromrepo = kwargs.pop('fromrepo', '') or ''
disablerepo = kwargs.pop('disablerepo', '') or ''
enablerepo = kwargs.pop('enablerepo', '') or ''
repo_arg = _get_repo_options(fromrepo=fromrepo, **kwargs)
if fromrepo and not isinstance(fromrepo, list):
try:
fromrepo = [x.strip() for x in fromrepo.split(',')]
except AttributeError:
fromrepo = [x.strip() for x in str(fromrepo).split(',')]
if disablerepo and not isinstance(disablerepo, list):
try:
disablerepo = [x.strip() for x in disablerepo.split(',')
if x != '*']
except AttributeError:
disablerepo = [x.strip() for x in str(disablerepo).split(',')
if x != '*']
if enablerepo and not isinstance(enablerepo, list):
try:
enablerepo = [x.strip() for x in enablerepo.split(',')
if x != '*']
except AttributeError:
enablerepo = [x.strip() for x in str(enablerepo).split(',')
if x != '*']
if fromrepo:
repos = fromrepo
else:
repos = [
repo_name for repo_name, repo_info in six.iteritems(list_repos())
if repo_name in enablerepo
or (repo_name not in disablerepo
and str(repo_info.get('enabled', '1')) == '1')
]
ret = {}
@ -715,7 +795,10 @@ def list_repo_pkgs(*args, **kwargs):
)
# Really old version of yum; does not even have --showduplicates option
if yum_version and yum_version < _LooseVersion('3.2.13'):
cmd_prefix = ['yum', '--quiet', 'list']
cmd_prefix = ['yum', '--quiet']
if cacheonly:
cmd_prefix.append('-C')
cmd_prefix.append('list')
for pkg_src in ('installed', 'available'):
# Check installed packages first
out = __salt__['cmd.run_all'](
@ -729,7 +812,10 @@ def list_repo_pkgs(*args, **kwargs):
# The --showduplicates option is added in 3.2.13, but the
# repository-packages subcommand is only in 3.4.3 and newer
elif yum_version and yum_version < _LooseVersion('3.4.3'):
cmd_prefix = ['yum', '--quiet', 'list', '--showduplicates']
cmd_prefix = ['yum', '--quiet', '--showduplicates']
if cacheonly:
cmd_prefix.append('-C')
cmd_prefix.append('list')
for pkg_src in ('installed', 'available'):
# Check installed packages first
out = __salt__['cmd.run_all'](
@ -744,6 +830,8 @@ def list_repo_pkgs(*args, **kwargs):
for repo in repos:
cmd = [_yum(), '--quiet', 'repository-packages', repo,
'list', '--showduplicates']
if cacheonly:
cmd.append('-C')
# Can't concatenate because args is a tuple, using list.extend()
cmd.extend(args)
@ -755,15 +843,28 @@ def list_repo_pkgs(*args, **kwargs):
continue
_parse_output(out['stdout'])
for reponame in ret:
# Sort versions newest to oldest
for pkgname in ret[reponame]:
if byrepo:
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]],
reverse=True
)
ret[reponame][pkgname] = [x.vstring for x in sorted_versions]
return ret
else:
byrepo_ret = {}
for reponame in ret:
for pkgname in ret[reponame]:
byrepo_ret.setdefault(pkgname, []).extend(ret[reponame][pkgname])
for pkgname in byrepo_ret:
sorted_versions = sorted(
[_LooseVersion(x) for x in ret[reponame][pkgname]],
[_LooseVersion(x) for x in byrepo_ret[pkgname]],
reverse=True
)
ret[reponame][pkgname] = [x.vstring for x in sorted_versions]
return ret
byrepo_ret[pkgname] = [x.vstring for x in sorted_versions]
return byrepo_ret
def list_upgrades(refresh=True, **kwargs):
@ -1109,6 +1210,9 @@ def install(name=None,
# version of held packages.
if pkg_type == 'repository':
has_wildcards = [x for x, y in six.iteritems(pkg_params)
if y is not None and '*' in y]
_available = list_repo_pkgs(*has_wildcards, byrepo=False, **kwargs)
pkg_params_items = six.iteritems(pkg_params)
else:
pkg_params_items = []
@ -1130,6 +1234,7 @@ def install(name=None,
[rpm_info['name'], pkg_source, rpm_info['version']]
)
errors = []
for pkg_item_list in pkg_params_items:
if pkg_type == 'repository':
pkgname, version_num = pkg_item_list
@ -1172,6 +1277,23 @@ def install(name=None,
arch = '.' + archpart
pkgname = namepart
if '*' in version_num:
# Resolve wildcard matches
candidates = _available.get(pkgname, [])
match = salt.utils.fnmatch_multiple(candidates, version_num)
if match is not None:
version_num = match
else:
errors.append(
'No version matching \'{0}\' found for package '
'\'{1}\' (available: {2})'.format(
version_num,
pkgname,
', '.join(candidates) if candidates else 'none'
)
)
continue
pkgstr = '{0}-{1}{2}'.format(pkgname, version_num, arch)
else:
pkgstr = pkgpath
@ -1229,9 +1351,9 @@ def install(name=None,
'''
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)
for arg in (repo_arg, exclude_arg, branch_arg):
if arg:
cmd.extend(arg)
if skip_verify:
cmd.append('--nogpgcheck')
@ -1244,7 +1366,6 @@ def install(name=None,
'installed'
)
unhold_prevented = []
errors = []
@contextlib.contextmanager
def _temporarily_unhold(pkgs, targets):
@ -1442,6 +1563,7 @@ def upgrade(name=None,
(e.g., ``yum --disableexcludes='main'``)
.. versionadded:: 2014.7
name
The name of the package to be upgraded. Note that this parameter is
ignored if "pkgs" is passed.
@ -1460,6 +1582,7 @@ def upgrade(name=None,
salt '*' pkg.upgrade name=openssl
.. versionadded:: 2016.3.0
pkgs
A list of packages to upgrade from a software repository. Must be
passed as a python list. A specific version number can be specified
@ -1487,7 +1610,6 @@ def upgrade(name=None,
salt -G role:nsd pkg.upgrade gpfs.gplbin-2.6.32-279.31.1.el6.x86_64 normalize=False
.. versionadded:: 2016.3.0
'''
repo_arg = _get_repo_options(**kwargs)
exclude_arg = _get_excludes_option(**kwargs)

View File

@ -76,6 +76,7 @@ state module
# Import python libs
from __future__ import absolute_import
import errno
import fnmatch
import logging
import os
import re
@ -191,11 +192,15 @@ def _fulfills_version_spec(versions, oper, desired_version,
if isinstance(versions, dict) and 'version' in versions:
versions = versions['version']
for ver in versions:
if salt.utils.compare_versions(ver1=ver,
oper=oper,
ver2=desired_version,
cmp_func=cmp_func,
ignore_epoch=ignore_epoch):
if oper == '==':
if fnmatch.fnmatch(ver, desired_version):
return True
elif salt.utils.compare_versions(ver1=ver,
oper=oper,
ver2=desired_version,
cmp_func=cmp_func,
ignore_epoch=ignore_epoch):
return True
return False
@ -748,19 +753,18 @@ def installed(
.. code-block:: bash
# salt myminion pkg.list_repo_pkgs httpd
# salt myminion pkg.list_repo_pkgs bash
myminion:
----------
base:
|_
----------
httpd:
2.2.15-29.el6.centos
updates:
|_
----------
httpd:
2.2.15-30.el6.centos
----------
bash:
- 4.2.46-21.el7_3
- 4.2.46-20.el7_2
This function was first added for :mod:`pkg.list_repo_pkgs
<salt.modules.yumpkg.list_repo_pkgs>` in 2014.1.0, and was expanded to
:py:func:`Debian/Ubuntu <salt.modules.aptpkg.list_repo_pkgs>` and
:py:func:`Arch Linux <salt.modules.pacman.list_repo_pkgs>`-based
distros in the Nitrogen release.
The version strings returned by either of these functions can be used
as version specifiers in pkg states.
@ -780,6 +784,21 @@ def installed(
If the version given is the string ``latest``, the latest available
package version will be installed à la ``pkg.latest``.
**WILDCARD VERSIONS**
As of the Nitrogen release, this state now supports wildcards in
package versions for Debian/Ubuntu, RHEL/CentOS, Arch Linux, and their
derivatives. Using wildcards can be useful for packages where the
release name is built into the version in some way, such as for
RHEL/CentOS which typically has version numbers like ``1.2.34-5.el7``.
An example of the usage for this would be:
.. code-block:: yaml
mypkg:
pkg.installed:
- version: '1.2.34*'
:param bool refresh:
This parameter controls whether or not the package repo database is
updated prior to installing the requested package(s).

View File

@ -3222,3 +3222,25 @@ def filter_by(lookup_dict,
salt.utils.dictupdate.update(ret, copy.deepcopy(merge))
return ret
def fnmatch_multiple(candidates, pattern):
'''
Convenience function which runs fnmatch.fnmatch() on each element of passed
iterable. The first matching candidate is returned, or None if there is no
matching candidate.
'''
# Make sure that candidates is iterable to avoid a TypeError when we try to
# iterate over its items.
try:
candidates_iter = iter(candidates)
except TypeError:
return None
for candidate in candidates_iter:
try:
if fnmatch.fnmatch(candidate, pattern):
return candidate
except TypeError:
pass
return None

View File

@ -58,6 +58,8 @@ _PKG_TARGETS_EPOCH = {
'RedHat': {'7': 'comps-extras'},
}
_WILDCARDS_SUPPORTED = ('Arch', 'Debian', 'RedHat')
def pkgmgr_avail(run_function, grains):
'''
@ -597,6 +599,75 @@ class PkgTest(integration.ModuleCase,
'Package {0} is already up-to-date'.format(target)
)
@requires_system_grains
def test_pkg_013_installed_with_wildcard_version(self, grains=None):
'''
This is a destructive test as it installs and then removes a package
'''
# Skip test if package manager not available
if not pkgmgr_avail(self.run_function, self.run_function('grains.items')):
self.skipTest('Package manager is not available')
os_family = grains.get('os_family', '')
if os_family not in _WILDCARDS_SUPPORTED:
self.skipTest(
'Wildcards only supported on {0}'.format(
', '.join(_WILDCARDS_SUPPORTED)
)
)
pkg_targets = _PKG_TARGETS.get(os_family, [])
# Make sure that we have targets that match the os_family. If this
# fails then the _PKG_TARGETS dict above needs to have an entry added,
# with two packages that are not installed before these tests are run
self.assertTrue(pkg_targets)
target = pkg_targets[0]
version = self.run_function('pkg.version', [target])
# If this assert fails, we need to find new targets, this test needs to
# be able to test successful installation of packages, so this package
# needs to not be installed before we run the states below
self.assertFalse(version)
ret = self.run_state(
'pkg.installed',
name=target,
version='*',
refresh=False,
)
self.assertSaltTrueReturn(ret)
# Repeat state, should pass
ret = self.run_state(
'pkg.installed',
name=target,
version='*',
refresh=False,
)
expected_comment = (
'All specified packages are already installed and are at the '
'desired version'
)
self.assertSaltTrueReturn(ret)
self.assertEqual(ret[next(iter(ret))]['comment'], expected_comment)
# Repeat one more time with unavailable version, test should fail
ret = self.run_state(
'pkg.installed',
name=target,
version='93413*',
refresh=False,
)
self.assertSaltFalseReturn(ret)
# Clean up
ret = self.run_state('pkg.removed', name=target)
self.assertSaltTrueReturn(ret)
@requires_salt_modules('pkg.group_install')
@requires_system_grains
def test_group_installed_handle_missing_package_group(self, grains=None): # pylint: disable=unused-argument