mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 17:09:03 +00:00
Merge pull request #38621 from terminalmage/pr34442
Add support for wildcard versions in pkg.installed states
This commit is contained in:
commit
0f838b2b12
@ -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`.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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).
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user