implement resolve_capabilities option for packages

This commit is contained in:
Michael Calmer 2017-11-14 14:41:31 +01:00
parent b8db7ab7bf
commit 3d28be0938
2 changed files with 183 additions and 3 deletions

View File

@ -20,6 +20,7 @@ import re
import os import os
import time import time
import datetime import datetime
import copy
# Import 3rd-party libs # Import 3rd-party libs
# pylint: disable=import-error,redefined-builtin,no-name-in-module # pylint: disable=import-error,redefined-builtin,no-name-in-module
@ -1049,6 +1050,10 @@ def install(name=None,
operator (<, >, <=, >=, =) and a version number (ex. '>1.2.3-4'). operator (<, >, <=, >=, =) and a version number (ex. '>1.2.3-4').
This parameter is ignored if ``pkgs`` or ``sources`` is passed. This parameter is ignored if ``pkgs`` or ``sources`` is passed.
resolve_capabilities
If this option is set to True zypper will take capabilites into
account. In this case names which are just provided by a package
will get installed. Default is False.
Multiple Package Installation Options: Multiple Package Installation Options:
@ -1164,7 +1169,13 @@ def install(name=None,
log.info('Targeting repo \'{0}\''.format(fromrepo)) log.info('Targeting repo \'{0}\''.format(fromrepo))
else: else:
fromrepoopt = '' fromrepoopt = ''
cmd_install = ['install', '--name', '--auto-agree-with-licenses'] cmd_install = ['install', '--auto-agree-with-licenses']
if kwargs.get('resolve_capabilities', False):
cmd_install.append('--capability')
else:
cmd_install.append('--name')
if not refresh: if not refresh:
cmd_install.insert(0, '--no-refresh') cmd_install.insert(0, '--no-refresh')
if skip_verify: if skip_verify:
@ -1195,6 +1206,7 @@ def install(name=None,
__zypper__(no_repo_failure=ignore_repo_failure).call(*cmd) __zypper__(no_repo_failure=ignore_repo_failure).call(*cmd)
__context__.pop('pkg.list_pkgs', None) __context__.pop('pkg.list_pkgs', None)
__context__.pop('pkg.list_list_provides', None)
new = list_pkgs(attr=diff_attr) if not downloadonly else list_downloaded() new = list_pkgs(attr=diff_attr) if not downloadonly else list_downloaded()
# Handle packages which report multiple new versions # Handle packages which report multiple new versions
@ -1312,6 +1324,7 @@ def upgrade(refresh=True,
__zypper__(systemd_scope=_systemd_scope()).noraise.call(*cmd_update) __zypper__(systemd_scope=_systemd_scope()).noraise.call(*cmd_update)
__context__.pop('pkg.list_pkgs', None) __context__.pop('pkg.list_pkgs', None)
__context__.pop('pkg.list_list_provides', None)
new = list_pkgs() new = list_pkgs()
# Handle packages which report multiple new versions # Handle packages which report multiple new versions
@ -1361,6 +1374,7 @@ def _uninstall(name=None, pkgs=None):
targets = targets[500:] targets = targets[500:]
__context__.pop('pkg.list_pkgs', None) __context__.pop('pkg.list_pkgs', None)
__context__.pop('pkg.list_list_provides', None)
ret = salt.utils.data.compare_dicts(old, list_pkgs()) ret = salt.utils.data.compare_dicts(old, list_pkgs())
if errors: if errors:
@ -2109,3 +2123,85 @@ def list_installed_patches():
salt '*' pkg.list_installed_patches salt '*' pkg.list_installed_patches
''' '''
return _get_patches(installed_only=True) return _get_patches(installed_only=True)
def list_provides(**kwargs):
'''
List package provides currently installed as a dict.
{'<provided_name>': ['<package_name', 'package_name', ...]}
'''
if 'pkg.list_provides' in __context__:
cached = __context__['pkg.list_provides']
return cached
cmd = ['rpm', '-qa', '--queryformat', '[%{PROVIDES}_|-%{NAME}\n]']
ret = {}
for line in __salt__['cmd.run'](cmd, output_loglevel='trace', python_shell=False).splitlines():
provide, realname = line.split('_|-')
if provide == realname:
continue
if provide not in ret:
ret[provide] = list()
ret[provide].append(realname)
__context__['pkg.list_list_provides'] = ret
return ret
def resolve_capabilities(pkgs, refresh, **kwargs):
'''
.. versionadded:: Oxygen
Convert name provides in ``pkgs`` into real package names if
``resolve_capabilities`` parameter is set to True. In case of
``resolve_capabilities`` is set to False the package list
is returned unchanged.
refresh
force a refresh if set to True.
If set to False (default) it depends on zypper if a refresh is
executed.
resolve_capabilities
If this option is set to True the input will be checked if
a package with this name exists. If not, this function will
search for a package which provides this name. If one is found
the output is exchanged with the real package name.
In case this option is set to False (Default) the input will
be returned unchanged.
CLI Examples:
.. code-block:: bash
salt '*' pkg.resolve_capabilities resolve_capabilities=True w3m_ssl
'''
if refresh:
refresh_db()
ret = list()
for pkg in pkgs:
if isinstance(pkg, dict):
for name, version in pkg.items():
break
else:
name = pkg
version = None
if kwargs.get('resolve_capabilities', False):
try:
search(name, match='exact')
except CommandExecutionError as e:
# no package this such a name found
# search for a package which provides this name
result = search(name, provides=True, match='exact')
if len(result.keys()) == 1:
name = result.keys()[0]
elif len(result.keys()) > 1:
log.warn("Found ambiguous match for capability '{0}'.".format(pkg))
if version:
ret.append({name: version})
else:
ret.append(name)
return ret

View File

@ -508,8 +508,11 @@ def _find_install_targets(name=None,
# add it to the kwargs. # add it to the kwargs.
kwargs['refresh'] = refresh kwargs['refresh'] = refresh
resolve_capabilities = kwargs.get('resolve_capabilities', False) and 'pkg.list_provides' in __salt__
try: try:
cur_pkgs = __salt__['pkg.list_pkgs'](versions_as_list=True, **kwargs) cur_pkgs = __salt__['pkg.list_pkgs'](versions_as_list=True, **kwargs)
cur_prov = resolve_capabilities and __salt__['pkg.list_provides'](**kwargs) or dict()
except CommandExecutionError as exc: except CommandExecutionError as exc:
return {'name': name, return {'name': name,
'changes': {}, 'changes': {},
@ -669,6 +672,9 @@ def _find_install_targets(name=None,
failed_verify = False failed_verify = False
for key, val in six.iteritems(desired): for key, val in six.iteritems(desired):
cver = cur_pkgs.get(key, []) cver = cur_pkgs.get(key, [])
if resolve_capabilities and not cver and key in cur_prov:
cver = cur_pkgs.get(cur_prov.get(key)[0], [])
# Package not yet installed, so add to targets # Package not yet installed, so add to targets
if not cver: if not cver:
targets[key] = val targets[key] = val
@ -786,7 +792,7 @@ def _find_install_targets(name=None,
warnings, was_refreshed) warnings, was_refreshed)
def _verify_install(desired, new_pkgs, ignore_epoch=False): def _verify_install(desired, new_pkgs, ignore_epoch=False, new_caps={}):
''' '''
Determine whether or not the installed packages match what was requested in Determine whether or not the installed packages match what was requested in
the SLS file. the SLS file.
@ -809,6 +815,8 @@ def _verify_install(desired, new_pkgs, ignore_epoch=False):
cver = new_pkgs.get(pkgname.split('=')[0]) cver = new_pkgs.get(pkgname.split('=')[0])
else: else:
cver = new_pkgs.get(pkgname) cver = new_pkgs.get(pkgname)
if not cver and pkgname in new_caps:
cver = new_pkgs.get(new_caps.get(pkgname)[0])
if not cver: if not cver:
failed.append(pkgname) failed.append(pkgname)
@ -872,6 +880,27 @@ def _nested_output(obj):
ret = nested.output(obj).rstrip() ret = nested.output(obj).rstrip()
return ret return ret
def _resolve_capabilities(pkgs, refresh=False, **kwargs):
'''
Resolve capabilities in ``pkgs`` and exchange them with real package
names, when the result is distinct.
This feature can be turned on while setting the paramter
``resolve_capabilities`` to True.
Return the input dictionary with replaced capability names and as
second return value a bool which indicate if a refresh was run.
In case of ``resolve_capabilities`` is False (disabled) or not
supported by the implementation the input is returned unchanged.
'''
was_refreshed = False
if not pkgs or 'pkg.resolve_capabilities' not in __salt__:
return (pkgs, was_refreshed)
ret = __salt__['pkg.resolve_capabilities'](pkgs, refresh=refresh, **kwargs)
was_refreshed = refresh
return (ret, was_refreshed)
def installed( def installed(
name, name,
@ -1105,6 +1134,11 @@ def installed(
.. versionadded:: 2014.1.1 .. versionadded:: 2014.1.1
:param bool resolve_capabilities:
Turn on resolving capabilities. This allow to name "provides" or alias names for packages.
.. versionadded:: Oxygen
:param bool allow_updates: :param bool allow_updates:
Allow the package to be updated outside Salt's control (e.g. auto Allow the package to be updated outside Salt's control (e.g. auto
updates on Windows). This means a package on the Minion can have a updates on Windows). This means a package on the Minion can have a
@ -1448,6 +1482,14 @@ def installed(
kwargs['saltenv'] = __env__ kwargs['saltenv'] = __env__
refresh = salt.utils.pkg.check_refresh(__opts__, refresh) refresh = salt.utils.pkg.check_refresh(__opts__, refresh)
# check if capabilities should be checked and modify the requested packages
# accordingly.
if pkgs:
(pkgs, was_refreshed) = _resolve_capabilities(pkgs, refresh=refresh, **kwargs)
if was_refreshed:
refresh = False
if not isinstance(pkg_verify, list): if not isinstance(pkg_verify, list):
pkg_verify = pkg_verify is True pkg_verify = pkg_verify is True
if (pkg_verify or isinstance(pkg_verify, list)) \ if (pkg_verify or isinstance(pkg_verify, list)) \
@ -1707,8 +1749,13 @@ def installed(
if __grains__['os'] == 'FreeBSD': if __grains__['os'] == 'FreeBSD':
kwargs['with_origin'] = True kwargs['with_origin'] = True
new_pkgs = __salt__['pkg.list_pkgs'](versions_as_list=True, **kwargs) new_pkgs = __salt__['pkg.list_pkgs'](versions_as_list=True, **kwargs)
if kwargs.get('resolve_capabilities', False) and 'pkg.list_provides' in __salt__:
new_caps = __salt__['pkg.list_provides'](**kwargs)
else:
new_caps = {}
ok, failed = _verify_install(desired, new_pkgs, ok, failed = _verify_install(desired, new_pkgs,
ignore_epoch=ignore_epoch) ignore_epoch=ignore_epoch,
new_caps=new_caps)
modified = [x for x in ok if x in targets] modified = [x for x in ok if x in targets]
not_modified = [x for x in ok not_modified = [x for x in ok
if x not in targets if x not in targets
@ -1927,6 +1974,11 @@ def downloaded(name,
- dos2unix - dos2unix
- salt-minion: 2015.8.5-1.el6 - salt-minion: 2015.8.5-1.el6
:param bool resolve_capabilities:
Turn on resolving capabilities. This allow to name "provides" or alias names for packages.
.. versionadded:: Oxygen
CLI Example: CLI Example:
.. code-block:: yaml .. code-block:: yaml
@ -1952,11 +2004,24 @@ def downloaded(name,
ret['comment'] = 'No packages to download provided' ret['comment'] = 'No packages to download provided'
return ret return ret
# If just a name (and optionally a version) is passed, just pack them into
# the pkgs argument.
if name and not pkgs:
if version:
pkgs = [{name: version}]
version = None
else:
pkgs = [name]
# It doesn't make sense here to received 'downloadonly' as kwargs # It doesn't make sense here to received 'downloadonly' as kwargs
# as we're explicitely passing 'downloadonly=True' to execution module. # as we're explicitely passing 'downloadonly=True' to execution module.
if 'downloadonly' in kwargs: if 'downloadonly' in kwargs:
del kwargs['downloadonly'] del kwargs['downloadonly']
(pkgs, was_refreshed) = _resolve_capabilities(pkgs, **kwargs)
if was_refreshed:
refresh = False
# Only downloading not yet downloaded packages # Only downloading not yet downloaded packages
targets = _find_download_targets(name, targets = _find_download_targets(name,
version, version,
@ -2203,6 +2268,10 @@ def latest(
This parameter is available only on Debian based distributions and This parameter is available only on Debian based distributions and
has no effect on the rest. has no effect on the rest.
:param bool resolve_capabilities:
Turn on resolving capabilities. This allow to name "provides" or alias names for packages.
.. versionadded:: Oxygen
Multiple Package Installation Options: Multiple Package Installation Options:
@ -2300,6 +2369,12 @@ def latest(
kwargs['saltenv'] = __env__ kwargs['saltenv'] = __env__
# check if capabilities should be checked and modify the requested packages
# accordingly.
(desired_pkgs, was_refreshed) = _resolve_capabilities(desired_pkgs, refresh=refresh, **kwargs)
if was_refreshed:
refresh = False
try: try:
avail = __salt__['pkg.latest_version'](*desired_pkgs, avail = __salt__['pkg.latest_version'](*desired_pkgs,
fromrepo=fromrepo, fromrepo=fromrepo,
@ -2822,6 +2897,11 @@ def uptodate(name, refresh=False, pkgs=None, **kwargs):
This parameter available only on Debian based distributions, and This parameter available only on Debian based distributions, and
have no effect on the rest. have no effect on the rest.
:param bool resolve_capabilities:
Turn on resolving capabilities. This allow to name "provides" or alias names for packages.
.. versionadded:: Oxygen
kwargs kwargs
Any keyword arguments to pass through to ``pkg.upgrade``. Any keyword arguments to pass through to ``pkg.upgrade``.
@ -2841,7 +2921,11 @@ def uptodate(name, refresh=False, pkgs=None, **kwargs):
ret['comment'] = '\'fromrepo\' argument not supported on this platform' ret['comment'] = '\'fromrepo\' argument not supported on this platform'
return ret return ret
if isinstance(refresh, bool): if isinstance(refresh, bool):
(pkgs, was_refreshed) = _resolve_capabilities(pkgs, refresh=refresh, **kwargs)
if was_refreshed:
refresh = False
try: try:
packages = __salt__['pkg.list_upgrades'](refresh=refresh, **kwargs) packages = __salt__['pkg.list_upgrades'](refresh=refresh, **kwargs)
if isinstance(pkgs, list): if isinstance(pkgs, list):