Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
systemsmanagement:saltstack:products:testing
py26-compat-salt
adding-support-for-installing-patches-in-yum-dn...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File adding-support-for-installing-patches-in-yum-dnf-exe.patch of Package py26-compat-salt
From a82897ed9f830485df611f4b5c0592b163f88ee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= <psuarezhernandez@suse.com> Date: Tue, 21 Mar 2017 11:10:06 +0000 Subject: [PATCH] Adding support for installing patches in yum/dnf execution module Adding support for installing patches in Zypper module Adding list_downloaded function to Zypper module Adding list_downloaded function to Yum module Adding new pkg.downloaded state Adding documentation for pkg.downloaded state Adding pkg.patched and pkg.patch_downloaded states Check targets for advisory patches installation Adds support for listing advisory patches with Zypper Adds support for listing advisory patches with Yum Improving function naming Moving advisory ids checks into pkg_resource.parse_targets Fixes _find_download_targets to call _preflight_check Fixes parse_targets when advisory id is passed as name Pylint fixes Enables pkg.downloaded to verify packages after package manager call. Adding missing kwargs parameters to pkg.install call Adding versionadded flags Some refactoring and minor fixes Adding unit tests for Zypper execution module Adding more unit tests for Zypper module Pylint fix --- salt/modules/pkg_resource.py | 9 + salt/modules/yumpkg.py | 118 +++++- salt/modules/zypper.py | 91 ++++- salt/states/pkg.py | 420 +++++++++++++++++++++ tests/unit/modules/zypp/zypper-patches.xml | 10 + tests/unit/modules/zypper_test.py | 119 ++++++ 6 files changed, 761 insertions(+), 6 deletions(-) create mode 100644 tests/unit/modules/zypp/zypper-patches.xml diff --git a/salt/modules/pkg_resource.py b/salt/modules/pkg_resource.py index 9657853c38..928dccae7d 100644 --- a/salt/modules/pkg_resource.py +++ b/salt/modules/pkg_resource.py @@ -121,6 +121,15 @@ def parse_targets(name=None, log.error('Only one of "pkgs" and "sources" can be used.') return None, None + elif 'advisory_ids' in kwargs: + if pkgs: + log.error('Cannot use "advisory_ids" and "pkgs" at the same time') + return None, None + elif kwargs['advisory_ids']: + return kwargs['advisory_ids'], 'advisory' + else: + return [name], 'advisory' + elif pkgs: if version is not None: log.warning('\'version\' argument will be ignored for multiple ' diff --git a/salt/modules/yumpkg.py b/salt/modules/yumpkg.py index a763d468d1..be69320fe8 100644 --- a/salt/modules/yumpkg.py +++ b/salt/modules/yumpkg.py @@ -19,6 +19,7 @@ from __future__ import absolute_import import contextlib import copy import fnmatch +import glob import itertools import logging import os @@ -828,6 +829,27 @@ def list_upgrades(refresh=True, **kwargs): list_updates = salt.utils.alias_function(list_upgrades, 'list_updates') +def list_downloaded(): + ''' + .. versionadded:: Oxygen + + List prefetched packages downloaded by Yum in the local disk. + + CLI example: + + .. code-block:: bash + + salt '*' pkg.list_downloaded + ''' + CACHE_DIR = os.path.join('/var/cache/', _yum()) + + ret = {} + for package_path in glob.glob(os.path.join(CACHE_DIR, '*/*/*/packages/*.rpm')): + pkg_info = __salt__['lowpkg.bin_pkg_info'](package_path) + ret.setdefault(pkg_info['name'], {})[pkg_info['version']] = package_path + return ret + + def info_installed(*names): ''' .. versionadded:: 2015.8.1 @@ -1108,10 +1130,20 @@ def install(name=None, if pkg_params is None or len(pkg_params) == 0: return {} - old = list_pkgs(versions_as_list=False) + old = list_pkgs(versions_as_list=False) if not downloadonly else list_downloaded() + version_num = kwargs.get('version') + if version_num: + if pkgs is None and sources is None: + # Allow "version" to work for single package target + pkg_params = {name: version_num} + else: + log.warning('"version" parameter will be ignored for multiple ' + 'package targets') + + # Use of __context__ means no duplicate work here, just accessing # information already in __context__ from the previous call to list_pkgs() - old_as_list = list_pkgs(versions_as_list=True) + old_as_list = list_pkgs(versions_as_list=True) if not downloadonly else list_downloaded() to_install = [] to_downgrade = [] @@ -1134,6 +1166,16 @@ def install(name=None, if pkg_type == 'repository': pkg_params_items = six.iteritems(pkg_params) + elif pkg_type == 'advisory': + pkg_params_items = [] + cur_patches = list_patches() + for advisory_id in pkg_params: + if advisory_id not in cur_patches: + raise CommandExecutionError( + 'Advisory id "{0}" not found'.format(advisory_id) + ) + else: + pkg_params_items.append(advisory_id) else: pkg_params_items = [] for pkg_source in pkg_params: @@ -1157,6 +1199,9 @@ def install(name=None, for pkg_item_list in pkg_params_items: if pkg_type == 'repository': pkgname, version_num = pkg_item_list + elif pkg_type == 'advisory': + pkgname = pkg_item_list + version_num = None else: try: pkgname, pkgpath, version_num = pkg_item_list @@ -1171,6 +1216,8 @@ def install(name=None, to_reinstall.append((pkgname, pkgname)) else: to_install.append((pkgname, pkgname)) + elif pkg_type == 'advisory': + to_install.append((pkgname, pkgname)) else: to_install.append((pkgname, pkgpath)) else: @@ -1319,6 +1366,8 @@ def install(name=None, targets = [] with _temporarily_unhold(to_install, targets): if targets: + if pkg_type == 'advisory': + targets = ["--advisory={0}".format(t) for t in targets] cmd = [] if salt.utils.systemd.has_scope(__context__) \ and __salt__['config.get']('systemd.scope', True): @@ -1327,7 +1376,7 @@ def install(name=None, if _yum() == 'dnf': cmd.extend(['--best', '--allowerasing']) _add_common_args(cmd) - cmd.append('install') + cmd.append('install' if pkg_type is not 'advisory' else 'update') cmd.extend(targets) out = __salt__['cmd.run_all']( cmd, @@ -1379,7 +1428,7 @@ def install(name=None, errors.append(out['stdout']) __context__.pop('pkg.list_pkgs', None) - new = list_pkgs(versions_as_list=False) + new = list_pkgs(versions_as_list=False) if not downloadonly else list_downloaded() ret = salt.utils.compare_dicts(old, new) @@ -2789,3 +2838,64 @@ def diff(*paths): local_pkgs[pkg]['path'], path) or 'Unchanged' return ret + + +def _get_patches(installed_only=False): + ''' + List all known patches in repos. + ''' + patches = {} + + cmd = [_yum(), '--quiet', 'updateinfo', 'list', 'security', 'all'] + ret = __salt__['cmd.run_stdout']( + cmd, + python_shell=False + ) + for line in salt.utils.itertools.split(ret, os.linesep): + inst, advisory_id, sev, pkg = re.match(r'([i|\s]) ([^\s]+) +([^\s]+) +([^\s]+)', + line).groups() + if inst != 'i' and installed_only: + continue + patches[advisory_id] = { + 'installed': True if inst == 'i' else False, + 'summary': pkg + } + return patches + + +def list_patches(refresh=False): + ''' + .. versionadded:: Oxygen + + List all known advisory patches from available repos. + + refresh + force a refresh if set to True. + If set to False (default) it depends on yum if a refresh is + executed. + + CLI Examples: + + .. code-block:: bash + + salt '*' pkg.list_patches + ''' + if refresh: + refresh_db() + + return _get_patches() + + +def list_installed_patches(): + ''' + .. versionadded:: Oxygen + + List installed advisory patches on the system. + + CLI Examples: + + .. code-block:: bash + + salt '*' pkg.list_installed_patches + ''' + return _get_patches(installed_only=True) diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py index 97f0954ee3..50ddd57ec7 100644 --- a/salt/modules/zypper.py +++ b/salt/modules/zypper.py @@ -15,6 +15,7 @@ Package support for openSUSE via the zypper package manager # Import python libs from __future__ import absolute_import import copy +import glob import logging import re import os @@ -1024,10 +1025,18 @@ def install(name=None, for problem in problems: log.error(problem) return {} + elif pkg_type == 'advisory': + targets = [] + cur_patches = list_patches() + for advisory_id in pkg_params: + if advisory_id not in cur_patches: + raise CommandExecutionError('Advisory id "{0}" not found'.format(advisory_id)) + else: + targets.append(advisory_id) else: targets = pkg_params - old = list_pkgs() + old = list_pkgs() if not downloadonly else list_downloaded() downgrades = [] if fromrepo: fromrepoopt = ['--force', '--force-resolution', '--from', fromrepo] @@ -1045,6 +1054,8 @@ def install(name=None, cmd_install.extend(fromrepoopt) errors = [] + if pkg_type == 'advisory': + targets = ["patch:{0}".format(t) for t in targets] # Split the targets into batches of 500 packages each, so that # the maximal length of the command line is not broken @@ -1063,7 +1074,7 @@ def install(name=None, __zypper__(no_repo_failure=ignore_repo_failure).call(*cmd) __context__.pop('pkg.list_pkgs', None) - new = list_pkgs() + new = list_pkgs() if not downloadonly else list_downloaded() # Handle packages which report multiple new versions # (affects only kernel packages at this point) @@ -1777,6 +1788,28 @@ def download(*packages, **kwargs): ) +def list_downloaded(): + ''' + .. versionadded:: Oxygen + + List prefetched packages downloaded by Zypper in the local disk. + + CLI example: + + .. code-block:: bash + + salt '*' pkg.list_downloaded + ''' + CACHE_DIR = '/var/cache/zypp/packages/' + + ret = {} + # Zypper storage is repository_tag/arch/package-version.rpm + for package_path in glob.glob(os.path.join(CACHE_DIR, '*/*/*.rpm')): + pkg_info = __salt__['lowpkg.bin_pkg_info'](package_path) + ret.setdefault(pkg_info['name'], {})[pkg_info['version']] = package_path + return ret + + def diff(*paths): ''' Return a formatted diff between current files and original in a package. @@ -1814,3 +1847,57 @@ def diff(*paths): ) or 'Unchanged' return ret + + +def _get_patches(installed_only=False): + ''' + List all known patches in repos. + ''' + patches = {} + for element in __zypper__.nolock.xml.call('se', '-t', 'patch').getElementsByTagName('solvable'): + installed = element.getAttribute('status') == 'installed' + if (installed_only and installed) or not installed_only: + patches[element.getAttribute('name')] = { + 'installed': installed, + 'summary': element.getAttribute('summary'), + } + + return patches + + +def list_patches(refresh=False): + ''' + .. versionadded:: Oxygen + + List all known advisory patches from available repos. + + refresh + force a refresh if set to True. + If set to False (default) it depends on zypper if a refresh is + executed. + + CLI Examples: + + .. code-block:: bash + + salt '*' pkg.list_patches + ''' + if refresh: + refresh_db() + + return _get_patches() + + +def list_installed_patches(): + ''' + .. versionadded:: Oxygen + + List installed advisory patches on the system. + + CLI Examples: + + .. code-block:: bash + + salt '*' pkg.list_installed_patches + ''' + return _get_patches(installed_only=True) diff --git a/salt/states/pkg.py b/salt/states/pkg.py index fe2a67a057..aa3cf7b92d 100644 --- a/salt/states/pkg.py +++ b/salt/states/pkg.py @@ -206,6 +206,171 @@ def _find_unpurge_targets(desired): ] +def _find_download_targets(name=None, + version=None, + pkgs=None, + normalize=True, + skip_suggestions=False, + ignore_epoch=False, + **kwargs): + ''' + Inspect the arguments to pkg.downloaded and discover what packages need to + be downloaded. Return a dict of packages to download. + ''' + cur_pkgs = __salt__['pkg.list_downloaded']() + if pkgs: + to_download = _repack_pkgs(pkgs, normalize=normalize) + + if not to_download: + # Badly-formatted SLS + return {'name': name, + 'changes': {}, + 'result': False, + 'comment': 'Invalidly formatted pkgs parameter. See ' + 'minion log.'} + else: + if normalize: + _normalize_name = \ + __salt__.get('pkg.normalize_name', lambda pkgname: pkgname) + to_download = {_normalize_name(name): version} + else: + to_download = {name: version} + + cver = cur_pkgs.get(name, {}) + if name in to_download: + # Package already downloaded, no need to download again + if cver and version in cver: + return {'name': name, + 'changes': {}, + 'result': True, + 'comment': 'Version {0} of package \'{1}\' is already ' + 'downloaded'.format(version, name)} + + # if cver is not an empty string, the package is already downloaded + elif cver and version is None: + # The package is downloaded + return {'name': name, + 'changes': {}, + 'result': True, + 'comment': 'Package {0} is already ' + 'downloaded'.format(name)} + + version_spec = False + if not skip_suggestions: + try: + problems = _preflight_check(to_download, **kwargs) + except CommandExecutionError: + pass + else: + comments = [] + if problems.get('no_suggest'): + comments.append( + 'The following package(s) were not found, and no ' + 'possible matches were found in the package db: ' + '{0}'.format( + ', '.join(sorted(problems['no_suggest'])) + ) + ) + if problems.get('suggest'): + for pkgname, suggestions in \ + six.iteritems(problems['suggest']): + comments.append( + 'Package \'{0}\' not found (possible matches: ' + '{1})'.format(pkgname, ', '.join(suggestions)) + ) + if comments: + if len(comments) > 1: + comments.append('') + return {'name': name, + 'changes': {}, + 'result': False, + 'comment': '. '.join(comments).rstrip()} + + # Find out which packages will be targeted in the call to pkg.download + # Check current downloaded versions against specified versions + targets = {} + problems = [] + for pkgname, pkgver in six.iteritems(to_download): + cver = cur_pkgs.get(pkgname, {}) + # Package not yet downloaded, so add to targets + if not cver: + targets[pkgname] = pkgver + continue + # No version specified but package is already downloaded + elif cver and not pkgver: + continue + + version_spec = True + try: + oper, verstr = _get_comparison_spec(pkgver) + except CommandExecutionError as exc: + problems.append(exc.strerror) + continue + + if not _fulfills_version_spec(cver.keys(), oper, verstr, + ignore_epoch=ignore_epoch): + targets[pkgname] = pkgver + + if problems: + return {'name': name, + 'changes': {}, + 'result': False, + 'comment': ' '.join(problems)} + + if not targets: + # All specified packages are already downloaded + msg = ( + 'All specified packages{0} are already downloaded' + .format(' (matching specified versions)' if version_spec else '') + ) + return {'name': name, + 'changes': {}, + 'result': True, + 'comment': msg} + + return targets + + +def _find_advisory_targets(name=None, + advisory_ids=None, + **kwargs): + ''' + Inspect the arguments to pkg.patch_installed and discover what advisory + patches need to be installed. Return a dict of advisory patches to install. + ''' + cur_patches = __salt__['pkg.list_installed_patches']() + if advisory_ids: + to_download = advisory_ids + else: + to_download = [name] + if cur_patches.get(name, {}): + # Advisory patch already installed, no need to install it again + return {'name': name, + 'changes': {}, + 'result': True, + 'comment': 'Advisory patch {0} is already ' + 'installed'.format(name)} + + # Find out which advisory patches will be targeted in the call to pkg.install + targets = [] + for patch_name in to_download: + cver = cur_patches.get(patch_name, {}) + # Advisory patch not yet installed, so add to targets + if not cver: + targets.append(patch_name) + continue + + if not targets: + # All specified packages are already downloaded + msg = ('All specified advisory patches are already installed') + return {'name': name, + 'changes': {}, + 'result': True, + 'comment': msg} + + return targets + + def _find_remove_targets(name=None, version=None, pkgs=None, @@ -1689,6 +1854,261 @@ def installed( return ret +def downloaded(name, + version=None, + pkgs=None, + fromrepo=None, + ignore_epoch=None, + **kwargs): + ''' + .. versionadded:: Oxygen + + Ensure that the package is downloaded, and that it is the correct version + (if specified). + + Currently supported for the following pkg providers: + :mod:`yumpkg <salt.modules.yumpkg>` and :mod:`zypper <salt.modules.zypper>` + + :param str name: + The name of the package to be downloaded. This parameter is ignored if + either "pkgs" is used. Additionally, please note that this option can + only be used to download packages from a software repository. + + :param str version: + Download a specific version of a package. + + .. important:: + As of version 2015.8.7, for distros which use yum/dnf, packages + which have a version with a nonzero epoch (that is, versions which + start with a number followed by a colon must have the epoch included + when specifying the version number. For example: + + .. code-block:: yaml + + vim-enhanced: + pkg.downloaded: + - version: 2:7.4.160-1.el7 + + An **ignore_epoch** argument has been added to which causes the + epoch to be disregarded when the state checks to see if the desired + version was installed. + + You can install a specific version when using the ``pkgs`` argument by + including the version after the package: + + .. code-block:: yaml + + common_packages: + pkg.downloaded: + - pkgs: + - unzip + - dos2unix + - salt-minion: 2015.8.5-1.el6 + + CLI Example: + + .. code-block:: yaml + + zsh: + pkg.downloaded: + - version: 5.0.5-4.63 + - fromrepo: "myrepository" + ''' + ret = {'name': name, + 'changes': {}, + 'result': None, + 'comment': ''} + + if 'pkg.list_downloaded' not in __salt__: + ret['result'] = False + ret['comment'] = 'The pkg.downloaded state is not available on ' \ + 'this platform' + return ret + + if isinstance(pkgs, list) and len(pkgs) == 0: + ret['result'] = True + ret['comment'] = 'No packages to download provided' + return ret + + # It doesn't make sense here to received 'downloadonly' as kwargs + # as we're explicitely passing 'downloadonly=True' to execution module. + if 'downloadonly' in kwargs: + del kwargs['downloadonly'] + + # Only downloading not yet downloaded packages + targets = _find_download_targets(name, + version, + pkgs, + fromrepo=fromrepo, + ignore_epoch=ignore_epoch, + **kwargs) + if isinstance(targets, dict) and 'result' in targets: + return targets + elif not isinstance(targets, dict): + ret['result'] = False + ret['comment'] = 'An error was encountered while checking targets: ' \ + '{0}'.format(targets) + return ret + + if __opts__['test']: + summary = ', '.join(targets) + ret['comment'] = 'The following packages would be ' \ + 'downloaded: {0}'.format(summary) + return ret + + try: + pkg_ret = __salt__['pkg.install'](name=name, + pkgs=pkgs, + version=version, + downloadonly=True, + fromrepo=fromrepo, + ignore_epoch=ignore_epoch, + **kwargs) + ret['result'] = True + ret['changes'].update(pkg_ret) + except CommandExecutionError as exc: + ret = {'name': name, 'result': False} + if exc.info: + # Get information for state return from the exception. + ret['changes'] = exc.info.get('changes', {}) + ret['comment'] = exc.strerror_without_changes + else: + ret['changes'] = {} + ret['comment'] = 'An error was encountered while downloading ' \ + 'package(s): {0}'.format(exc) + return ret + + new_pkgs = __salt__['pkg.list_downloaded']() + ok, failed = _verify_install(targets, new_pkgs, ignore_epoch=ignore_epoch) + + if failed: + summary = ', '.join([_get_desired_pkg(x, targets) + for x in failed]) + ret['result'] = False + ret['comment'] = 'The following packages failed to ' \ + 'download: {0}'.format(summary) + + if not ret['changes'] and not ret['comment']: + ret['result'] = True + ret['comment'] = 'Packages are already downloaded: ' \ + '{0}'.format(', '.join(targets)) + + return ret + + +def patch_installed(name, advisory_ids=None, downloadonly=None, **kwargs): + ''' + .. versionadded:: Oxygen + + Ensure that packages related to certain advisory ids are installed. + + Currently supported for the following pkg providers: + :mod:`yumpkg <salt.modules.yumpkg>` and :mod:`zypper <salt.modules.zypper>` + + CLI Example: + + .. code-block:: yaml + + issue-foo-fixed: + pkg.patch_installed: + - advisory_ids: + - SUSE-SLE-SERVER-12-SP2-2017-185 + - SUSE-SLE-SERVER-12-SP2-2017-150 + - SUSE-SLE-SERVER-12-SP2-2017-120 + ''' + ret = {'name': name, + 'changes': {}, + 'result': None, + 'comment': ''} + + if 'pkg.list_patches' not in __salt__: + ret['result'] = False + ret['comment'] = 'The pkg.patch_installed state is not available on ' \ + 'this platform' + return ret + + if isinstance(advisory_ids, list) and len(advisory_ids) == 0: + ret['result'] = True + ret['comment'] = 'No advisory ids provided' + return ret + + # Only downloading not yet downloaded packages + targets = _find_advisory_targets(name, advisory_ids, **kwargs) + if isinstance(targets, dict) and 'result' in targets: + return targets + elif not isinstance(targets, list): + ret['result'] = False + ret['comment'] = 'An error was encountered while checking targets: ' \ + '{0}'.format(targets) + return ret + + if __opts__['test']: + summary = ', '.join(targets) + ret['comment'] = 'The following advisory patches would be ' \ + 'downloaded: {0}'.format(summary) + return ret + + try: + pkg_ret = __salt__['pkg.install'](name=name, + advisory_ids=advisory_ids, + downloadonly=downloadonly, + **kwargs) + ret['result'] = True + ret['changes'].update(pkg_ret) + except CommandExecutionError as exc: + ret = {'name': name, 'result': False} + if exc.info: + # Get information for state return from the exception. + ret['changes'] = exc.info.get('changes', {}) + ret['comment'] = exc.strerror_without_changes + else: + ret['changes'] = {} + ret['comment'] = ('An error was encountered while downloading ' + 'package(s): {0}'.format(exc)) + return ret + + if not ret['changes'] and not ret['comment']: + status = 'downloaded' if downloadonly else 'installed' + ret['result'] = True + ret['comment'] = 'Related packages are already {}'.format(status) + + return ret + + +def patch_downloaded(name, advisory_ids=None, **kwargs): + ''' + .. versionadded:: Oxygen + + Ensure that packages related to certain advisory ids are downloaded. + + Currently supported for the following pkg providers: + :mod:`yumpkg <salt.modules.yumpkg>` and :mod:`zypper <salt.modules.zypper>` + + CLI Example: + + .. code-block:: yaml + + preparing-to-fix-issues: + pkg.patch_downloaded: + - advisory_ids: + - SUSE-SLE-SERVER-12-SP2-2017-185 + - SUSE-SLE-SERVER-12-SP2-2017-150 + - SUSE-SLE-SERVER-12-SP2-2017-120 + ''' + if 'pkg.list_patches' not in __salt__: + return {'name': name, + 'result': False, + 'changes': {}, + 'comment': 'The pkg.patch_downloaded state is not available on ' + 'this platform'} + + # It doesn't make sense here to received 'downloadonly' as kwargs + # as we're explicitely passing 'downloadonly=True' to execution module. + if 'downloadonly' in kwargs: + del kwargs['downloadonly'] + return patch_installed(name=name, advisory_ids=advisory_ids, downloadonly=True, **kwargs) + + def latest( name, refresh=None, diff --git a/tests/unit/modules/zypp/zypper-patches.xml b/tests/unit/modules/zypp/zypper-patches.xml new file mode 100644 index 0000000000..20886345b7 --- /dev/null +++ b/tests/unit/modules/zypp/zypper-patches.xml @@ -0,0 +1,10 @@ +<?xml version='1.0'?> +<stream> + <search-result version="0.0"> + <solvable-list> + <solvable status="not-installed" name="SUSE-SLE-SERVER-12-SP2-2017-97" summary="Recommended update for ovmf" kind="patch"/> + <solvable status="installed" name="SUSE-SLE-SERVER-12-SP2-2017-98" summary="Recommended update for kmod" kind="patch"/> + <solvable status="not-installed" name="SUSE-SLE-SERVER-12-SP2-2017-99" summary="Security update for apache2" kind="patch"/> + </solvable-list> + </search-result> +</stream> diff --git a/tests/unit/modules/zypper_test.py b/tests/unit/modules/zypper_test.py index 621fa600c4..7bbfd6c39e 100644 --- a/tests/unit/modules/zypper_test.py +++ b/tests/unit/modules/zypper_test.py @@ -487,6 +487,48 @@ Repository 'DUMMY' not found by its alias, number, or URI. self.assertTrue(pkgs.get(pkg_name)) self.assertEqual(pkgs[pkg_name], pkg_version) + def test_list_patches(self): + ''' + Test advisory patches listing. + + :return: + ''' + + ref_out = { + 'stdout': get_test_data('zypper-patches.xml'), + 'stderr': None, + 'retcode': 0 + } + + PATCHES_RET = { + 'SUSE-SLE-SERVER-12-SP2-2017-97': {'installed': False, 'summary': 'Recommended update for ovmf'}, + 'SUSE-SLE-SERVER-12-SP2-2017-98': {'installed': True, 'summary': 'Recommended update for kmod'}, + 'SUSE-SLE-SERVER-12-SP2-2017-99': {'installed': False, 'summary': 'Security update for apache2'} + } + + with patch.dict(zypper.__salt__, {'cmd.run_all': MagicMock(return_value=ref_out)}): + list_patches = zypper.list_patches(refresh=False) + self.assertEqual(len(list_patches), 3) + self.assertDictEqual(list_patches, PATCHES_RET) + + @patch('glob.glob', MagicMock(return_value=['/var/cache/zypper/packages/foo/bar/test_package.rpm'])) + def test_list_downloaded(self): + ''' + Test downloaded packages listing. + + :return: + ''' + DOWNLOADED_RET = { + 'test-package': { + '1.0': '/var/cache/zypper/packages/foo/bar/test_package.rpm' + } + } + + with patch.dict(zypper.__salt__, {'lowpkg.bin_pkg_info': MagicMock(return_value={'name': 'test-package', 'version': '1.0'})}): + list_downloaded = zypper.list_downloaded() + self.assertEqual(len(list_downloaded), 1) + self.assertDictEqual(list_downloaded, DOWNLOADED_RET) + def test_download(self): ''' Test package download @@ -512,6 +554,83 @@ Repository 'DUMMY' not found by its alias, number, or URI. test_out['_error'] = "The following package(s) failed to download: foo" self.assertEqual(zypper.download("nmap", "foo"), test_out) + @patch('salt.modules.zypper._systemd_scope', MagicMock(return_value=False)) + @patch('salt.modules.zypper.list_downloaded', MagicMock(side_effect=[{}, {'vim': {'1.1': '/foo/bar/test.rpm'}}])) + def test_install_with_downloadonly(self): + ''' + Test a package installation with downloadonly=True. + + :return: + ''' + with patch.dict(zypper.__salt__, {'pkg_resource.parse_targets': MagicMock(return_value=({'vim': None}, 'repository'))}): + with patch('salt.modules.zypper.__zypper__.noraise.call', MagicMock()) as zypper_mock: + ret = zypper.install(pkgs=['vim'], downloadonly=True) + zypper_mock.assert_called_once_with( + '--no-refresh', + 'install', + '--name', + '--auto-agree-with-licenses', + '--download-only', + 'vim' + ) + self.assertDictEqual(ret, {'vim': {'new': {'1.1': '/foo/bar/test.rpm'}, 'old': ''}}) + + @patch('salt.modules.zypper._systemd_scope', MagicMock(return_value=False)) + @patch('salt.modules.zypper.list_downloaded', MagicMock(return_value={'vim': {'1.1': '/foo/bar/test.rpm'}})) + def test_install_with_downloadonly_already_downloaded(self): + ''' + Test a package installation with downloadonly=True when package is already downloaded. + + :return: + ''' + with patch.dict(zypper.__salt__, {'pkg_resource.parse_targets': MagicMock(return_value=({'vim': None}, 'repository'))}): + with patch('salt.modules.zypper.__zypper__.noraise.call', MagicMock()) as zypper_mock: + ret = zypper.install(pkgs=['vim'], downloadonly=True) + zypper_mock.assert_called_once_with( + '--no-refresh', + 'install', + '--name', + '--auto-agree-with-licenses', + '--download-only', + 'vim' + ) + self.assertDictEqual(ret, {}) + + @patch('salt.modules.zypper._systemd_scope', MagicMock(return_value=False)) + @patch('salt.modules.zypper._get_patches', MagicMock(return_value={'SUSE-PATCH-1234': {'installed': False, 'summary': 'test'}})) + @patch('salt.modules.zypper.list_pkgs', MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.2"}])) + def test_install_advisory_patch_ok(self): + ''' + Test successfully advisory patch installation. + + :return: + ''' + with patch.dict(zypper.__salt__, {'pkg_resource.parse_targets': MagicMock(return_value=({'SUSE-PATCH-1234': None}, 'advisory'))}): + with patch('salt.modules.zypper.__zypper__.noraise.call', MagicMock()) as zypper_mock: + ret = zypper.install(advisory_ids=['SUSE-PATCH-1234']) + zypper_mock.assert_called_once_with( + '--no-refresh', + 'install', + '--name', + '--auto-agree-with-licenses', + 'patch:SUSE-PATCH-1234' + ) + self.assertDictEqual(ret, {"vim": {"old": "1.1", "new": "1.2"}}) + + @patch('salt.modules.zypper._systemd_scope', MagicMock(return_value=False)) + @patch('salt.modules.zypper._get_patches', MagicMock(return_value={'SUSE-PATCH-1234': {'installed': False, 'summary': 'test'}})) + @patch('salt.modules.zypper.list_pkgs', MagicMock(return_value={"vim": "1.1"})) + def test_install_advisory_patch_failure(self): + ''' + Test failing advisory patch installation because patch does not exist. + + :return: + ''' + with patch.dict(zypper.__salt__, {'pkg_resource.parse_targets': MagicMock(return_value=({'SUSE-PATCH-XXX': None}, 'advisory'))}): + with patch('salt.modules.zypper.__zypper__.noraise.call', MagicMock()) as zypper_mock: + with self.assertRaisesRegex(CommandExecutionError, '^Advisory id "SUSE-PATCH-XXX" not found$'): + zypper.install(advisory_ids=['SUSE-PATCH-XXX']) + def test_remove_purge(self): ''' Test package removal -- 2.17.1
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor