diff --git a/0006-Create-salt-proxy-instantiated-service-file.patch b/0006-Create-salt-proxy-instantiated-service-file.patch new file mode 100644 index 0000000..45c9aa6 --- /dev/null +++ b/0006-Create-salt-proxy-instantiated-service-file.patch @@ -0,0 +1,36 @@ +From e2236cc77888d3c359c23dfb47a57e1e057864bb Mon Sep 17 00:00:00 2001 +From: Christian McHugh +Date: Thu, 10 Mar 2016 13:25:01 -0600 +Subject: [PATCH 6/7] Create salt-proxy instantiated service file + +Add a systemd service file for salt-proxy. + +Instantiate a new proxy service with proxyid=p8000: +# systemctl enable salt-proxy\@p8000.service +# systemctl start salt-proxy\@p8000.service +--- + pkg/salt-proxy@.service | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + create mode 100644 pkg/salt-proxy@.service + +diff --git a/pkg/salt-proxy@.service b/pkg/salt-proxy@.service +new file mode 100644 +index 0000000..f97120a +--- /dev/null ++++ b/pkg/salt-proxy@.service +@@ -0,0 +1,12 @@ ++[Unit] ++Description=salt-proxy service ++After=network.target ++ ++[Service] ++ExecStart=/usr/bin/salt-proxy --proxyid=%I ++Type=simple ++Restart=on-failure ++RestartSec=5s ++ ++[Install] ++WantedBy=multi-user.target +-- +2.8.2 + diff --git a/0006-Update-to-2015.8.8.2.patch b/0006-Update-to-2015.8.8.2.patch deleted file mode 100644 index 4536e12..0000000 --- a/0006-Update-to-2015.8.8.2.patch +++ /dev/null @@ -1,168 +0,0 @@ -From 00600229ac41ae618bf01e8af6e2c0183d924204 Mon Sep 17 00:00:00 2001 -From: Theo Chatzimichos -Date: Sat, 2 Apr 2016 12:29:04 +0200 -Subject: [PATCH 06/12] Update to 2015.8.8.2 - -upstream released a bunch of fixes on top of 2015.8.8, without creating a new -tag and proper release. This commit includes: - - https://github.com/saltstack/salt/pull/32135 - - https://github.com/saltstack/salt/pull/32023 - - https://github.com/saltstack/salt/pull/32117 -see https://docs.saltstack.com/en/latest/topics/releases/2015.8.8.html#salt-2015-8-8-2 ---- - salt/config.py | 63 ++++++++++++++++++++++++++++-------------------- - salt/modules/win_dacl.py | 7 +++--- - 2 files changed, 41 insertions(+), 29 deletions(-) - -diff --git a/salt/config.py b/salt/config.py -index fe1f572..929e094 100644 ---- a/salt/config.py -+++ b/salt/config.py -@@ -63,7 +63,7 @@ FLO_DIR = os.path.join( - - VALID_OPTS = { - # The address of the salt master. May be specified as IP address or hostname -- 'master': str, -+ 'master': (str, list), - - # The TCP/UDP port of the master to connect to in order to listen to publications - 'master_port': int, -@@ -541,7 +541,7 @@ VALID_OPTS = { - 'file_recv': bool, - 'file_recv_max_size': int, - 'file_ignore_regex': list, -- 'file_ignore_glob': bool, -+ 'file_ignore_glob': list, - 'fileserver_backend': list, - 'fileserver_followsymlinks': bool, - 'fileserver_ignoresymlinks': bool, -@@ -833,7 +833,7 @@ DEFAULT_MINION_OPTS = { - 'file_recv': False, - 'file_recv_max_size': 100, - 'file_ignore_regex': [], -- 'file_ignore_glob': None, -+ 'file_ignore_glob': [], - 'fileserver_backend': ['roots'], - 'fileserver_followsymlinks': True, - 'fileserver_ignoresymlinks': False, -@@ -1348,26 +1348,30 @@ def _validate_opts(opts): - Check that all of the types of values passed into the config are - of the right types - ''' -+ def format_multi_opt(valid_type): -+ try: -+ num_types = len(valid_type) -+ except TypeError: -+ # Bare type name won't have a length, return the name of the type -+ # passed. -+ return valid_type.__name__ -+ else: -+ if num_types == 1: -+ return valid_type.__name__ -+ elif num_types > 1: -+ ret = ', '.join(x.__name__ for x in valid_type[:-1]) -+ ret += ' or ' + valid_type[-1].__name__ -+ - errors = [] -- err = ('Key {0} with value {1} has an invalid type of {2}, a {3} is ' -+ -+ err = ('Key \'{0}\' with value {1} has an invalid type of {2}, a {3} is ' - 'required for this value') - for key, val in six.iteritems(opts): - if key in VALID_OPTS: -- if isinstance(VALID_OPTS[key](), list): -- if isinstance(val, VALID_OPTS[key]): -- continue -- else: -- errors.append( -- err.format(key, val, type(val).__name__, 'list') -- ) -- if isinstance(VALID_OPTS[key](), dict): -- if isinstance(val, VALID_OPTS[key]): -- continue -- else: -- errors.append( -- err.format(key, val, type(val).__name__, 'dict') -- ) -- else: -+ if isinstance(val, VALID_OPTS[key]): -+ continue -+ -+ if hasattr(VALID_OPTS[key], '__call__'): - try: - VALID_OPTS[key](val) - if isinstance(val, (list, dict)): -@@ -1384,14 +1388,21 @@ def _validate_opts(opts): - VALID_OPTS[key].__name__ - ) - ) -- except ValueError: -+ except (TypeError, ValueError): - errors.append( -- err.format(key, val, type(val).__name__, VALID_OPTS[key]) -- ) -- except TypeError: -- errors.append( -- err.format(key, val, type(val).__name__, VALID_OPTS[key]) -+ err.format(key, -+ val, -+ type(val).__name__, -+ VALID_OPTS[key].__name__) - ) -+ continue -+ -+ errors.append( -+ err.format(key, -+ val, -+ type(val).__name__, -+ format_multi_opt(VALID_OPTS[key].__name__)) -+ ) - - # RAET on Windows uses 'win32file.CreateMailslot()' for IPC. Due to this, - # sock_dirs must start with '\\.\mailslot\' and not contain any colons. -@@ -1404,7 +1415,7 @@ def _validate_opts(opts): - '\\\\.\\mailslot\\' + opts['sock_dir'].replace(':', '')) - - for error in errors: -- log.warning(error) -+ log.debug(error) - if errors: - return False - return True -diff --git a/salt/modules/win_dacl.py b/salt/modules/win_dacl.py -index d57bb7b..d9ee27a 100644 ---- a/salt/modules/win_dacl.py -+++ b/salt/modules/win_dacl.py -@@ -44,9 +44,10 @@ class daclConstants(object): - # in ntsecuritycon has the extra bits 0x200 enabled. - # Note that you when you set this permission what you'll generally get back is it - # ORed with 0x200 (SI_NO_ACL_PROTECT), which is what ntsecuritycon incorrectly defines. -- FILE_ALL_ACCESS = (ntsecuritycon.STANDARD_RIGHTS_REQUIRED | ntsecuritycon.SYNCHRONIZE | 0x1ff) - - def __init__(self): -+ self.FILE_ALL_ACCESS = (ntsecuritycon.STANDARD_RIGHTS_REQUIRED | ntsecuritycon.SYNCHRONIZE | 0x1ff) -+ - self.hkeys_security = { - 'HKEY_LOCAL_MACHINE': 'MACHINE', - 'HKEY_USERS': 'USERS', -@@ -88,7 +89,7 @@ class daclConstants(object): - ntsecuritycon.DELETE, - 'TEXT': 'modify'}, - 'FULLCONTROL': { -- 'BITS': daclConstants.FILE_ALL_ACCESS, -+ 'BITS': self.FILE_ALL_ACCESS, - 'TEXT': 'full control'} - } - } -@@ -368,7 +369,7 @@ def add_ace(path, objectType, user, permission, acetype, propagation): - path: path to the object (i.e. c:\\temp\\file, HKEY_LOCAL_MACHINE\\SOFTWARE\\KEY, etc) - user: user to add - permission: permissions for the user -- acetypes: either allow/deny for each user/permission (ALLOW, DENY) -+ acetype: either allow/deny for each user/permission (ALLOW, DENY) - propagation: how the ACE applies to children for Registry Keys and Directories(KEY, KEY&SUBKEYS, SUBKEYS) - - CLI Example: --- -2.1.4 - diff --git a/0007-Add-SUSE-Manager-plugin.patch b/0007-Add-SUSE-Manager-plugin.patch new file mode 100644 index 0000000..a6d39f0 --- /dev/null +++ b/0007-Add-SUSE-Manager-plugin.patch @@ -0,0 +1,221 @@ +From df87ac3485ff8b5013e720435905afda6b53ada8 Mon Sep 17 00:00:00 2001 +From: Bo Maryniuk +Date: Mon, 9 May 2016 10:33:44 +0200 +Subject: [PATCH 7/7] Add SUSE Manager plugin + +* Add unit test to the libzypp drift detector plugin +--- + scripts/zypper/plugins/commit/README.md | 3 ++ + scripts/zypper/plugins/commit/susemanager | 59 ++++++++++++++++++++++++++++ + tests/unit/zypp_plugins_test.py | 51 ++++++++++++++++++++++++ + tests/zypp_plugin.py | 64 +++++++++++++++++++++++++++++++ + 4 files changed, 177 insertions(+) + create mode 100644 scripts/zypper/plugins/commit/README.md + create mode 100755 scripts/zypper/plugins/commit/susemanager + create mode 100644 tests/unit/zypp_plugins_test.py + create mode 100644 tests/zypp_plugin.py + +diff --git a/scripts/zypper/plugins/commit/README.md b/scripts/zypper/plugins/commit/README.md +new file mode 100644 +index 0000000..01c8917 +--- /dev/null ++++ b/scripts/zypper/plugins/commit/README.md +@@ -0,0 +1,3 @@ ++# Zypper plugins ++ ++Plugins here are required to interact with SUSE Manager in conjunction of SaltStack and Zypper. +diff --git a/scripts/zypper/plugins/commit/susemanager b/scripts/zypper/plugins/commit/susemanager +new file mode 100755 +index 0000000..268298b +--- /dev/null ++++ b/scripts/zypper/plugins/commit/susemanager +@@ -0,0 +1,59 @@ ++#!/usr/bin/python ++# ++# Copyright (c) 2016 SUSE Linux LLC ++# All Rights Reserved. ++# ++# Author: Bo Maryniuk ++ ++import sys ++import os ++import hashlib ++ ++from zypp_plugin import Plugin ++ ++ ++class DriftDetector(Plugin): ++ """ ++ Return diff of the installed packages outside the Salt. ++ """ ++ def __init__(self): ++ Plugin.__init__(self) ++ self.ck_path = "/var/cache/salt/minion/rpmdb.cookie" ++ self.rpm_path = "/var/lib/rpm/Packages" ++ ++ def _get_mtime(self): ++ ''' ++ Get the modified time of the RPM Database. ++ Returns: ++ Unix ticks ++ ''' ++ return os.path.exists(self.rpm_path) and int(os.path.getmtime(self.rpm_path)) or 0 ++ ++ def _get_checksum(self): ++ ''' ++ Get the checksum of the RPM Database. ++ Returns: ++ hexdigest ++ ''' ++ digest = hashlib.md5() ++ with open(self.rpm_path, "rb") as rpm_db_fh: ++ while True: ++ buff = rpm_db_fh.read(0x1000) ++ if not buff: ++ break ++ digest.update(buff) ++ ++ return digest.hexdigest() ++ ++ def PLUGINEND(self, headers, body): ++ """ ++ Hook when plugin closes Zypper's transaction. ++ """ ++ if 'SALT_RUNNING' not in os.environ: ++ with open(self.ck_path, 'w') as ck_fh: ++ ck_fh.write('{chksum} {mtime}\n'.format(chksum=self._get_checksum(), mtime=self._get_mtime())) ++ ++ self.ack() ++ ++ ++DriftDetector().main() +diff --git a/tests/unit/zypp_plugins_test.py b/tests/unit/zypp_plugins_test.py +new file mode 100644 +index 0000000..6075288 +--- /dev/null ++++ b/tests/unit/zypp_plugins_test.py +@@ -0,0 +1,51 @@ ++# -*- coding: utf-8 -*- ++''' ++ :codeauthor: :email:`Bo Maryniuk ` ++''' ++ ++# Import Python Libs ++from __future__ import absolute_import ++ ++# Import Salt Testing Libs ++from salttesting.helpers import ensure_in_syspath ++from salttesting import TestCase, skipIf ++from salttesting.mock import ( ++ MagicMock, ++ patch, ++ NO_MOCK, ++ NO_MOCK_REASON ++) ++ ++ensure_in_syspath('../') ++ ++import os ++import imp ++from zypp_plugin import BogusIO ++ ++susemanager = imp.load_source('susemanager', os.path.sep.join(os.path.dirname(__file__).split( ++ os.path.sep)[:-2] + ['scripts', 'zypper', 'plugins', 'commit', 'susemanager'])) ++ ++@skipIf(NO_MOCK, NO_MOCK_REASON) ++class ZyppPluginsTestCase(TestCase): ++ ''' ++ Test shipped libzypp plugins. ++ ''' ++ def test_drift_detector(self): ++ ''' ++ Test drift detector for a correct cookie file. ++ Returns: ++ ++ ''' ++ drift = susemanager.DriftDetector() ++ drift._get_mtime = MagicMock(return_value=123) ++ drift._get_checksum = MagicMock(return_value='deadbeef') ++ bogus_io = BogusIO() ++ with patch('susemanager.open', bogus_io): ++ drift.PLUGINEND(None, None) ++ self.assertEqual(str(bogus_io), 'deadbeef 123\n') ++ self.assertEqual(bogus_io.mode, 'w') ++ self.assertEqual(bogus_io.path, '/var/cache/salt/minion/rpmdb.cookie') ++ ++if __name__ == '__main__': ++ from integration import run_tests ++ run_tests(ZyppPluginsTestCase, needs_daemon=False) +diff --git a/tests/zypp_plugin.py b/tests/zypp_plugin.py +new file mode 100644 +index 0000000..218f703 +--- /dev/null ++++ b/tests/zypp_plugin.py +@@ -0,0 +1,64 @@ ++''' ++Related to zypp_plugins_test.py module. ++''' ++ ++ ++class Plugin(object): ++ ''' ++ Bogus module for Zypp Plugins tests. ++ ''' ++ def ack(self): ++ ''' ++ Acknowledge that the plugin had finished the transaction ++ Returns: ++ ++ ''' ++ ++ def main(self): ++ ''' ++ Register plugin ++ Returns: ++ ++ ''' ++ ++ ++class BogusIO(object): ++ ''' ++ Read/write logger. ++ ''' ++ ++ def __init__(self): ++ self.content = list() ++ self.closed = False ++ ++ def __str__(self): ++ return '\n'.join(self.content) ++ ++ def __call__(self, *args, **kwargs): ++ self.path, self.mode = args ++ return self ++ ++ def __exit__(self, exc_type, exc_val, exc_tb): ++ self.close() ++ ++ def __enter__(self): ++ return self ++ ++ def write(self, data): ++ ''' ++ Simulate writing data ++ Args: ++ data: ++ ++ Returns: ++ ++ ''' ++ self.content.append(data) ++ ++ def close(self): ++ ''' ++ Simulate closing the IO object. ++ Returns: ++ ++ ''' ++ self.closed = True +-- +2.8.2 + diff --git a/0007-Force-sort-the-RPM-output-to-ensure-latest-version-o.patch b/0007-Force-sort-the-RPM-output-to-ensure-latest-version-o.patch deleted file mode 100644 index 6774a4e..0000000 --- a/0007-Force-sort-the-RPM-output-to-ensure-latest-version-o.patch +++ /dev/null @@ -1,350 +0,0 @@ -From e3a599712daafb88b6b77ebf6c7684fdd10ffedf Mon Sep 17 00:00:00 2001 -From: Bo Maryniuk -Date: Wed, 30 Mar 2016 12:14:21 +0200 -Subject: [PATCH 07/12] Force-sort the RPM output to ensure latest version of - the multi-package on top of the list. - -- Remove version_cmp from the yumpkg and use just a lowpkg alias -- Remove version_cmp from Zypper module and use just lowpkg alias -- Merge yumpkg's and zypper's version_cmp for a common use -- Sort installed pkgs data by version_cmp -- Move "string to EVR" function to the utilities -- Remove suse/redhat checks, refactor code. -- Fix condition from returning None on 0 -- Remove tests from the zypper_test that belongs to rpm_test -- Add lowpkg tests for version comparison -- Fix lint -- Fix the documentation ---- - salt/modules/rpm.py | 60 +++++++++++++++++++++++++++++++++++++-- - salt/modules/yumpkg.py | 28 ++---------------- - salt/modules/zypper.py | 58 +------------------------------------ - salt/utils/__init__.py | 35 +++++++++++++++++++++++ - tests/unit/modules/rpm_test.py | 21 ++++++++++++++ - tests/unit/modules/zypper_test.py | 22 -------------- - 6 files changed, 117 insertions(+), 107 deletions(-) - -diff --git a/salt/modules/rpm.py b/salt/modules/rpm.py -index 5d60dd2..6026f18 100644 ---- a/salt/modules/rpm.py -+++ b/salt/modules/rpm.py -@@ -17,6 +17,19 @@ import salt.utils.pkg.rpm - # pylint: disable=import-error,redefined-builtin - from salt.ext.six.moves import shlex_quote as _cmd_quote - from salt.ext.six.moves import zip -+ -+try: -+ import rpm -+ HAS_RPM = True -+except ImportError: -+ HAS_RPM = False -+ -+try: -+ import rpmUtils.miscutils -+ HAS_RPMUTILS = True -+except ImportError: -+ HAS_RPMUTILS = False -+ - # pylint: enable=import-error,redefined-builtin - from salt.exceptions import CommandExecutionError, SaltInvocationError - -@@ -491,7 +504,7 @@ def info(*packages, **attr): - else: - out = call['stdout'] - -- ret = dict() -+ _ret = list() - for pkg_info in re.split(r"----*", out): - pkg_info = pkg_info.strip() - if not pkg_info: -@@ -538,6 +551,49 @@ def info(*packages, **attr): - if attr and 'description' in attr or not attr: - pkg_data['description'] = os.linesep.join(descr) - if pkg_name: -- ret[pkg_name] = pkg_data -+ pkg_data['name'] = pkg_name -+ _ret.append(pkg_data) -+ -+ # Force-sort package data by version, -+ # pick only latest versions -+ # (in case multiple packages installed, e.g. kernel) -+ ret = dict() -+ for pkg_data in reversed(sorted(_ret, cmp=lambda a_vrs, b_vrs: version_cmp(a_vrs['version'], b_vrs['version']))): -+ pkg_name = pkg_data.pop('name') -+ if pkg_name not in ret: -+ ret[pkg_name] = pkg_data.copy() - - return ret -+ -+ -+def version_cmp(ver1, ver2): -+ ''' -+ .. versionadded:: 2015.8.9 -+ -+ Do a cmp-style comparison on two packages. Return -1 if ver1 < ver2, 0 if -+ ver1 == ver2, and 1 if ver1 > ver2. Return None if there was a problem -+ making the comparison. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt '*' pkg.version_cmp '0.2-001' '0.2.0.1-002' -+ ''' -+ try: -+ if HAS_RPM: -+ cmp_func = rpm.labelCompare -+ elif HAS_RPMUTILS: -+ cmp_func = rpmUtils.miscutils.compareEVR -+ else: -+ cmp_func = None -+ cmp_result = cmp_func is None and 2 or cmp_func(salt.utils.str_version_to_evr(ver1), -+ salt.utils.str_version_to_evr(ver2)) -+ if cmp_result not in (-1, 0, 1): -+ raise Exception("Comparison result '{0}' is invalid".format(cmp_result)) -+ -+ return cmp_result -+ except Exception as exc: -+ log.warning("Failed to compare version '{0}' to '{1}' using RPM: {2}".format(ver1, ver2, exc)) -+ -+ return salt.utils.version_cmp(ver1, ver2) -diff --git a/salt/modules/yumpkg.py b/salt/modules/yumpkg.py -index 1bfc38d..1cde676 100644 ---- a/salt/modules/yumpkg.py -+++ b/salt/modules/yumpkg.py -@@ -40,12 +40,6 @@ try: - except ImportError: - from salt.ext.six.moves import configparser - HAS_YUM = False -- --try: -- import rpmUtils.miscutils -- HAS_RPMUTILS = True --except ImportError: -- HAS_RPMUTILS = False - # pylint: enable=import-error,redefined-builtin - - # Import salt libs -@@ -665,26 +659,8 @@ def version_cmp(pkg1, pkg2): - - salt '*' pkg.version_cmp '0.2-001' '0.2.0.1-002' - ''' -- if HAS_RPMUTILS: -- try: -- cmp_result = rpmUtils.miscutils.compareEVR( -- rpmUtils.miscutils.stringToVersion(pkg1), -- rpmUtils.miscutils.stringToVersion(pkg2) -- ) -- if cmp_result not in (-1, 0, 1): -- raise Exception( -- 'cmp result \'{0}\' is invalid'.format(cmp_result) -- ) -- return cmp_result -- except Exception as exc: -- log.warning( -- 'Failed to compare version \'%s\' to \'%s\' using ' -- 'rpmUtils: %s', pkg1, pkg2, exc -- ) -- # Fall back to distutils.version.LooseVersion (should only need to do -- # this for RHEL5, or if an exception is raised when attempting to compare -- # using rpmUtils) -- return salt.utils.version_cmp(pkg1, pkg2) -+ -+ return __salt__['lowpkg.version_cmp'](pkg1, pkg2) - - - def list_pkgs(versions_as_list=False, **kwargs): -diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py -index 27b00d5..63c473c 100644 ---- a/salt/modules/zypper.py -+++ b/salt/modules/zypper.py -@@ -17,12 +17,6 @@ import os - import salt.ext.six as six - from salt.ext.six.moves import configparser - from salt.ext.six.moves.urllib.parse import urlparse as _urlparse -- --try: -- import rpm -- HAS_RPM = True --except ImportError: -- HAS_RPM = False - # pylint: enable=import-error,redefined-builtin,no-name-in-module - - from xml.dom import minidom as dom -@@ -347,40 +341,6 @@ def version(*names, **kwargs): - return __salt__['pkg_resource.version'](*names, **kwargs) or {} - - --def _string_to_evr(verstring): -- ''' -- Split the version string into epoch, version and release and -- return this as tuple. -- -- epoch is always not empty. -- version and release can be an empty string if such a component -- could not be found in the version string. -- -- "2:1.0-1.2" => ('2', '1.0', '1.2) -- "1.0" => ('0', '1.0', '') -- "" => ('0', '', '') -- ''' -- if verstring in [None, '']: -- return ('0', '', '') -- idx_e = verstring.find(':') -- if idx_e != -1: -- try: -- epoch = str(int(verstring[:idx_e])) -- except ValueError: -- # look, garbage in the epoch field, how fun, kill it -- epoch = '0' # this is our fallback, deal -- else: -- epoch = '0' -- idx_r = verstring.find('-') -- if idx_r != -1: -- version = verstring[idx_e + 1:idx_r] -- release = verstring[idx_r + 1:] -- else: -- version = verstring[idx_e + 1:] -- release = '' -- return (epoch, version, release) -- -- - def version_cmp(ver1, ver2): - ''' - .. versionadded:: 2015.5.4 -@@ -395,23 +355,7 @@ def version_cmp(ver1, ver2): - - salt '*' pkg.version_cmp '0.2-001' '0.2.0.1-002' - ''' -- if HAS_RPM: -- try: -- cmp_result = rpm.labelCompare( -- _string_to_evr(ver1), -- _string_to_evr(ver2) -- ) -- if cmp_result not in (-1, 0, 1): -- raise Exception( -- 'cmp result \'{0}\' is invalid'.format(cmp_result) -- ) -- return cmp_result -- except Exception as exc: -- log.warning( -- 'Failed to compare version \'{0}\' to \'{1}\' using ' -- 'rpmUtils: {2}'.format(ver1, ver2, exc) -- ) -- return salt.utils.version_cmp(ver1, ver2) -+ return __salt__['lowpkg.version_cmp'](ver1, ver2) - - - def list_pkgs(versions_as_list=False, **kwargs): -diff --git a/salt/utils/__init__.py b/salt/utils/__init__.py -index f83a677..8956a15 100644 ---- a/salt/utils/__init__.py -+++ b/salt/utils/__init__.py -@@ -2881,3 +2881,38 @@ def split_input(val): - return [x.strip() for x in val.split(',')] - except AttributeError: - return [x.strip() for x in str(val).split(',')] -+ -+ -+def str_version_to_evr(verstring): -+ ''' -+ Split the package version string into epoch, version and release. -+ Return this as tuple. -+ -+ The epoch is always not empty. The version and the release can be an empty -+ string if such a component could not be found in the version string. -+ -+ "2:1.0-1.2" => ('2', '1.0', '1.2) -+ "1.0" => ('0', '1.0', '') -+ "" => ('0', '', '') -+ ''' -+ if verstring in [None, '']: -+ return '0', '', '' -+ -+ idx_e = verstring.find(':') -+ if idx_e != -1: -+ try: -+ epoch = str(int(verstring[:idx_e])) -+ except ValueError: -+ # look, garbage in the epoch field, how fun, kill it -+ epoch = '0' # this is our fallback, deal -+ else: -+ epoch = '0' -+ idx_r = verstring.find('-') -+ if idx_r != -1: -+ version = verstring[idx_e + 1:idx_r] -+ release = verstring[idx_r + 1:] -+ else: -+ version = verstring[idx_e + 1:] -+ release = '' -+ -+ return epoch, version, release -diff --git a/tests/unit/modules/rpm_test.py b/tests/unit/modules/rpm_test.py -index 8bfce9b..f180736 100644 ---- a/tests/unit/modules/rpm_test.py -+++ b/tests/unit/modules/rpm_test.py -@@ -95,6 +95,27 @@ class RpmTestCase(TestCase): - self.assertDictEqual(rpm.owner('/usr/bin/python', '/usr/bin/vim'), - ret) - -+ @patch('salt.modules.rpm.HAS_RPM', True) -+ def test_version_cmp_rpm(self): -+ ''' -+ Test package version is called RPM version if RPM-Python is installed -+ -+ :return: -+ ''' -+ rpm.rpm = MagicMock(return_value=MagicMock) -+ with patch('salt.modules.rpm.rpm.labelCompare', MagicMock(return_value=0)): -+ self.assertEqual(0, rpm.version_cmp('1', '2')) # mock returns 0, which means RPM was called -+ -+ @patch('salt.modules.rpm.HAS_RPM', False) -+ def test_version_cmp_fallback(self): -+ ''' -+ Test package version is called RPM version if RPM-Python is installed -+ -+ :return: -+ ''' -+ rpm.rpm = MagicMock(return_value=MagicMock) -+ with patch('salt.modules.rpm.rpm.labelCompare', MagicMock(return_value=0)): -+ self.assertEqual(-1, rpm.version_cmp('1', '2')) # mock returns -1, a python implementation was called - - if __name__ == '__main__': - from integration import run_tests -diff --git a/tests/unit/modules/zypper_test.py b/tests/unit/modules/zypper_test.py -index 5c4eb67..67cf52a 100644 ---- a/tests/unit/modules/zypper_test.py -+++ b/tests/unit/modules/zypper_test.py -@@ -301,28 +301,6 @@ class ZypperTestCase(TestCase): - self.assertFalse(zypper.upgrade_available(pkg_name)) - self.assertTrue(zypper.upgrade_available('vim')) - -- @patch('salt.modules.zypper.HAS_RPM', True) -- def test_version_cmp_rpm(self): -- ''' -- Test package version is called RPM version if RPM-Python is installed -- -- :return: -- ''' -- with patch('salt.modules.zypper.rpm', MagicMock(return_value=MagicMock)): -- with patch('salt.modules.zypper.rpm.labelCompare', MagicMock(return_value=0)): -- self.assertEqual(0, zypper.version_cmp('1', '2')) # mock returns 0, which means RPM was called -- -- @patch('salt.modules.zypper.HAS_RPM', False) -- def test_version_cmp_fallback(self): -- ''' -- Test package version is called RPM version if RPM-Python is installed -- -- :return: -- ''' -- with patch('salt.modules.zypper.rpm', MagicMock(return_value=MagicMock)): -- with patch('salt.modules.zypper.rpm.labelCompare', MagicMock(return_value=0)): -- self.assertEqual(-1, zypper.version_cmp('1', '2')) # mock returns -1, a python implementation was called -- - def test_list_pkgs(self): - ''' - Test packages listing. --- -2.1.4 - diff --git a/0008-Cleaner-deprecation-process-with-decorators.patch b/0008-Cleaner-deprecation-process-with-decorators.patch deleted file mode 100644 index c189641..0000000 --- a/0008-Cleaner-deprecation-process-with-decorators.patch +++ /dev/null @@ -1,922 +0,0 @@ -From 2dcc979ab2897619baebfef5779120a98284d408 Mon Sep 17 00:00:00 2001 -From: Bo Maryniuk -Date: Wed, 6 Apr 2016 20:55:45 +0200 -Subject: [PATCH 08/12] Cleaner deprecation process with decorators - -* Add deprecation decorator scaffold - -* Capture type error and unhandled exceptions while function calls - -* Aware of the current and future version of deprecation - -* Implement initially is_deprecated decorator - -* Add an alias for the capitalization - -* Fix capitalization easier way - -* Remove an extra line - -* Add successor name to the deprecation decorator. - -* Granulate logging and error messages. - -* Implement function swapper - -* Raise later the caught exception - -* Clarify exception message - -* Save function original name - -* Remove an extra line - -* Hide an alternative hidden function name in the error message, preserving the error itself - -* Rename variable as private - -* Add a method to detect if a function is using its previous version - -* Message to the log and/or raise an exception accordingly to the status of used function - -* Log an error along with the exception - -* Add internal method documentation - -* Add documentation and usage process for decorator "is_deprecated" - -* Add documentation and process usage for the decorator "with_deprecated" - -* Hide private method name - -* Fix PEP8, re-word the error message - -* Deprecate basic uptime function - -* Add initial decorator unit test - -* Rename old/new functions, mock versions - -* Move frequent data to the test setup - -* Add logging on EOL exception - -* Rename and document high to low version test on is_deprecated - -* Implement a test on low to high version of is_deprecated decorator - -* Add a correction to the test description - -* Remove a dead code - -* Implement a test for high to low version on is_deprecated, using with_successor param - -* Correct typso adn mistaeks - -* Implement high to low version with successor param on is_deprecated - -* Setup a virtual name for the module - -* Implement test for with_deprecated should raise an exception if same deprecated function not found - -* Implement test for with_deprecated an old function is picked up if configured - -* Correct test description purpose - -* Implement test with_deprecated when no deprecation is requested - -* Add logging test to the configured deprecation request - -* Add logging testing when deprecated version wasn't requested - -* Implement test EOL for with_deprecated decorator - -* Correct test explanation - -* Rename the test - -* Implement with_deprecated no EOL, deprecated other function name - -* Implement with_deprecated, deprecated other function name, EOL reached - -* Add test description for the with_deprecated + with_name + EOL - -* Fix confusing test names - -* Add logging test to the is_deprecated decorator when function as not found. - -* Add more test point to each test, remove empty lines - -* Bugfix: at certain conditions a wrong alias name is reported to the log - -* Fix a typo in a comment - -* Add test for the logging - -* Disable a pylint: None will _never_ be raised - -* Fix test for the deprecated "status.uptime" version - -* Bugfix: Do not yank raised exceptions - -* Remove unnecessary decorator - -* Add test for the new uptime - -* Add test for the new uptime fails when /proc/uptime does not exists - -* Rename old test case - -* Skip test for the UTC time, unless freeze time is used. - -* Fix pylint - -* Fix documentation - -* Bugfix: proxy-pass the docstring of the decorated function - -* Lint fix ---- - salt/modules/status.py | 40 ++++- - salt/utils/decorators/__init__.py | 345 +++++++++++++++++++++++++++++++++++- - tests/unit/modules/status_test.py | 48 ++++- - tests/unit/utils/decorators_test.py | 232 ++++++++++++++++++++++++ - 4 files changed, 649 insertions(+), 16 deletions(-) - create mode 100644 tests/unit/utils/decorators_test.py - -diff --git a/salt/modules/status.py b/salt/modules/status.py -index 1e80b36..04c6204 100644 ---- a/salt/modules/status.py -+++ b/salt/modules/status.py -@@ -11,6 +11,8 @@ import os - import re - import fnmatch - import collections -+import time -+import datetime - - # Import 3rd-party libs - import salt.ext.six as six -@@ -23,6 +25,8 @@ import salt.utils.event - from salt.utils.network import host_to_ip as _host_to_ip - from salt.utils.network import remote_port_tcp as _remote_port_tcp - from salt.ext.six.moves import zip -+from salt.utils.decorators import with_deprecated -+from salt.exceptions import CommandExecutionError - - __virtualname__ = 'status' - __opts__ = {} -@@ -30,7 +34,8 @@ __opts__ = {} - - def __virtual__(): - if salt.utils.is_windows(): -- return (False, 'Cannot load status module on windows') -+ return False, 'Windows platform is not supported by this module' -+ - return __virtualname__ - - -@@ -120,7 +125,38 @@ def custom(): - return ret - - --def uptime(human_readable=True): -+@with_deprecated(globals(), "Boron") -+def uptime(): -+ ''' -+ Return the uptime for this system. -+ -+ CLI Example: -+ -+ .. code-block:: bash -+ -+ salt '*' status.uptime -+ ''' -+ ut_path = "/proc/uptime" -+ if not os.path.exists(ut_path): -+ raise CommandExecutionError("File {ut_path} was not found.".format(ut_path=ut_path)) -+ -+ ut_ret = { -+ 'seconds': int(float(open(ut_path).read().strip().split()[0])) -+ } -+ -+ utc_time = datetime.datetime.utcfromtimestamp(time.time() - ut_ret['seconds']) -+ ut_ret['since_iso'] = utc_time.isoformat() -+ ut_ret['since_t'] = time.mktime(utc_time.timetuple()) -+ ut_ret['days'] = ut_ret['seconds'] / 60 / 60 / 24 -+ hours = (ut_ret['seconds'] - (ut_ret['days'] * 24 * 60 * 60)) / 60 / 60 -+ minutes = ((ut_ret['seconds'] - (ut_ret['days'] * 24 * 60 * 60)) / 60) - hours * 60 -+ ut_ret['time'] = '{0}:{1}'.format(hours, minutes) -+ ut_ret['users'] = len(__salt__['cmd.run']("who -s").split(os.linesep)) -+ -+ return ut_ret -+ -+ -+def _uptime(human_readable=True): - ''' - Return the uptime for this minion - -diff --git a/salt/utils/decorators/__init__.py b/salt/utils/decorators/__init__.py -index 45d3bd6..3b43504 100644 ---- a/salt/utils/decorators/__init__.py -+++ b/salt/utils/decorators/__init__.py -@@ -13,7 +13,8 @@ from collections import defaultdict - - # Import salt libs - import salt.utils --from salt.exceptions import CommandNotFoundError -+from salt.exceptions import CommandNotFoundError, CommandExecutionError -+from salt.version import SaltStackVersion, __saltstack_version__ - from salt.log import LOG_LEVELS - - # Import 3rd-party libs -@@ -144,10 +145,7 @@ class Depends(object): - continue - - --class depends(Depends): # pylint: disable=C0103 -- ''' -- Wrapper of Depends for capitalization -- ''' -+depends = Depends - - - def timing(function): -@@ -248,3 +246,340 @@ def memoize(func): - cache[args] = func(*args) - return cache[args] - return _memoize -+ -+ -+class _DeprecationDecorator(object): -+ ''' -+ Base mix-in class for the deprecation decorator. -+ Takes care of a common functionality, used in its derivatives. -+ ''' -+ -+ def __init__(self, globals, version): -+ ''' -+ Constructor. -+ -+ :param globals: Module globals. Important for finding out replacement functions -+ :param version: Expiration version -+ :return: -+ ''' -+ -+ self._globals = globals -+ self._exp_version_name = version -+ self._exp_version = SaltStackVersion.from_name(self._exp_version_name) -+ self._curr_version = __saltstack_version__.info -+ self._options = self._globals['__opts__'] -+ self._raise_later = None -+ self._function = None -+ self._orig_f_name = None -+ -+ def _get_args(self, kwargs): -+ ''' -+ Extract function-specific keywords from all of the kwargs. -+ -+ :param kwargs: -+ :return: -+ ''' -+ _args = list() -+ _kwargs = dict() -+ -+ for arg_item in kwargs.get('__pub_arg', list()): -+ if type(arg_item) == dict: -+ _kwargs.update(arg_item.copy()) -+ else: -+ _args.append(arg_item) -+ return _args, _kwargs -+ -+ def _call_function(self, kwargs): -+ ''' -+ Call target function that has been decorated. -+ -+ :return: -+ ''' -+ if self._raise_later: -+ raise self._raise_later # pylint: disable=E0702 -+ -+ if self._function: -+ args, kwargs = self._get_args(kwargs) -+ try: -+ return self._function(*args, **kwargs) -+ except TypeError as error: -+ error = str(error).replace(self._function.func_name, self._orig_f_name) # Hide hidden functions -+ log.error('Function "{f_name}" was not properly called: {error}'.format(f_name=self._orig_f_name, -+ error=error)) -+ return self._function.__doc__ -+ except Exception as error: -+ log.error('Unhandled exception occurred in ' -+ 'function "{f_name}: {error}'.format(f_name=self._function.func_name, -+ error=error)) -+ raise error -+ else: -+ raise CommandExecutionError("Function is deprecated, but the successor function was not found.") -+ -+ def __call__(self, function): -+ ''' -+ Callable method of the decorator object when -+ the decorated function is gets called. -+ -+ :param function: -+ :return: -+ ''' -+ self._function = function -+ self._orig_f_name = self._function.func_name -+ -+ -+class _IsDeprecated(_DeprecationDecorator): -+ ''' -+ This decorator should be used only with the deprecated functions -+ to mark them as deprecated and alter its behavior a corresponding way. -+ The usage is only suitable if deprecation process is renaming -+ the function from one to another. In case function name or even function -+ signature stays the same, please use 'with_deprecated' decorator instead. -+ -+ It has the following functionality: -+ -+ 1. Put a warning level message to the log, informing that -+ the deprecated function has been in use. -+ -+ 2. Raise an exception, if deprecated function is being called, -+ but the lifetime of it already expired. -+ -+ 3. Point to the successor of the deprecated function in the -+ log messages as well during the blocking it, once expired. -+ -+ Usage of this decorator as follows. In this example no successor -+ is mentioned, hence the function "foo()" will be logged with the -+ warning each time is called and blocked completely, once EOF of -+ it is reached: -+ -+ from salt.util.decorators import is_deprecated -+ -+ @is_deprecated(globals(), "Beryllium") -+ def foo(): -+ pass -+ -+ In the following example a successor function is mentioned, hence -+ every time the function "bar()" is called, message will suggest -+ to use function "baz()" instead. Once EOF is reached of the function -+ "bar()", an exception will ask to use function "baz()", in order -+ to continue: -+ -+ from salt.util.decorators import is_deprecated -+ -+ @is_deprecated(globals(), "Beryllium", with_successor="baz") -+ def bar(): -+ pass -+ -+ def baz(): -+ pass -+ ''' -+ -+ def __init__(self, globals, version, with_successor=None): -+ ''' -+ Constructor of the decorator 'is_deprecated'. -+ -+ :param globals: Module globals -+ :param version: Version to be deprecated -+ :param with_successor: Successor function (optional) -+ :return: -+ ''' -+ _DeprecationDecorator.__init__(self, globals, version) -+ self._successor = with_successor -+ -+ def __call__(self, function): -+ ''' -+ Callable method of the decorator object when -+ the decorated function is gets called. -+ -+ :param function: -+ :return: -+ ''' -+ _DeprecationDecorator.__call__(self, function) -+ -+ def _decorate(*args, **kwargs): -+ ''' -+ Decorator function. -+ -+ :param args: -+ :param kwargs: -+ :return: -+ ''' -+ if self._curr_version < self._exp_version: -+ msg = ['The function "{f_name}" is deprecated and will ' -+ 'expire in version "{version_name}".'.format(f_name=self._function.func_name, -+ version_name=self._exp_version_name)] -+ if self._successor: -+ msg.append('Use successor "{successor}" instead.'.format(successor=self._successor)) -+ log.warning(' '.join(msg)) -+ else: -+ msg = ['The lifetime of the function "{f_name}" expired.'.format(f_name=self._function.func_name)] -+ if self._successor: -+ msg.append('Please use its successor "{successor}" instead.'.format(successor=self._successor)) -+ log.warning(' '.join(msg)) -+ raise CommandExecutionError(' '.join(msg)) -+ return self._call_function(kwargs) -+ return _decorate -+ -+ -+is_deprecated = _IsDeprecated -+ -+ -+class _WithDeprecated(_DeprecationDecorator): -+ ''' -+ This decorator should be used with the successor functions -+ to mark them as a new and alter its behavior in a corresponding way. -+ It is used alone if a function content or function signature -+ needs to be replaced, leaving the name of the function same. -+ In case function needs to be renamed or just dropped, it has -+ to be used in pair with 'is_deprecated' decorator. -+ -+ It has the following functionality: -+ -+ 1. Put a warning level message to the log, in case a component -+ is using its deprecated version. -+ -+ 2. Switch between old and new function in case an older version -+ is configured for the desired use. -+ -+ 3. Raise an exception, if deprecated version reached EOL and -+ point out for the new version. -+ -+ Usage of this decorator as follows. If 'with_name' is not specified, -+ then the name of the deprecated function is assumed with the "_" prefix. -+ In this case, in order to deprecate a function, it is required: -+ -+ - Add a prefix "_" to an existing function. E.g.: "foo()" to "_foo()". -+ -+ - Implement a new function with exactly the same name, just without -+ the prefix "_". -+ -+ Example: -+ -+ from salt.util.decorators import with_deprecated -+ -+ @with_deprecated(globals(), "Beryllium") -+ def foo(): -+ "This is a new function" -+ -+ def _foo(): -+ "This is a deprecated function" -+ -+ -+ In case there is a need to deprecate a function and rename it, -+ the decorator shuld be used with the 'with_name' parameter. This -+ parameter is pointing to the existing deprecated function. In this -+ case deprecation process as follows: -+ -+ - Leave a deprecated function without changes, as is. -+ -+ - Implement a new function and decorate it with this decorator. -+ -+ - Set a parameter 'with_name' to the deprecated function. -+ -+ - If a new function has a different name than a deprecated, -+ decorate a deprecated function with the 'is_deprecated' decorator -+ in order to let the function have a deprecated behavior. -+ -+ Example: -+ -+ from salt.util.decorators import with_deprecated -+ -+ @with_deprecated(globals(), "Beryllium", with_name="an_old_function") -+ def a_new_function(): -+ "This is a new function" -+ -+ @is_deprecated(globals(), "Beryllium", with_successor="a_new_function") -+ def an_old_function(): -+ "This is a deprecated function" -+ -+ ''' -+ MODULE_NAME = '__virtualname__' -+ CFG_KEY = 'use_deprecated' -+ -+ def __init__(self, globals, version, with_name=None): -+ ''' -+ Constructor of the decorator 'with_deprecated' -+ -+ :param globals: -+ :param version: -+ :param with_name: -+ :return: -+ ''' -+ _DeprecationDecorator.__init__(self, globals, version) -+ self._with_name = with_name -+ -+ def _set_function(self, function): -+ ''' -+ Based on the configuration, set to execute an old or a new function. -+ :return: -+ ''' -+ full_name = "{m_name}.{f_name}".format(m_name=self._globals.get(self.MODULE_NAME, ''), -+ f_name=function.func_name) -+ if full_name.startswith("."): -+ self._raise_later = CommandExecutionError('Module not found for function "{f_name}"'.format( -+ f_name=function.func_name)) -+ -+ if full_name in self._options.get(self.CFG_KEY, list()): -+ self._function = self._globals.get(self._with_name or "_{0}".format(function.func_name)) -+ -+ def _is_used_deprecated(self): -+ ''' -+ Returns True, if a component configuration explicitly is -+ asking to use an old version of the deprecated function. -+ -+ :return: -+ ''' -+ return "{m_name}.{f_name}".format(m_name=self._globals.get(self.MODULE_NAME, ''), -+ f_name=self._orig_f_name) in self._options.get(self.CFG_KEY, list()) -+ -+ def __call__(self, function): -+ ''' -+ Callable method of the decorator object when -+ the decorated function is gets called. -+ -+ :param function: -+ :return: -+ ''' -+ _DeprecationDecorator.__call__(self, function) -+ -+ def _decorate(*args, **kwargs): -+ ''' -+ Decorator function. -+ -+ :param args: -+ :param kwargs: -+ :return: -+ ''' -+ self._set_function(function) -+ if self._is_used_deprecated(): -+ if self._curr_version < self._exp_version: -+ msg = list() -+ if self._with_name: -+ msg.append('The function "{f_name}" is deprecated and will ' -+ 'expire in version "{version_name}".'.format( -+ f_name=self._with_name.startswith("_") and self._orig_f_name or self._with_name, -+ version_name=self._exp_version_name)) -+ else: -+ msg.append('The function is using its deprecated version and will ' -+ 'expire in version "{version_name}".'.format(version_name=self._exp_version_name)) -+ msg.append('Use its successor "{successor}" instead.'.format(successor=self._orig_f_name)) -+ log.warning(' '.join(msg)) -+ else: -+ msg_patt = 'The lifetime of the function "{f_name}" expired.' -+ if '_' + self._orig_f_name == self._function.func_name: -+ msg = [msg_patt.format(f_name=self._orig_f_name), -+ 'Please turn off its deprecated version in the configuration'] -+ else: -+ msg = ['Although function "{f_name}" is called, an alias "{f_alias}" ' -+ 'is configured as its deprecated version.'.format( -+ f_name=self._orig_f_name, f_alias=self._with_name or self._orig_f_name), -+ msg_patt.format(f_name=self._with_name or self._orig_f_name), -+ 'Please use its successor "{successor}" instead.'.format(successor=self._orig_f_name)] -+ log.error(' '.join(msg)) -+ raise CommandExecutionError(' '.join(msg)) -+ return self._call_function(kwargs) -+ -+ _decorate.__doc__ = self._function.__doc__ -+ return _decorate -+ -+ -+with_deprecated = _WithDeprecated -diff --git a/tests/unit/modules/status_test.py b/tests/unit/modules/status_test.py -index 191da09..b5cee4f 100644 ---- a/tests/unit/modules/status_test.py -+++ b/tests/unit/modules/status_test.py -@@ -5,15 +5,14 @@ from __future__ import absolute_import - - # Import Salt Libs - from salt.modules import status -+from salt.exceptions import CommandExecutionError - - # Import Salt Testing Libs --from salttesting import skipIf, TestCase -+from salttesting import TestCase - from salttesting.helpers import ensure_in_syspath - from salttesting.mock import ( - MagicMock, - patch, -- NO_MOCK, -- NO_MOCK_REASON - ) - - ensure_in_syspath('../../') -@@ -22,36 +21,67 @@ ensure_in_syspath('../../') - status.__salt__ = {} - - --@skipIf(NO_MOCK, NO_MOCK_REASON) - class StatusTestCase(TestCase): - ''' - test modules.status functions - ''' -+ - def test_uptime(self): - ''' -- test modules.status.uptime function -+ Test modules.status.uptime function, new version -+ :return: -+ ''' -+ class ProcUptime(object): -+ def __init__(self, *args, **kwargs): -+ self.data = "773865.18 1003405.46" -+ -+ def read(self): -+ return self.data -+ -+ with patch.dict(status.__salt__, {'cmd.run': MagicMock(return_value="1\n2\n3")}): -+ with patch('os.path.exists', MagicMock(return_value=True)): -+ with patch('time.time', MagicMock(return_value=1458821523.72)): -+ status.open = ProcUptime -+ u_time = status.uptime() -+ self.assertEqual(u_time['users'], 3) -+ self.assertEqual(u_time['seconds'], 773865) -+ self.assertEqual(u_time['days'], 8) -+ self.assertEqual(u_time['time'], '22:57') -+ -+ def test_uptime_failure(self): -+ ''' -+ Test modules.status.uptime function should raise an exception if /proc/uptime does not exists. -+ :return: -+ ''' -+ with patch('os.path.exists', MagicMock(return_value=False)): -+ with self.assertRaises(CommandExecutionError): -+ status.uptime() -+ -+ def test_deprecated_uptime(self): -+ ''' -+ test modules.status.uptime function, deprecated version - ''' - mock_uptime = 'very often' - mock_run = MagicMock(return_value=mock_uptime) - with patch.dict(status.__salt__, {'cmd.run': mock_run}): -- self.assertEqual(status.uptime(), mock_uptime) -+ self.assertEqual(status._uptime(), mock_uptime) - - mock_uptime = 'very idle' - mock_run = MagicMock(return_value=mock_uptime) - with patch.dict(status.__salt__, {'cmd.run': mock_run}): - with patch('os.path.exists', MagicMock(return_value=True)): -- self.assertEqual(status.uptime(human_readable=False), mock_uptime.split()[0]) -+ self.assertEqual(status._uptime(human_readable=False), mock_uptime.split()[0]) - - mock_uptime = '' - mock_return = 'unexpected format in /proc/uptime' - mock_run = MagicMock(return_value=mock_uptime) - with patch.dict(status.__salt__, {'cmd.run': mock_run}): - with patch('os.path.exists', MagicMock(return_value=True)): -- self.assertEqual(status.uptime(human_readable=False), mock_return) -+ self.assertEqual(status._uptime(human_readable=False), mock_return) - - mock_return = 'cannot find /proc/uptime' - with patch('os.path.exists', MagicMock(return_value=False)): -- self.assertEqual(status.uptime(human_readable=False), mock_return) -+ self.assertEqual(status._uptime(human_readable=False), mock_return) - - - if __name__ == '__main__': -diff --git a/tests/unit/utils/decorators_test.py b/tests/unit/utils/decorators_test.py -new file mode 100644 -index 0000000..4078340 ---- /dev/null -+++ b/tests/unit/utils/decorators_test.py -@@ -0,0 +1,232 @@ -+# -*- coding: utf-8 -*- -+''' -+ :codeauthor: :email:`Bo Maryniuk (bo@suse.de)` -+ unit.utils.decorators_test -+''' -+ -+# Import Python libs -+from __future__ import absolute_import -+ -+# Import Salt Testing libs -+from salttesting import TestCase -+from salttesting.helpers import ensure_in_syspath -+from salt.utils import decorators -+from salt.version import SaltStackVersion -+from salt.exceptions import CommandExecutionError -+ -+ensure_in_syspath('../../') -+ -+ -+class DummyLogger(object): -+ ''' -+ Dummy logger accepts everything and simply logs -+ ''' -+ def __init__(self, messages): -+ self._messages = messages -+ -+ def __getattr__(self, item): -+ return self._log -+ -+ def _log(self, msg): -+ self._messages.append(msg) -+ -+ -+class DecoratorsTest(TestCase): -+ ''' -+ Testing decorators. -+ ''' -+ def old_function(self): -+ return "old" -+ -+ def new_function(self): -+ return "new" -+ -+ def _mk_version(self, name): -+ ''' -+ Make a version -+ -+ :return: -+ ''' -+ return name, SaltStackVersion.from_name(name) -+ -+ def setUp(self): -+ ''' -+ Setup a test -+ :return: -+ ''' -+ self.globs = { -+ '__virtualname__': 'test', -+ '__opts__': {}, -+ 'old_function': self.old_function, -+ 'new_function': self.new_function, -+ } -+ self.messages = list() -+ decorators.log = DummyLogger(self.messages) -+ -+ def test_is_deprecated_version_eol(self): -+ ''' -+ Use of is_deprecated will result to the exception, -+ if the expiration version is lower than the current version. -+ A successor function is not pointed out. -+ -+ :return: -+ ''' -+ depr = decorators.is_deprecated(self.globs, "Helium") -+ depr._curr_version = self._mk_version("Beryllium")[1] -+ with self.assertRaises(CommandExecutionError): -+ depr(self.old_function)() -+ self.assertEqual(self.messages, -+ ['The lifetime of the function "old_function" expired.']) -+ -+ def test_is_deprecated_with_successor_eol(self): -+ ''' -+ Use of is_deprecated will result to the exception, -+ if the expiration version is lower than the current version. -+ A successor function is pointed out. -+ -+ :return: -+ ''' -+ depr = decorators.is_deprecated(self.globs, "Helium", with_successor="new_function") -+ depr._curr_version = self._mk_version("Beryllium")[1] -+ with self.assertRaises(CommandExecutionError): -+ depr(self.old_function)() -+ self.assertEqual(self.messages, -+ ['The lifetime of the function "old_function" expired. ' -+ 'Please use its successor "new_function" instead.']) -+ -+ def test_is_deprecated(self): -+ ''' -+ Use of is_deprecated will result to the log message, -+ if the expiration version is higher than the current version. -+ A successor function is not pointed out. -+ -+ :return: -+ ''' -+ depr = decorators.is_deprecated(self.globs, "Beryllium") -+ depr._curr_version = self._mk_version("Helium")[1] -+ self.assertEqual(depr(self.old_function)(), self.old_function()) -+ self.assertEqual(self.messages, -+ ['The function "old_function" is deprecated ' -+ 'and will expire in version "Beryllium".']) -+ -+ def test_is_deprecated_with_successor(self): -+ ''' -+ Use of is_deprecated will result to the log message, -+ if the expiration version is higher than the current version. -+ A successor function is pointed out. -+ -+ :return: -+ ''' -+ depr = decorators.is_deprecated(self.globs, "Beryllium", with_successor="old_function") -+ depr._curr_version = self._mk_version("Helium")[1] -+ self.assertEqual(depr(self.old_function)(), self.old_function()) -+ self.assertEqual(self.messages, -+ ['The function "old_function" is deprecated ' -+ 'and will expire in version "Beryllium". ' -+ 'Use successor "old_function" instead.']) -+ -+ def test_with_deprecated_notfound(self): -+ ''' -+ Test with_deprecated should raise an exception, if a same name -+ function with the "_" prefix not implemented. -+ -+ :return: -+ ''' -+ self.globs['__opts__']['use_deprecated'] = ['test.new_function'] -+ depr = decorators.with_deprecated(self.globs, "Beryllium") -+ depr._curr_version = self._mk_version("Helium")[1] -+ with self.assertRaises(CommandExecutionError): -+ depr(self.new_function)() -+ self.assertEqual(self.messages, -+ ['The function is using its deprecated version and will expire in version "Beryllium". ' -+ 'Use its successor "new_function" instead.']) -+ -+ def test_with_deprecated_found(self): -+ ''' -+ Test with_deprecated should not raise an exception, if a same name -+ function with the "_" prefix is implemented, but should use -+ an old version instead, if "use_deprecated" is requested. -+ -+ :return: -+ ''' -+ self.globs['__opts__']['use_deprecated'] = ['test.new_function'] -+ self.globs['_new_function'] = self.old_function -+ depr = decorators.with_deprecated(self.globs, "Beryllium") -+ depr._curr_version = self._mk_version("Helium")[1] -+ self.assertEqual(depr(self.new_function)(), self.old_function()) -+ log_msg = ['The function is using its deprecated version and will expire in version "Beryllium". ' -+ 'Use its successor "new_function" instead.'] -+ self.assertEqual(self.messages, log_msg) -+ -+ def test_with_deprecated_found_eol(self): -+ ''' -+ Test with_deprecated should raise an exception, if a same name -+ function with the "_" prefix is implemented, "use_deprecated" is requested -+ and EOL is reached. -+ -+ :return: -+ ''' -+ self.globs['__opts__']['use_deprecated'] = ['test.new_function'] -+ self.globs['_new_function'] = self.old_function -+ depr = decorators.with_deprecated(self.globs, "Helium") -+ depr._curr_version = self._mk_version("Beryllium")[1] -+ with self.assertRaises(CommandExecutionError): -+ depr(self.new_function)() -+ self.assertEqual(self.messages, -+ ['Although function "new_function" is called, an alias "new_function" ' -+ 'is configured as its deprecated version. The lifetime of the function ' -+ '"new_function" expired. Please use its successor "new_function" instead.']) -+ -+ def test_with_deprecated_no_conf(self): -+ ''' -+ Test with_deprecated should not raise an exception, if a same name -+ function with the "_" prefix is implemented, but should use -+ a new version instead, if "use_deprecated" is not requested. -+ -+ :return: -+ ''' -+ self.globs['_new_function'] = self.old_function -+ depr = decorators.with_deprecated(self.globs, "Beryllium") -+ depr._curr_version = self._mk_version("Helium")[1] -+ self.assertEqual(depr(self.new_function)(), self.new_function()) -+ self.assertFalse(self.messages) -+ -+ def test_with_deprecated_with_name(self): -+ ''' -+ Test with_deprecated should not raise an exception, if a different name -+ function is implemented and specified with the "with_name" parameter, -+ but should use an old version instead and log a warning log message. -+ -+ :return: -+ ''' -+ self.globs['__opts__']['use_deprecated'] = ['test.new_function'] -+ depr = decorators.with_deprecated(self.globs, "Beryllium", with_name="old_function") -+ depr._curr_version = self._mk_version("Helium")[1] -+ self.assertEqual(depr(self.new_function)(), self.old_function()) -+ self.assertEqual(self.messages, -+ ['The function "old_function" is deprecated and will expire in version "Beryllium". ' -+ 'Use its successor "new_function" instead.']) -+ -+ def test_with_deprecated_with_name_eol(self): -+ ''' -+ Test with_deprecated should raise an exception, if a different name -+ function is implemented and specified with the "with_name" parameter -+ and EOL is reached. -+ -+ :return: -+ ''' -+ self.globs['__opts__']['use_deprecated'] = ['test.new_function'] -+ depr = decorators.with_deprecated(self.globs, "Helium", with_name="old_function") -+ depr._curr_version = self._mk_version("Beryllium")[1] -+ with self.assertRaises(CommandExecutionError): -+ depr(self.new_function)() -+ self.assertEqual(self.messages, -+ ['Although function "new_function" is called, ' -+ 'an alias "old_function" is configured as its deprecated version. ' -+ 'The lifetime of the function "old_function" expired. ' -+ 'Please use its successor "new_function" instead.']) -+ -+ -+if __name__ == '__main__': -+ from integration import run_tests -+ run_tests(DecoratorsTest, needs_daemon=False) --- -2.1.4 - diff --git a/0008-Prevent-several-minion-processes-on-the-same-machine.patch b/0008-Prevent-several-minion-processes-on-the-same-machine.patch new file mode 100644 index 0000000..07a4506 --- /dev/null +++ b/0008-Prevent-several-minion-processes-on-the-same-machine.patch @@ -0,0 +1,25 @@ +From 46713681bda8e45667691fbda6efe808c81574b1 Mon Sep 17 00:00:00 2001 +From: Bo Maryniuk +Date: Mon, 23 May 2016 17:29:15 +0200 +Subject: [PATCH 8/8] Prevent several minion processes on the same machine + +--- + pkg/suse/salt-minion | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/pkg/suse/salt-minion b/pkg/suse/salt-minion +index c476fab..28cf32c 100644 +--- a/pkg/suse/salt-minion ++++ b/pkg/suse/salt-minion +@@ -55,7 +55,7 @@ RETVAL=0 + start() { + echo -n $"Starting salt-minion daemon: " + if [ -f $SUSE_RELEASE ]; then +- startproc -f -p /var/run/$SERVICE.pid $SALTMINION -d $MINION_ARGS ++ startproc -p /var/run/$SERVICE.pid $SALTMINION -d $MINION_ARGS + rc_status -v + elif [ -e $DEBIAN_VERSION ]; then + if [ -f $LOCKFILE ]; then +-- +2.8.3 + diff --git a/0009-checksum-validation-when-zypper-pkg.download.patch b/0009-checksum-validation-when-zypper-pkg.download.patch new file mode 100644 index 0000000..c8f8f63 --- /dev/null +++ b/0009-checksum-validation-when-zypper-pkg.download.patch @@ -0,0 +1,74 @@ +From d27af7dee61e83165bbd9adb9f0b6dc467907faa Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Tue, 24 May 2016 11:01:55 +0100 +Subject: [PATCH 09/11] checksum validation when zypper pkg.download + +check the signature of downloaded RPM files + +bugfix: showing errors when a package download fails using zypper pkg.download + +Renamed check_sig to checksum and some refactoring + +simpler rpm.checksum function +--- + salt/modules/rpm.py | 26 ++++++++++++++++++++++++++ + salt/modules/zypper.py | 6 +++++- + 2 files changed, 31 insertions(+), 1 deletion(-) + +diff --git a/salt/modules/rpm.py b/salt/modules/rpm.py +index 1469368..4991f24 100644 +--- a/salt/modules/rpm.py ++++ b/salt/modules/rpm.py +@@ -602,3 +602,29 @@ def version_cmp(ver1, ver2): + log.warning("Failed to compare version '{0}' to '{1}' using RPM: {2}".format(ver1, ver2, exc)) + + return salt.utils.version_cmp(ver1, ver2) ++ ++ ++def checksum(*paths): ++ ''' ++ Return if the signature of a RPM file is valid. ++ ++ CLI Example: ++ ++ .. code-block:: bash ++ ++ salt '*' lowpkg.checksum /path/to/package1.rpm ++ salt '*' lowpkg.checksum /path/to/package1.rpm /path/to/package2.rpm ++ ''' ++ ret = dict() ++ ++ if not paths: ++ raise CommandExecutionError("No package files has been specified.") ++ ++ for package_file in paths: ++ ret[package_file] = (bool(__salt__['file.file_exists'](package_file)) and ++ not __salt__['cmd.retcode'](["rpm", "-K", "--quiet", package_file], ++ ignore_retcode=True, ++ output_loglevel='trace', ++ python_shell=False)) ++ ++ return ret +diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py +index f9538e5..39b071b 100644 +--- a/salt/modules/zypper.py ++++ b/salt/modules/zypper.py +@@ -1534,9 +1534,13 @@ def download(*packages, **kwargs): + 'repository-alias': repo.getAttribute("alias"), + 'path': dld_result.getElementsByTagName("localfile")[0].getAttribute("path"), + } +- pkg_ret[_get_first_aggregate_text(dld_result.getElementsByTagName("name"))] = pkg_info ++ if __salt__['lowpkg.checksum'](pkg_info['path']): ++ pkg_ret[_get_first_aggregate_text(dld_result.getElementsByTagName("name"))] = pkg_info + + if pkg_ret: ++ failed = [pkg for pkg in packages if pkg not in pkg_ret] ++ if failed: ++ pkg_ret['_error'] = ('The following package(s) failed to download: {0}'.format(', '.join(failed))) + return pkg_ret + + raise CommandExecutionError("Unable to download packages: {0}.".format(', '.join(packages))) +-- +2.8.2 + diff --git a/0009-fix-sorting-by-latest-version-when-called-with-an-at.patch b/0009-fix-sorting-by-latest-version-when-called-with-an-at.patch deleted file mode 100644 index ef9298c..0000000 --- a/0009-fix-sorting-by-latest-version-when-called-with-an-at.patch +++ /dev/null @@ -1,48 +0,0 @@ -From cb588505919b6c74ed824d26a184eec0f47a585b Mon Sep 17 00:00:00 2001 -From: Michael Calmer -Date: Mon, 4 Apr 2016 09:49:31 +0200 -Subject: [PATCH 09/12] fix sorting by latest version when called with an - attribute - ---- - salt/modules/rpm.py | 7 ++++++- - 1 file changed, 6 insertions(+), 1 deletion(-) - -diff --git a/salt/modules/rpm.py b/salt/modules/rpm.py -index 6026f18..1469368 100644 ---- a/salt/modules/rpm.py -+++ b/salt/modules/rpm.py -@@ -471,6 +471,7 @@ def info(*packages, **attr): - "url": "%|URL?{url: %{URL}\\n}|", - "summary": "summary: %{SUMMARY}\\n", - "description": "description:\\n%{DESCRIPTION}\\n", -+ "edition": "edition: %|EPOCH?{%{EPOCH}:}|%{VERSION}-%{RELEASE}\\n", - } - - attr = attr.get('attr', None) and attr['attr'].split(",") or None -@@ -484,6 +485,9 @@ def info(*packages, **attr): - if 'name' not in attr: - attr.append('name') - query.append(attr_map['name']) -+ if 'edition' not in attr: -+ attr.append('edition') -+ query.append(attr_map['edition']) - else: - for attr_k, attr_v in attr_map.iteritems(): - if attr_k != 'description': -@@ -558,10 +562,11 @@ def info(*packages, **attr): - # pick only latest versions - # (in case multiple packages installed, e.g. kernel) - ret = dict() -- for pkg_data in reversed(sorted(_ret, cmp=lambda a_vrs, b_vrs: version_cmp(a_vrs['version'], b_vrs['version']))): -+ for pkg_data in reversed(sorted(_ret, cmp=lambda a_vrs, b_vrs: version_cmp(a_vrs['edition'], b_vrs['edition']))): - pkg_name = pkg_data.pop('name') - if pkg_name not in ret: - ret[pkg_name] = pkg_data.copy() -+ del ret[pkg_name]['edition'] - - return ret - --- -2.1.4 - diff --git a/0010-Prevent-metadata-download-when-getting-installed-pro.patch b/0010-Prevent-metadata-download-when-getting-installed-pro.patch deleted file mode 100644 index 822eae6..0000000 --- a/0010-Prevent-metadata-download-when-getting-installed-pro.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 336929a4cadca55b00dbf1cd33eb35d19f420c73 Mon Sep 17 00:00:00 2001 -From: Michael Calmer -Date: Tue, 5 Apr 2016 12:06:29 +0200 -Subject: [PATCH 10/12] Prevent metadata download when getting installed - products - ---- - salt/modules/zypper.py | 5 ++++- - 1 file changed, 4 insertions(+), 1 deletion(-) - -diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py -index 63c473c..9702f42 100644 ---- a/salt/modules/zypper.py -+++ b/salt/modules/zypper.py -@@ -1309,7 +1309,10 @@ def list_products(all=False, refresh=False): - - ret = list() - OEM_PATH = "/var/lib/suseRegister/OEM" -- cmd = _zypper('-x', 'products') -+ cmd = _zypper() -+ if not all: -+ cmd.append('--disable-repos') -+ cmd.extend(['-x', 'products']) - if not all: - cmd.append('-i') - --- -2.1.4 - diff --git a/0010-unit-tests-for-rpm.checksum-and-zypper.download.patch b/0010-unit-tests-for-rpm.checksum-and-zypper.download.patch new file mode 100644 index 0000000..9a3461e --- /dev/null +++ b/0010-unit-tests-for-rpm.checksum-and-zypper.download.patch @@ -0,0 +1,105 @@ +From 2742ee76ccc50cd4f84e44861ef82ec5f3b5234a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Wed, 25 May 2016 17:08:16 +0100 +Subject: [PATCH 10/11] unit tests for rpm.checksum() and zypper.download() + +lint issue fixed +--- + tests/unit/modules/rpm_test.py | 16 ++++++++++++++++ + tests/unit/modules/zypp/zypper-download.xml | 19 +++++++++++++++++++ + tests/unit/modules/zypper_test.py | 25 +++++++++++++++++++++++++ + 3 files changed, 60 insertions(+) + create mode 100644 tests/unit/modules/zypp/zypper-download.xml + +diff --git a/tests/unit/modules/rpm_test.py b/tests/unit/modules/rpm_test.py +index f180736..4042137 100644 +--- a/tests/unit/modules/rpm_test.py ++++ b/tests/unit/modules/rpm_test.py +@@ -95,6 +95,22 @@ class RpmTestCase(TestCase): + self.assertDictEqual(rpm.owner('/usr/bin/python', '/usr/bin/vim'), + ret) + ++ # 'checksum' function tests: 1 ++ ++ def test_checksum(self): ++ ''' ++ Test if checksum validate as expected ++ ''' ++ ret = { ++ "file1.rpm": True, ++ "file2.rpm": False, ++ "file3.rpm": False, ++ } ++ ++ mock = MagicMock(side_effect=[True, 0, True, 1, False, 0]) ++ with patch.dict(rpm.__salt__, {'file.file_exists': mock, 'cmd.retcode': mock}): ++ self.assertDictEqual(rpm.checksum("file1.rpm", "file2.rpm", "file3.rpm"), ret) ++ + @patch('salt.modules.rpm.HAS_RPM', True) + def test_version_cmp_rpm(self): + ''' +diff --git a/tests/unit/modules/zypp/zypper-download.xml b/tests/unit/modules/zypp/zypper-download.xml +new file mode 100644 +index 0000000..eeea0a5 +--- /dev/null ++++ b/tests/unit/modules/zypp/zypper-download.xml +@@ -0,0 +1,19 @@ ++ ++ ++ Loading repository data... ++ Reading installed packages... ++ Argument resolves to no package: foo ++ ++ ++ ++ package ++ nmap ++ ++ x86_64 ++ ++ ++ ++ ++ ++ download: Done. ++ +diff --git a/tests/unit/modules/zypper_test.py b/tests/unit/modules/zypper_test.py +index 4e735cd..9ec2b83 100644 +--- a/tests/unit/modules/zypper_test.py ++++ b/tests/unit/modules/zypper_test.py +@@ -354,6 +354,31 @@ class ZypperTestCase(TestCase): + self.assertTrue(pkgs.get(pkg_name)) + self.assertEqual(pkgs[pkg_name], pkg_version) + ++ def test_download(self): ++ ''' ++ Test package download ++ :return: ++ ''' ++ download_out = { ++ 'stdout': get_test_data('zypper-download.xml'), ++ 'stderr': None, ++ 'retcode': 0 ++ } ++ ++ test_out = { ++ 'nmap': { ++ 'path': u'/var/cache/zypp/packages/SLE-12-x86_64-Pool/x86_64/nmap-6.46-1.72.x86_64.rpm', ++ 'repository-alias': u'SLE-12-x86_64-Pool', ++ 'repository-name': u'SLE-12-x86_64-Pool' ++ } ++ } ++ ++ with patch.dict(zypper.__salt__, {'cmd.run_all': MagicMock(return_value=download_out)}): ++ with patch.dict(zypper.__salt__, {'lowpkg.checksum': MagicMock(return_value=True)}): ++ self.assertEqual(zypper.download("nmap"), test_out) ++ test_out['_error'] = "The following package(s) failed to download: foo" ++ self.assertEqual(zypper.download("nmap", "foo"), test_out) ++ + def test_remove_purge(self): + ''' + Test package removal +-- +2.8.2 + diff --git a/0011-Check-if-EOL-is-available-in-a-particular-product-bs.patch b/0011-Check-if-EOL-is-available-in-a-particular-product-bs.patch deleted file mode 100644 index 22a598f..0000000 --- a/0011-Check-if-EOL-is-available-in-a-particular-product-bs.patch +++ /dev/null @@ -1,146 +0,0 @@ -From aae1c09957eab3c89a6c8f78a579cdf9dcfbe188 Mon Sep 17 00:00:00 2001 -From: Bo Maryniuk -Date: Tue, 12 Apr 2016 13:52:35 +0200 -Subject: [PATCH 11/12] Check if EOL is available in a particular product - (bsc#975093) - -Update SLE11 SP3 data - -Update SLE12 SP1 data - -Adjust test values according to the testing data ---- - salt/modules/zypper.py | 13 +++++++-- - .../unit/modules/zypp/zypper-products-sle11sp3.xml | 10 +++++++ - .../unit/modules/zypp/zypper-products-sle12sp1.xml | 8 ++++++ - tests/unit/modules/zypper_test.py | 32 ++++++++++++---------- - 4 files changed, 45 insertions(+), 18 deletions(-) - -diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py -index 9702f42..4ce5853 100644 ---- a/salt/modules/zypper.py -+++ b/salt/modules/zypper.py -@@ -1318,12 +1318,19 @@ def list_products(all=False, refresh=False): - - call = __salt__['cmd.run_all'](cmd, output_loglevel='trace') - doc = dom.parseString(_zypper_check_result(call, xml=True)) -- for prd in doc.getElementsByTagName('product-list')[0].getElementsByTagName('product'): -+ product_list = doc.getElementsByTagName('product-list') -+ if not product_list: -+ return ret # No products found -+ -+ for prd in product_list[0].getElementsByTagName('product'): - p_nfo = dict() - for k_p_nfo, v_p_nfo in prd.attributes.items(): - p_nfo[k_p_nfo] = k_p_nfo not in ['isbase', 'installed'] and v_p_nfo or v_p_nfo in ['true', '1'] -- p_nfo['eol'] = prd.getElementsByTagName('endoflife')[0].getAttribute('text') -- p_nfo['eol_t'] = int(prd.getElementsByTagName('endoflife')[0].getAttribute('time_t')) -+ -+ eol = prd.getElementsByTagName('endoflife') -+ if eol: -+ p_nfo['eol'] = eol[0].getAttribute('text') -+ p_nfo['eol_t'] = int(eol[0].getAttribute('time_t') or 0) - p_nfo['description'] = " ".join( - [line.strip() for line in _get_first_aggregate_text( - prd.getElementsByTagName('description') -diff --git a/tests/unit/modules/zypp/zypper-products-sle11sp3.xml b/tests/unit/modules/zypp/zypper-products-sle11sp3.xml -index 89a85e3..99444fe 100644 ---- a/tests/unit/modules/zypp/zypper-products-sle11sp3.xml -+++ b/tests/unit/modules/zypp/zypper-products-sle11sp3.xml -@@ -31,7 +31,17 @@ - offers common management tools and technology - certifications across the platform, and - each product is enterprise-class. -+0x7ffdb538e948SUSE Linux Enterprise offers a comprehensive -+ suite of products built on a single code base. -+ The platform addresses business needs from -+ the smallest thin-client devices to the world’s -+ most powerful high-performance computing -+ and mainframe servers. SUSE Linux Enterprise -+ offers common management tools and technology -+ certifications across the platform, and -+ each product is enterprise-class. - 0x7ffdb538e948SUSE Manager Server appliance - 0x7ffdb538e948SUSE Manager Server appliance -+0x7ffdb538e948SUSE Manager Server appliance - - -diff --git a/tests/unit/modules/zypp/zypper-products-sle12sp1.xml b/tests/unit/modules/zypp/zypper-products-sle12sp1.xml -index 1a50363..a086058 100644 ---- a/tests/unit/modules/zypp/zypper-products-sle12sp1.xml -+++ b/tests/unit/modules/zypp/zypper-products-sle12sp1.xml -@@ -24,6 +24,14 @@ provisioning. - SUSE Manager Tools provide packages required to connect to a - SUSE Manager Server. - <p> -+extension<p> -+ SUSE Manager Tools provide packages required to connect to a -+ SUSE Manager Server. -+ <p> -+extension<p> -+ SUSE Manager Tools provide packages required to connect to a -+ SUSE Manager Server. -+ <p> - SUSE Linux Enterprise offers a comprehensive - suite of products built on a single code base. - The platform addresses business needs from -diff --git a/tests/unit/modules/zypper_test.py b/tests/unit/modules/zypper_test.py -index 67cf52a..97e42ef 100644 ---- a/tests/unit/modules/zypper_test.py -+++ b/tests/unit/modules/zypper_test.py -@@ -153,24 +153,26 @@ class ZypperTestCase(TestCase): - for filename, test_data in { - 'zypper-products-sle12sp1.xml': { - 'name': ['SLES', 'SLES', 'SUSE-Manager-Proxy', -- 'SUSE-Manager-Server', 'sle-manager-tools-beta'], -+ 'SUSE-Manager-Server', 'sle-manager-tools-beta', -+ 'sle-manager-tools-beta-broken-eol', 'sle-manager-tools-beta-no-eol'], - 'vendor': 'SUSE LLC ', -- 'release': ['0', '0', '0', '0', '0'], -- 'productline': [False, False, False, False, 'sles'], -- 'eol_t': [1509408000, 1522454400, 1522454400, 1730332800, 1730332800], -- 'isbase': [False, False, False, False, True], -- 'installed': [False, False, False, False, True], -+ 'release': ['0', '0', '0', '0', '0', '0', '0'], -+ 'productline': [False, False, False, False, False, False, 'sles'], -+ 'eol_t': [None, 0, 1509408000, 1522454400, 1522454400, 1730332800, 1730332800], -+ 'isbase': [False, False, False, False, False, False, True], -+ 'installed': [False, False, False, False, False, False, True], - }, - 'zypper-products-sle11sp3.xml': { -- 'name': ['SUSE-Manager-Server', 'SUSE-Manager-Server', -- 'SUSE_SLES', 'SUSE_SLES', 'SUSE_SLES-SP4-migration'], -+ 'name': ['SUSE-Manager-Server', 'SUSE-Manager-Server', 'SUSE-Manager-Server-Broken-EOL', -+ 'SUSE_SLES', 'SUSE_SLES', 'SUSE_SLES', 'SUSE_SLES-SP4-migration'], - 'vendor': 'SUSE LINUX Products GmbH, Nuernberg, Germany', -- 'release': ['1.138', '1.2', '1.2', '1.201', '1.4'], -- 'productline': [False, False, False, False, 'manager'], -- 'eol_t': [0, 0, 0, 0, 0], -- 'isbase': [False, False, False, False, True], -- 'installed': [False, False, False, False, True], -+ 'release': ['1.138', '1.2', '1.2', '1.2', '1.201', '1.201', '1.4'], -+ 'productline': [False, False, False, False, False, 'manager', 'manager'], -+ 'eol_t': [None, 0, 0, 0, 0, 0, 0], -+ 'isbase': [False, False, False, False, False, True, True], -+ 'installed': [False, False, False, False, False, True, True], - }}.items(): -+ - ref_out = { - 'retcode': 0, - 'stdout': get_test_data(filename) -@@ -178,10 +180,10 @@ class ZypperTestCase(TestCase): - - with patch.dict(zypper.__salt__, {'cmd.run_all': MagicMock(return_value=ref_out)}): - products = zypper.list_products() -- self.assertEqual(len(products), 5) -+ self.assertEqual(len(products), 7) - self.assertIn(test_data['vendor'], [product['vendor'] for product in products]) - for kwd in ['name', 'isbase', 'installed', 'release', 'productline', 'eol_t']: -- self.assertEqual(test_data[kwd], sorted([prod[kwd] for prod in products])) -+ self.assertEqual(test_data[kwd], sorted([prod.get(kwd) for prod in products])) - - def test_refresh_db(self): - ''' --- -2.1.4 - diff --git a/0011-jobs.exit_success-allow-to-check-if-a-job-has-execut.patch b/0011-jobs.exit_success-allow-to-check-if-a-job-has-execut.patch new file mode 100644 index 0000000..2614e9f --- /dev/null +++ b/0011-jobs.exit_success-allow-to-check-if-a-job-has-execut.patch @@ -0,0 +1,53 @@ +From e0f15c5292869549b5c80997ccb3282961be8e49 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Tue, 24 May 2016 09:21:43 +0100 +Subject: [PATCH 11/11] jobs.exit_success allow to check if a job has executed + and exit successfully + +jobs.exit_success() now works parsing the results of jobs.lookup_id() +--- + salt/runners/jobs.py | 27 +++++++++++++++++++++++++++ + 1 file changed, 27 insertions(+) + +diff --git a/salt/runners/jobs.py b/salt/runners/jobs.py +index 57d0324..e2b8737 100644 +--- a/salt/runners/jobs.py ++++ b/salt/runners/jobs.py +@@ -488,6 +488,33 @@ def print_job(jid, ext_source=None, outputter=None): + return ret + + ++def exit_success(jid, ext_source=None): ++ ''' ++ Check if a job has been executed and exit successfully ++ ++ jid ++ The jid to look up. ++ ext_source ++ The external job cache to use. Default: `None`. ++ ++ CLI Example: ++ .. code-block:: bash ++ salt-run jobs.exit_success 20160520145827701627 ++ ''' ++ ret = dict() ++ ++ data = lookup_jid( ++ jid, ++ ext_source=ext_source ++ ) ++ ++ for minion in data: ++ if "retcode" in data[minion]: ++ ret[minion] = True if not data[minion]['retcode'] else False ++ ++ return ret ++ ++ + def last_run(ext_source=None, + outputter=None, + metadata=None, +-- +2.8.2 + diff --git a/0012-Bugfix-salt-key-crashes-if-tries-to-generate-keys-to.patch b/0012-Bugfix-salt-key-crashes-if-tries-to-generate-keys-to.patch deleted file mode 100644 index 89c2824..0000000 --- a/0012-Bugfix-salt-key-crashes-if-tries-to-generate-keys-to.patch +++ /dev/null @@ -1,69 +0,0 @@ -From 5e99ee2bec1139b1944284975454c716d477f3e0 Mon Sep 17 00:00:00 2001 -From: Bo Maryniuk -Date: Wed, 13 Apr 2016 16:15:37 +0200 -Subject: [PATCH 12/12] Bugfix: salt-key crashes if tries to generate keys to - the directory w/o write access (#32436) - -* Raise an exception if keys are tried to be written to the directory that has no write access permissions - -* Show an reasonable error message instead of a traceback crash. - -* Fix the unit tests ---- - salt/crypt.py | 6 ++++++ - salt/scripts.py | 2 ++ - tests/unit/crypt_test.py | 1 + - 3 files changed, 9 insertions(+) - -diff --git a/salt/crypt.py b/salt/crypt.py -index 573a3c1..e5f3317 100644 ---- a/salt/crypt.py -+++ b/salt/crypt.py -@@ -15,6 +15,7 @@ import logging - import traceback - import binascii - import weakref -+import getpass - from salt.ext.six.moves import zip # pylint: disable=import-error,redefined-builtin - - # Import third party libs -@@ -94,6 +95,11 @@ def gen_keys(keydir, keyname, keysize, user=None): - # Between first checking and the generation another process has made - # a key! Use the winner's key - return priv -+ -+ # Do not try writing anything, if directory has no permissions. -+ if not os.access(keydir, os.W_OK): -+ raise IOError('Write access denied to "{0}" for user "{1}".'.format(os.path.abspath(keydir), getpass.getuser())) -+ - cumask = os.umask(191) - with salt.utils.fopen(priv, 'wb+') as f: - f.write(gen.exportKey('PEM')) -diff --git a/salt/scripts.py b/salt/scripts.py -index 7da79bf..38b100d 100644 ---- a/salt/scripts.py -+++ b/salt/scripts.py -@@ -297,6 +297,8 @@ def salt_key(): - SystemExit('\nExiting gracefully on Ctrl-c'), - err, - hardcrash, trace=trace) -+ except Exception as err: -+ sys.stderr.write("Error: {0}\n".format(err.message)) - - - def salt_cp(): -diff --git a/tests/unit/crypt_test.py b/tests/unit/crypt_test.py -index 3ff3b09..f548820 100644 ---- a/tests/unit/crypt_test.py -+++ b/tests/unit/crypt_test.py -@@ -86,6 +86,7 @@ class CryptTestCase(TestCase): - @patch('os.umask', MagicMock()) - @patch('os.chmod', MagicMock()) - @patch('os.chown', MagicMock()) -+ @patch('os.access', MagicMock(return_value=True)) - def test_gen_keys(self): - with patch('salt.utils.fopen', mock_open()): - open_priv_wb = call('/keydir/keyname.pem', 'wb+') --- -2.1.4 - diff --git a/0012-Fix-pkgrepo.managed-gpgkey-argument-bsc-979448.patch b/0012-Fix-pkgrepo.managed-gpgkey-argument-bsc-979448.patch new file mode 100644 index 0000000..760aef7 --- /dev/null +++ b/0012-Fix-pkgrepo.managed-gpgkey-argument-bsc-979448.patch @@ -0,0 +1,325 @@ +From b0e1ba5158cc4b54102bac200ae343935eeb2db5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Mihai=20Dinc=C4=83?= +Date: Mon, 23 May 2016 23:15:29 +0200 +Subject: [PATCH 12/12] Fix pkgrepo.managed gpgkey argument (bsc#979448) + +* Call zypper refresh after adding/modifying a repository + +* Calling `zypper --gpg-auto-import-keys refresh` is required after +adding/modifying a repository because `--gpg-auto-import-keys` doesn't +do anything when called with `zypper ar` or `zypper mr`. +Without calling `zypper --gpg-auto-import-keys refresh` here, calling +`zypper ref` after adding/removing would still ask for +accepting/rejecting the gpg key. + +* Update test method names to pass pylint + +* Reduce dicts and lists to one line where possible + +* Reverse if conditions and rename variable + +* Assert only gpgautoimport: True works + +* Improve zypper_patcher_config looks + +* DRY test +--- + salt/modules/zypper.py | 24 +++-- + tests/unit/modules/zypper_test.py | 213 ++++++++++++++++++++++++++++++++++++++ + 2 files changed, 229 insertions(+), 8 deletions(-) + +diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py +index 39b071b..2c72448 100644 +--- a/salt/modules/zypper.py ++++ b/salt/modules/zypper.py +@@ -767,6 +767,8 @@ def mod_repo(repo, **kwargs): + + # Modify added or existing repo according to the options + cmd_opt = [] ++ global_cmd_opt = [] ++ call_refresh = False + + if 'enabled' in kwargs: + cmd_opt.append(kwargs['enabled'] and '--enable' or '--disable') +@@ -780,21 +782,27 @@ def mod_repo(repo, **kwargs): + if 'gpgcheck' in kwargs: + cmd_opt.append(kwargs['gpgcheck'] and '--gpgcheck' or '--no-gpgcheck') + +- if kwargs.get('gpgautoimport') is True: +- cmd_opt.append('--gpg-auto-import-keys') +- + if 'priority' in kwargs: + cmd_opt.append("--priority={0}".format(kwargs.get('priority', DEFAULT_PRIORITY))) + + if 'humanname' in kwargs: + cmd_opt.append("--name='{0}'".format(kwargs.get('humanname'))) + +- if cmd_opt: +- cmd_opt.append(repo) +- __zypper__.refreshable.xml.call('mr', *cmd_opt) ++ if kwargs.get('gpgautoimport') is True: ++ global_cmd_opt.append('--gpg-auto-import-keys') ++ call_refresh = True + +- # If repo nor added neither modified, error should be thrown +- if not added and not cmd_opt: ++ if cmd_opt: ++ cmd_opt = global_cmd_opt + ['mr'] + cmd_opt + [repo] ++ __zypper__.refreshable.xml.call(*cmd_opt) ++ ++ if call_refresh: ++ # when used with "zypper ar --refresh" or "zypper mr --refresh" ++ # --gpg-auto-import-keys is not doing anything ++ # so we need to specifically refresh here with --gpg-auto-import-keys ++ refresh_opts = global_cmd_opt + ['refresh'] + [repo] ++ __zypper__.xml.call(*refresh_opts) ++ elif not added and not cmd_opt: + raise CommandExecutionError( + 'Specified arguments did not result in modification of repo' + ) +diff --git a/tests/unit/modules/zypper_test.py b/tests/unit/modules/zypper_test.py +index 9ec2b83..c4f7597 100644 +--- a/tests/unit/modules/zypper_test.py ++++ b/tests/unit/modules/zypper_test.py +@@ -9,7 +9,9 @@ from __future__ import absolute_import + # Import Salt Testing Libs + from salttesting import TestCase, skipIf + from salttesting.mock import ( ++ Mock, + MagicMock, ++ call, + patch, + NO_MOCK, + NO_MOCK_REASON +@@ -54,10 +56,26 @@ zypper.rpm = None + + @skipIf(NO_MOCK, NO_MOCK_REASON) + class ZypperTestCase(TestCase): ++ + ''' + Test cases for salt.modules.zypper + ''' + ++ def setUp(self): ++ self.new_repo_config = dict( ++ name='mock-repo-name', ++ url='http://repo.url/some/path' ++ ) ++ side_effect = [ ++ Mock(**{'sections.return_value': []}), ++ Mock(**{'sections.return_value': [self.new_repo_config['name']]}) ++ ] ++ self.zypper_patcher_config = { ++ '_get_configured_repos': Mock(side_effect=side_effect), ++ '__zypper__': Mock(), ++ 'get_repo': Mock() ++ } ++ + def test_list_upgrades(self): + ''' + List package upgrades +@@ -438,6 +456,201 @@ class ZypperTestCase(TestCase): + self.assertEqual(r_info['enabled'], alias == 'SLE12-SP1-x86_64-Update') + self.assertEqual(r_info['autorefresh'], alias == 'SLE12-SP1-x86_64-Update') + ++ def test_repo_add_nomod_noref(self): ++ ''' ++ Test mod_repo adds the new repo and nothing else ++ ++ :return: ++ ''' ++ zypper_patcher = patch.multiple( ++ 'salt.modules.zypper', **self.zypper_patcher_config) ++ ++ url = self.new_repo_config['url'] ++ name = self.new_repo_config['name'] ++ with zypper_patcher: ++ zypper.mod_repo(name, **{'url': url}) ++ self.assertEqual( ++ zypper.__zypper__.xml.call.call_args_list, ++ [call('ar', url, name)] ++ ) ++ zypper.__zypper__.refreshable.xml.call.assert_not_called() ++ ++ def test_repo_noadd_nomod_noref(self): ++ ''' ++ Test mod_repo detects the repo already exists, ++ no modification was requested and no refresh requested either ++ ++ :return: ++ ''' ++ url = self.new_repo_config['url'] ++ name = self.new_repo_config['name'] ++ self.zypper_patcher_config['_get_configured_repos'] = Mock( ++ **{'return_value.sections.return_value': [name]} ++ ) ++ zypper_patcher = patch.multiple( ++ 'salt.modules.zypper', **self.zypper_patcher_config) ++ ++ with zypper_patcher: ++ with self.assertRaisesRegexp( ++ Exception, ++ 'Specified arguments did not result in modification of repo' ++ ): ++ zypper.mod_repo(name, **{'url': url}) ++ with self.assertRaisesRegexp( ++ Exception, ++ 'Specified arguments did not result in modification of repo' ++ ): ++ zypper.mod_repo(name, **{'url': url, 'gpgautoimport': 'a'}) ++ ++ zypper.__zypper__.xml.call.assert_not_called() ++ zypper.__zypper__.refreshable.xml.call.assert_not_called() ++ ++ def test_repo_add_mod_noref(self): ++ ''' ++ Test mod_repo adds the new repo and call modify to update autorefresh ++ ++ :return: ++ ''' ++ zypper_patcher = patch.multiple( ++ 'salt.modules.zypper', **self.zypper_patcher_config) ++ ++ url = self.new_repo_config['url'] ++ name = self.new_repo_config['name'] ++ with zypper_patcher: ++ zypper.mod_repo(name, **{'url': url, 'refresh': True}) ++ self.assertEqual( ++ zypper.__zypper__.xml.call.call_args_list, ++ [call('ar', url, name)] ++ ) ++ zypper.__zypper__.refreshable.xml.call.assert_called_once_with( ++ 'mr', '--refresh', name ++ ) ++ ++ def test_repo_noadd_mod_noref(self): ++ ''' ++ Test mod_repo detects the repository exists, ++ calls modify to update 'autorefresh' but does not call refresh ++ ++ :return: ++ ''' ++ url = self.new_repo_config['url'] ++ name = self.new_repo_config['name'] ++ self.zypper_patcher_config['_get_configured_repos'] = Mock( ++ **{'return_value.sections.return_value': [name]}) ++ zypper_patcher = patch.multiple( ++ 'salt.modules.zypper', **self.zypper_patcher_config) ++ with zypper_patcher: ++ zypper.mod_repo(name, **{'url': url, 'refresh': True}) ++ zypper.__zypper__.xml.call.assert_not_called() ++ zypper.__zypper__.refreshable.xml.call.assert_called_once_with( ++ 'mr', '--refresh', name ++ ) ++ ++ def test_repo_add_nomod_ref(self): ++ ''' ++ Test mod_repo adds the new repo and refreshes the repo with ++ `zypper --gpg-auto-import-keys refresh ` ++ ++ :return: ++ ''' ++ zypper_patcher = patch.multiple( ++ 'salt.modules.zypper', **self.zypper_patcher_config) ++ ++ url = self.new_repo_config['url'] ++ name = self.new_repo_config['name'] ++ with zypper_patcher: ++ zypper.mod_repo(name, **{'url': url, 'gpgautoimport': True}) ++ self.assertEqual( ++ zypper.__zypper__.xml.call.call_args_list, ++ [ ++ call('ar', url, name), ++ call('--gpg-auto-import-keys', 'refresh', name) ++ ] ++ ) ++ zypper.__zypper__.refreshable.xml.call.assert_not_called() ++ ++ def test_repo_noadd_nomod_ref(self): ++ ''' ++ Test mod_repo detects the repo already exists, ++ has nothing to modify and refreshes the repo with ++ `zypper --gpg-auto-import-keys refresh ` ++ ++ :return: ++ ''' ++ url = self.new_repo_config['url'] ++ name = self.new_repo_config['name'] ++ self.zypper_patcher_config['_get_configured_repos'] = Mock( ++ **{'return_value.sections.return_value': [name]} ++ ) ++ zypper_patcher = patch.multiple( ++ 'salt.modules.zypper', **self.zypper_patcher_config) ++ ++ with zypper_patcher: ++ zypper.mod_repo(name, **{'url': url, 'gpgautoimport': True}) ++ self.assertEqual( ++ zypper.__zypper__.xml.call.call_args_list, ++ [call('--gpg-auto-import-keys', 'refresh', name)] ++ ) ++ zypper.__zypper__.refreshable.xml.call.assert_not_called() ++ ++ def test_repo_add_mod_ref(self): ++ ''' ++ Test mod_repo adds the new repo, ++ calls modify to update 'autorefresh' and refreshes the repo with ++ `zypper --gpg-auto-import-keys refresh ` ++ ++ :return: ++ ''' ++ zypper_patcher = patch.multiple( ++ 'salt.modules.zypper', **self.zypper_patcher_config) ++ ++ url = self.new_repo_config['url'] ++ name = self.new_repo_config['name'] ++ with zypper_patcher: ++ zypper.mod_repo( ++ name, ++ **{'url': url, 'refresh': True, 'gpgautoimport': True} ++ ) ++ self.assertEqual( ++ zypper.__zypper__.xml.call.call_args_list, ++ [ ++ call('ar', url, name), ++ call('--gpg-auto-import-keys', 'refresh', name) ++ ] ++ ) ++ zypper.__zypper__.refreshable.xml.call.assert_called_once_with( ++ '--gpg-auto-import-keys', 'mr', '--refresh', name ++ ) ++ ++ def test_repo_noadd_mod_ref(self): ++ ''' ++ Test mod_repo detects the repo already exists, ++ calls modify to update 'autorefresh' and refreshes the repo with ++ `zypper --gpg-auto-import-keys refresh ` ++ ++ :return: ++ ''' ++ url = self.new_repo_config['url'] ++ name = self.new_repo_config['name'] ++ self.zypper_patcher_config['_get_configured_repos'] = Mock( ++ **{'return_value.sections.return_value': [name]} ++ ) ++ zypper_patcher = patch.multiple( ++ 'salt.modules.zypper', **self.zypper_patcher_config) ++ ++ with zypper_patcher: ++ zypper.mod_repo( ++ name, ++ **{'url': url, 'refresh': True, 'gpgautoimport': True} ++ ) ++ self.assertEqual( ++ zypper.__zypper__.xml.call.call_args_list, ++ [call('--gpg-auto-import-keys', 'refresh', name)] ++ ) ++ zypper.__zypper__.refreshable.xml.call.assert_called_once_with( ++ '--gpg-auto-import-keys', 'mr', '--refresh', name ++ ) ++ + if __name__ == '__main__': + from integration import run_tests + run_tests(ZypperTestCase, needs_daemon=False) +-- +2.8.3 + diff --git a/0013-Prevent-crash-if-pygit2-package-is-requesting-re-com.patch b/0013-Prevent-crash-if-pygit2-package-is-requesting-re-com.patch deleted file mode 100644 index 6b78d19..0000000 --- a/0013-Prevent-crash-if-pygit2-package-is-requesting-re-com.patch +++ /dev/null @@ -1,86 +0,0 @@ -From f187ee058eb221eb5a34d51ca5db53bb8eeea5e1 Mon Sep 17 00:00:00 2001 -From: Bo Maryniuk -Date: Mon, 18 Apr 2016 16:25:05 +0200 -Subject: [PATCH 13/14] Prevent crash if pygit2 package is requesting - re-compilation - -* Prevent crash if pygit2 package is requesting re-compilation of the entire library on production systems (no *devel packages) - -* Fix PEP8: move imports to the top of the file - -* Move logger up - -* Add log error message in case if exception is not an ImportError ---- - salt/utils/gitfs.py | 33 ++++++++++++++++++++------------- - 1 file changed, 20 insertions(+), 13 deletions(-) - -diff --git a/salt/utils/gitfs.py b/salt/utils/gitfs.py -index 164c92e..5452c28 100644 ---- a/salt/utils/gitfs.py -+++ b/salt/utils/gitfs.py -@@ -19,6 +19,18 @@ import subprocess - import time - from datetime import datetime - -+# Import salt libs -+import salt.utils -+import salt.utils.itertools -+import salt.utils.url -+import salt.fileserver -+from salt.utils.process import os_is_running as pid_exists -+from salt.exceptions import FileserverConfigError, GitLockError -+from salt.utils.event import tagify -+ -+# Import third party libs -+import salt.ext.six as six -+ - VALID_PROVIDERS = ('gitpython', 'pygit2', 'dulwich') - # Optional per-remote params that can only be used on a per-remote basis, and - # thus do not have defaults in salt/config.py. -@@ -54,16 +66,8 @@ _INVALID_REPO = ( - 'master to continue to use this {2} remote.' - ) - --# Import salt libs --import salt.utils --import salt.utils.itertools --import salt.utils.url --import salt.fileserver --from salt.exceptions import FileserverConfigError, GitLockError --from salt.utils.event import tagify -+log = logging.getLogger(__name__) - --# Import third party libs --import salt.ext.six as six - # pylint: disable=import-error - try: - import git -@@ -79,8 +83,13 @@ try: - GitError = pygit2.errors.GitError - except AttributeError: - GitError = Exception --except ImportError: -- HAS_PYGIT2 = False -+except Exception as err: # cffi VerificationError also may happen -+ HAS_PYGIT2 = False # and pygit2 requrests re-compilation -+ # on a production system (!), -+ # but cffi might be absent as well! -+ # Therefore just a generic Exception class. -+ if not isinstance(err, ImportError): -+ log.error('Import pygit2 failed: {0}'.format(err)) - - try: - import dulwich.errors -@@ -93,8 +102,6 @@ except ImportError: - HAS_DULWICH = False - # pylint: enable=import-error - --log = logging.getLogger(__name__) -- - # Minimum versions for backend providers - GITPYTHON_MINVER = '0.3' - PYGIT2_MINVER = '0.20.3' --- -2.8.1 - diff --git a/0014-align-OS-grains-from-older-SLES-with-current-one-326.patch b/0014-align-OS-grains-from-older-SLES-with-current-one-326.patch deleted file mode 100644 index f438967..0000000 --- a/0014-align-OS-grains-from-older-SLES-with-current-one-326.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0961f5bd3e3b7aa3ebd75fe064044d078df62724 Mon Sep 17 00:00:00 2001 -From: Michael Calmer -Date: Mon, 18 Apr 2016 16:31:58 +0200 -Subject: [PATCH 14/14] align OS grains from older SLES with current one - (#32649) - ---- - salt/grains/core.py | 9 +++++++-- - 1 file changed, 7 insertions(+), 2 deletions(-) - -diff --git a/salt/grains/core.py b/salt/grains/core.py -index eb62b97..d5dbef8 100644 ---- a/salt/grains/core.py -+++ b/salt/grains/core.py -@@ -1184,14 +1184,19 @@ def os_data(): - for line in fhr: - if 'enterprise' in line.lower(): - grains['lsb_distrib_id'] = 'SLES' -+ grains['lsb_distrib_codename'] = re.sub(r'\(.+\)', '', line).strip() - elif 'version' in line.lower(): - version = re.sub(r'[^0-9]', '', line) - elif 'patchlevel' in line.lower(): - patch = re.sub(r'[^0-9]', '', line) - grains['lsb_distrib_release'] = version - if patch: -- grains['lsb_distrib_release'] += ' SP' + patch -- grains['lsb_distrib_codename'] = 'n.a' -+ grains['lsb_distrib_release'] += '.' + patch -+ patchstr = 'SP' + patch -+ if grains['lsb_distrib_codename'] and patchstr not in grains['lsb_distrib_codename']: -+ grains['lsb_distrib_codename'] += ' ' + patchstr -+ if not grains['lsb_distrib_codename']: -+ grains['lsb_distrib_codename'] = 'n.a' - elif os.path.isfile('/etc/altlinux-release'): - # ALT Linux - grains['lsb_distrib_id'] = 'altlinux' --- -2.8.1 - diff --git a/0015-Unblock-Zypper.-Modify-environment.patch b/0015-Unblock-Zypper.-Modify-environment.patch deleted file mode 100644 index 93e59c8..0000000 --- a/0015-Unblock-Zypper.-Modify-environment.patch +++ /dev/null @@ -1,904 +0,0 @@ -From e52c7926a699bdee3fad2767c8aa755ee115c5d7 Mon Sep 17 00:00:00 2001 -From: Bo Maryniuk -Date: Fri, 22 Apr 2016 14:59:14 +0200 -Subject: [PATCH 15/15] Unblock Zypper. Modify environment. - -* Bugfix: version_cmp crashes in CLI if there are versions, that looks like integer or float. - -* Standarize zypper call to "run_all" - -* Remove verbose wrapping - -* Remove an empty line - -* Remove an unused variable - -* Remove one-char variables - -* Implement block-proof Zypper call implementation - -* Remove blocking-prone Zypper call implementation - -* Use new Zypper call implementation - -* Fire an event to the Master about blocked Zypper. - -* Add Zypper lock constant - -* Check if zypper lock exists and add more debug logging - -* Replace string values with the constants - -* Fire an event about released Zypper with its result - -* Bugfix: accept refresh override param - -* Update docstrings according to the bugfix - -* Make Zypper caller module-level reusable - -* Bugfix: inverted logic on raising (or not) exceptions - -* Add Zypper Call mock - -* Remove an obsolete test case - -* Fix tests according to the new calling model - -* Bugfix: always trigger __getattr__ to reset and increment the configuration before the call. - -* Add Zypper caller test suite - -* Parse DOM out of the box, when XML mode is called - -* Add exception handling test - -* Test DOM parsing - -* Rename tags - -* Fix PID file path for SLE11 - -* Move log message down to the point where it actually sleeps. Rephrase the message. - -* Remove unused variable in a constructor. Adjust the docstring accordingly. - -* Prevent the use of "refreshable" together with "nolock" option. ---- - salt/modules/zypper.py | 403 +++++++++++++++++++++++++------------- - tests/unit/modules/zypper_test.py | 133 +++++++------ - 2 files changed, 345 insertions(+), 191 deletions(-) - -diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py -index 4ce5853..53b5d9f 100644 ---- a/salt/modules/zypper.py -+++ b/salt/modules/zypper.py -@@ -11,10 +11,13 @@ import copy - import logging - import re - import os -+import time -+import datetime - - # Import 3rd-party libs - # pylint: disable=import-error,redefined-builtin,no-name-in-module - import salt.ext.six as six -+import salt.utils.event - from salt.ext.six.moves import configparser - from salt.ext.six.moves.urllib.parse import urlparse as _urlparse - # pylint: enable=import-error,redefined-builtin,no-name-in-module -@@ -51,65 +54,226 @@ def __virtual__(): - return __virtualname__ - - --def _zypper(*opts): -- ''' -- Return zypper command with default options as a list. -- -- opts -- additional options for zypper command -- -- ''' -- cmd = ['zypper', '--non-interactive'] -- cmd.extend(opts) -- -- return cmd -- -- --def _is_zypper_error(retcode): -- ''' -- Return True in case the exist code indicate a zypper errror. -- Otherwise False -- ''' -- # see man zypper for existing exit codes -- return int(retcode) not in [0, 100, 101, 102, 103] -+class _Zypper(object): -+ ''' -+ Zypper parallel caller. -+ Validates the result and either raises an exception or reports an error. -+ Allows serial zypper calls (first came, first won). -+ ''' -+ -+ SUCCESS_EXIT_CODES = [0, 100, 101, 102, 103] -+ LOCK_EXIT_CODE = 7 -+ XML_DIRECTIVES = ['-x', '--xmlout'] -+ ZYPPER_LOCK = '/var/run/zypp.pid' -+ TAG_RELEASED = 'zypper/released' -+ TAG_BLOCKED = 'zypper/blocked' -+ -+ def __init__(self): -+ ''' -+ Constructor -+ ''' -+ self.__called = False -+ self._reset() -+ -+ def _reset(self): -+ ''' -+ Resets values of the call setup. -+ -+ :return: -+ ''' -+ self.__cmd = ['zypper', '--non-interactive'] -+ self.__exit_code = 0 -+ self.__call_result = dict() -+ self.__error_msg = '' -+ self.__env = {'SALT_RUNNING': "1"} # Subject to change -+ -+ # Call config -+ self.__xml = False -+ self.__no_lock = False -+ self.__no_raise = False -+ self.__refresh = False -+ -+ def __getattr__(self, item): -+ ''' -+ Call configurator. -+ -+ :param item: -+ :return: -+ ''' -+ # Reset after the call -+ if self.__called: -+ self._reset() -+ self.__called = False -+ -+ if item == 'xml': -+ self.__xml = True -+ elif item == 'nolock': -+ self.__no_lock = True -+ elif item == 'noraise': -+ self.__no_raise = True -+ elif item == 'refreshable': -+ self.__refresh = True -+ elif item == 'call': -+ return self.__call -+ else: -+ return self.__dict__[item] -+ -+ # Prevent the use of "refreshable" together with "nolock". -+ if self.__no_lock: -+ self.__no_lock = not self.__refresh -+ -+ return self -+ -+ @property -+ def exit_code(self): -+ return self.__exit_code -+ -+ @exit_code.setter -+ def exit_code(self, exit_code): -+ self.__exit_code = int(exit_code or '0') -+ -+ @property -+ def error_msg(self): -+ return self.__error_msg -+ -+ @error_msg.setter -+ def error_msg(self, msg): -+ if self._is_error(): -+ self.__error_msg = msg and os.linesep.join(msg) or "Check Zypper's logs." -+ -+ def stdout(self): -+ return self.__call_result.get('stdout', '') -+ -+ def stderr(self): -+ return self.__call_result.get('stderr', '') -+ -+ def _is_error(self): -+ ''' -+ Is this is an error code? -+ -+ :return: -+ ''' -+ return self.exit_code not in self.SUCCESS_EXIT_CODES -+ -+ def _is_lock(self): -+ ''' -+ Is this is a lock error code? -+ -+ :return: -+ ''' -+ return self.exit_code == self.LOCK_EXIT_CODE -+ -+ def _is_xml_mode(self): -+ ''' -+ Is Zypper's output is in XML format? -+ -+ :return: -+ ''' -+ return [itm for itm in self.XML_DIRECTIVES if itm in self.__cmd] and True or False -+ -+ def _check_result(self): -+ ''' -+ Check and set the result of a zypper command. In case of an error, -+ either raise a CommandExecutionError or extract the error. -+ -+ result -+ The result of a zypper command called with cmd.run_all -+ ''' -+ if not self.__call_result: -+ raise CommandExecutionError('No output result from Zypper?') -+ -+ self.exit_code = self.__call_result['retcode'] -+ if self._is_lock(): -+ return False -+ -+ if self._is_error(): -+ _error_msg = list() -+ if not self._is_xml_mode(): -+ msg = self.__call_result['stderr'] and self.__call_result['stderr'].strip() or "" -+ if msg: -+ _error_msg.append(msg) -+ else: -+ try: -+ doc = dom.parseString(self.__call_result['stdout']) -+ except ExpatError as err: -+ log.error(err) -+ doc = None -+ if doc: -+ msg_nodes = doc.getElementsByTagName('message') -+ for node in msg_nodes: -+ if node.getAttribute('type') == 'error': -+ _error_msg.append(node.childNodes[0].nodeValue) -+ elif self.__call_result['stderr'].strip(): -+ _error_msg.append(self.__call_result['stderr'].strip()) -+ self.error_msg = _error_msg -+ return True -+ -+ def __call(self, *args, **kwargs): -+ ''' -+ Call Zypper. -+ -+ :param state: -+ :return: -+ ''' -+ self.__called = True -+ if self.__xml: -+ self.__cmd.append('--xmlout') -+ if not self.__refresh: -+ self.__cmd.append('--no-refresh') -+ -+ self.__cmd.extend(args) -+ kwargs['output_loglevel'] = 'trace' -+ kwargs['python_shell'] = False -+ kwargs['env'] = self.__env.copy() -+ if self.__no_lock: -+ kwargs['env']['ZYPP_READONLY_HACK'] = "1" # Disables locking for read-only operations. Do not try that at home! -+ -+ # Zypper call will stuck here waiting, if another zypper hangs until forever. -+ # However, Zypper lock needs to be always respected. -+ was_blocked = False -+ while True: -+ log.debug("Calling Zypper: " + ' '.join(self.__cmd)) -+ self.__call_result = __salt__['cmd.run_all'](self.__cmd, **kwargs) -+ if self._check_result(): -+ break -+ -+ if os.path.exists(self.ZYPPER_LOCK): -+ try: -+ data = __salt__['ps.proc_info'](int(open(self.ZYPPER_LOCK).readline()), -+ attrs=['pid', 'name', 'cmdline', 'create_time']) -+ data['cmdline'] = ' '.join(data['cmdline']) -+ data['info'] = 'Blocking process created at {0}.'.format( -+ datetime.datetime.utcfromtimestamp(data['create_time']).isoformat()) -+ data['success'] = True -+ except Exception as err: -+ data = {'info': 'Unable to retrieve information about blocking process: {0}'.format(err.message), -+ 'success': False} -+ else: -+ data = {'info': 'Zypper is locked, but no Zypper lock has been found.', 'success': False} - -+ if not data['success']: -+ log.debug("Unable to collect data about blocking process.") -+ else: -+ log.debug("Collected data about blocking process.") - --def _zypper_check_result(result, xml=False): -- ''' -- Check the result of a zypper command. In case of an error, it raise -- a CommandExecutionError. Otherwise it returns stdout string of the -- command. -+ __salt__['event.fire_master'](data, self.TAG_BLOCKED) -+ log.debug("Fired a Zypper blocked event to the master with the data: {0}".format(str(data))) -+ log.debug("Waiting 5 seconds for Zypper gets released...") -+ time.sleep(5) -+ if not was_blocked: -+ was_blocked = True - -- result -- The result of a zypper command called with cmd.run_all -+ if was_blocked: -+ __salt__['event.fire_master']({'success': not len(self.error_msg), -+ 'info': self.error_msg or 'Zypper has been released'}, -+ self.TAG_RELEASED) -+ if self.error_msg and not self.__no_raise: -+ raise CommandExecutionError('Zypper command failure: {0}'.format(self.error_msg)) - -- xml -- Set to True if zypper command was called with --xmlout. -- In this case it try to read an error message out of the XML -- stream. Default is False. -- ''' -- if _is_zypper_error(result['retcode']): -- msg = list() -- if not xml: -- msg.append(result['stderr'] and result['stderr'] or "") -- else: -- try: -- doc = dom.parseString(result['stdout']) -- except ExpatError as err: -- log.error(err) -- doc = None -- if doc: -- msg_nodes = doc.getElementsByTagName('message') -- for node in msg_nodes: -- if node.getAttribute('type') == 'error': -- msg.append(node.childNodes[0].nodeValue) -- elif result['stderr'].strip(): -- msg.append(result['stderr'].strip()) -+ return self._is_xml_mode() and dom.parseString(self.__call_result['stdout']) or self.__call_result['stdout'] - -- raise CommandExecutionError("zypper command failed: {0}".format( -- msg and os.linesep.join(msg) or "Check zypper logs")) - -- return result['stdout'] -+__zypper__ = _Zypper() - - - def list_upgrades(refresh=True): -@@ -129,10 +293,9 @@ def list_upgrades(refresh=True): - ''' - if refresh: - refresh_db() -+ - ret = dict() -- run_data = __salt__['cmd.run_all'](_zypper('-x', 'list-updates'), output_loglevel='trace') -- doc = dom.parseString(_zypper_check_result(run_data, xml=True)) -- for update_node in doc.getElementsByTagName('update'): -+ for update_node in __zypper__.nolock.xml.call('list-updates').getElementsByTagName('update'): - if update_node.getAttribute('kind') == 'package': - ret[update_node.getAttribute('name')] = update_node.getAttribute('edition') - -@@ -191,7 +354,6 @@ def info_installed(*names, **kwargs): - t_nfo['source'] = value - else: - t_nfo[key] = value -- - ret[pkg_name] = t_nfo - - return ret -@@ -230,8 +392,8 @@ def info_available(*names, **kwargs): - - # Run in batches - while batch: -- cmd = _zypper('info', '-t', 'package', *batch[:batch_size]) -- pkg_info.extend(re.split(r"Information for package*", __salt__['cmd.run_stdout'](cmd, output_loglevel='trace'))) -+ pkg_info.extend(re.split(r"Information for package*", -+ __zypper__.nolock.call('info', '-t', 'package', *batch[:batch_size]))) - batch = batch[batch_size:] - - for pkg_data in pkg_info: -@@ -280,6 +442,11 @@ def latest_version(*names, **kwargs): - If the latest version of a given package is already installed, an empty - dict will be returned for that package. - -+ refresh -+ force a refresh if set to True (default). -+ If set to False it depends on zypper if a refresh is -+ executed or not. -+ - CLI example: - - .. code-block:: bash -@@ -293,7 +460,7 @@ def latest_version(*names, **kwargs): - return ret - - names = sorted(list(set(names))) -- package_info = info_available(*names) -+ package_info = info_available(*names, **kwargs) - for name in names: - pkg_info = package_info.get(name, {}) - status = pkg_info.get('status', '').lower() -@@ -311,10 +478,15 @@ def latest_version(*names, **kwargs): - available_version = salt.utils.alias_function(latest_version, 'available_version') - - --def upgrade_available(name): -+def upgrade_available(name, **kwargs): - ''' - Check whether or not an upgrade is available for a given package - -+ refresh -+ force a refresh if set to True (default). -+ If set to False it depends on zypper if a refresh is -+ executed or not. -+ - CLI Example: - - .. code-block:: bash -@@ -322,7 +494,7 @@ def upgrade_available(name): - salt '*' pkg.upgrade_available - ''' - # The "not not" tactic is intended here as it forces the return to be False. -- return not not latest_version(name) # pylint: disable=C0113 -+ return not not latest_version(name, **kwargs) # pylint: disable=C0113 - - - def version(*names, **kwargs): -@@ -355,7 +527,7 @@ def version_cmp(ver1, ver2): - - salt '*' pkg.version_cmp '0.2-001' '0.2.0.1-002' - ''' -- return __salt__['lowpkg.version_cmp'](ver1, ver2) -+ return __salt__['lowpkg.version_cmp'](str(ver1), str(ver2)) - - - def list_pkgs(versions_as_list=False, **kwargs): -@@ -398,12 +570,7 @@ def list_pkgs(versions_as_list=False, **kwargs): - - cmd = ['rpm', '-qa', '--queryformat', '%{NAME}_|-%{VERSION}_|-%{RELEASE}_|-%|EPOCH?{%{EPOCH}}:{}|\\n'] - ret = {} -- out = __salt__['cmd.run']( -- cmd, -- output_loglevel='trace', -- python_shell=False -- ) -- for line in out.splitlines(): -+ for line in __salt__['cmd.run'](cmd, output_loglevel='trace', python_shell=False).splitlines(): - name, pkgver, rel, epoch = line.split('_|-') - if epoch: - pkgver = '{0}:{1}'.format(epoch, pkgver) -@@ -415,6 +582,7 @@ def list_pkgs(versions_as_list=False, **kwargs): - __context__['pkg.list_pkgs'] = copy.deepcopy(ret) - if not versions_as_list: - __salt__['pkg_resource.stringify'](ret) -+ - return ret - - -@@ -434,15 +602,13 @@ def _get_repo_info(alias, repos_cfg=None): - Get one repo meta-data. - ''' - try: -- meta = dict((repos_cfg or _get_configured_repos()).items(alias)) -- meta['alias'] = alias -- for key, val in six.iteritems(meta): -- if val in ['0', '1']: -- meta[key] = int(meta[key]) == 1 -- elif val == 'NONE': -- meta[key] = None -- return meta -- except (ValueError, configparser.NoSectionError) as error: -+ ret = dict((repos_cfg or _get_configured_repos()).items(alias)) -+ ret['alias'] = alias -+ for key, val in six.iteritems(ret): -+ if val == 'NONE': -+ ret[key] = None -+ return ret -+ except (ValueError, configparser.NoSectionError): - return {} - - -@@ -490,9 +656,7 @@ def del_repo(repo): - repos_cfg = _get_configured_repos() - for alias in repos_cfg.sections(): - if alias == repo: -- cmd = _zypper('-x', 'rr', '--loose-auth', '--loose-query', alias) -- ret = __salt__['cmd.run_all'](cmd, output_loglevel='trace') -- doc = dom.parseString(_zypper_check_result(ret, xml=True)) -+ doc = __zypper__.xml.call('rr', '--loose-auth', '--loose-query', alias) - msg = doc.getElementsByTagName('message') - if doc.getElementsByTagName('progress') and msg: - return { -@@ -576,8 +740,7 @@ def mod_repo(repo, **kwargs): - 'Repository \'{0}\' already exists as \'{1}\'.'.format(repo, alias)) - - # Add new repo -- _zypper_check_result(__salt__['cmd.run_all'](_zypper('-x', 'ar', url, repo), -- output_loglevel='trace'), xml=True) -+ __zypper__.xml.call('ar', url, repo) - - # Verify the repository has been added - repos_cfg = _get_configured_repos() -@@ -613,9 +776,7 @@ def mod_repo(repo, **kwargs): - - if cmd_opt: - cmd_opt.append(repo) -- ret = __salt__['cmd.run_all'](_zypper('-x', 'mr', *cmd_opt), -- output_loglevel='trace') -- _zypper_check_result(ret, xml=True) -+ __zypper__.refreshable.xml.call('mr', *cmd_opt) - - # If repo nor added neither modified, error should be thrown - if not added and not cmd_opt: -@@ -637,9 +798,8 @@ def refresh_db(): - - salt '*' pkg.refresh_db - ''' -- cmd = _zypper('refresh', '--force') - ret = {} -- out = _zypper_check_result(__salt__['cmd.run_all'](cmd, output_loglevel='trace')) -+ out = __zypper__.refreshable.call('refresh', '--force') - - for line in out.splitlines(): - if not line: -@@ -779,8 +939,7 @@ def install(name=None, - log.info('Targeting repo {0!r}'.format(fromrepo)) - else: - fromrepoopt = '' -- cmd_install = _zypper() -- cmd_install += ['install', '--name', '--auto-agree-with-licenses'] -+ cmd_install = ['install', '--name', '--auto-agree-with-licenses'] - if downloadonly: - cmd_install.append('--download-only') - if fromrepo: -@@ -790,9 +949,7 @@ def install(name=None, - while targets: - cmd = cmd_install + targets[:500] - targets = targets[500:] -- call = __salt__['cmd.run_all'](cmd, output_loglevel='trace', python_shell=False) -- out = _zypper_check_result(call) -- for line in out.splitlines(): -+ for line in __zypper__.call(*cmd).splitlines(): - match = re.match(r"^The selected package '([^']+)'.+has lower version", line) - if match: - downgrades.append(match.group(1)) -@@ -800,8 +957,7 @@ def install(name=None, - while downgrades: - cmd = cmd_install + ['--force'] + downgrades[:500] - downgrades = downgrades[500:] -- -- _zypper_check_result(__salt__['cmd.run_all'](cmd, output_loglevel='trace', python_shell=False)) -+ __zypper__.call(*cmd) - - __context__.pop('pkg.list_pkgs', None) - new = list_pkgs() -@@ -837,18 +993,15 @@ def upgrade(refresh=True): - if refresh: - refresh_db() - old = list_pkgs() -- cmd = _zypper('update', '--auto-agree-with-licenses') -- call = __salt__['cmd.run_all'](cmd, output_loglevel='trace') -- if _is_zypper_error(call['retcode']): -+ __zypper__.noraise.call('update', '--auto-agree-with-licenses') -+ if __zypper__.exit_code not in __zypper__.SUCCESS_EXIT_CODES: - ret['result'] = False -- if 'stderr' in call: -- ret['comment'] += call['stderr'] -- if 'stdout' in call: -- ret['comment'] += call['stdout'] -+ ret['comment'] = (__zypper__.stdout() + os.linesep + __zypper__.stderr()).strip() - else: - __context__.pop('pkg.list_pkgs', None) - new = list_pkgs() - ret['changes'] = salt.utils.compare_dicts(old, new) -+ - return ret - - -@@ -868,8 +1021,7 @@ def _uninstall(name=None, pkgs=None): - return {} - - while targets: -- _zypper_check_result(__salt__['cmd.run_all'](_zypper('remove', *targets[:500]), -- output_loglevel='trace')) -+ __zypper__.call('remove', *targets[:500]) - targets = targets[500:] - __context__.pop('pkg.list_pkgs', None) - -@@ -982,9 +1134,7 @@ def clean_locks(): - if not os.path.exists("/etc/zypp/locks"): - return out - -- ret = __salt__['cmd.run_all'](_zypper('-x', 'cl'), output_loglevel='trace') -- doc = dom.parseString(_zypper_check_result(ret, xml=True)) -- for node in doc.getElementsByTagName("message"): -+ for node in __zypper__.xml.call('cl').getElementsByTagName("message"): - text = node.childNodes[0].nodeValue.lower() - if text.startswith(LCK): - out[LCK] = text.split(" ")[1] -@@ -1021,8 +1171,7 @@ def remove_lock(packages, **kwargs): # pylint: disable=unused-argument - missing.append(pkg) - - if removed: -- _zypper_check_result(__salt__['cmd.run_all'](_zypper('rl', *removed), -- output_loglevel='trace')) -+ __zypper__.call('rl', *removed) - - return {'removed': len(removed), 'not_found': missing} - -@@ -1051,8 +1200,7 @@ def add_lock(packages, **kwargs): # pylint: disable=unused-argument - added.append(pkg) - - if added: -- _zypper_check_result(__salt__['cmd.run_all'](_zypper('al', *added), -- output_loglevel='trace')) -+ __zypper__.call('al', *added) - - return {'added': len(added), 'packages': added} - -@@ -1185,10 +1333,7 @@ def _get_patterns(installed_only=None): - ''' - patterns = {} - -- ret = __salt__['cmd.run_all'](_zypper('--xmlout', 'se', '-t', 'pattern'), -- output_loglevel='trace') -- doc = dom.parseString(_zypper_check_result(ret, xml=True)) -- for element in doc.getElementsByTagName('solvable'): -+ for element in __zypper__.nolock.xml.call('se', '-t', 'pattern').getElementsByTagName('solvable'): - installed = element.getAttribute('status') == 'installed' - if (installed_only and installed) or not installed_only: - patterns[element.getAttribute('name')] = { -@@ -1251,20 +1396,16 @@ def search(criteria, refresh=False): - if refresh: - refresh_db() - -- ret = __salt__['cmd.run_all'](_zypper('--xmlout', 'se', criteria), -- output_loglevel='trace') -- doc = dom.parseString(_zypper_check_result(ret, xml=True)) -- solvables = doc.getElementsByTagName('solvable') -+ solvables = __zypper__.nolock.xml.call('se', criteria).getElementsByTagName('solvable') - if not solvables: - raise CommandExecutionError('No packages found by criteria "{0}".'.format(criteria)) - - out = {} -- for solvable in [s for s in solvables -- if s.getAttribute('status') == 'not-installed' and -- s.getAttribute('kind') == 'package']: -- out[solvable.getAttribute('name')] = { -- 'summary': solvable.getAttribute('summary') -- } -+ for solvable in [slv for slv in solvables -+ if slv.getAttribute('status') == 'not-installed' -+ and slv.getAttribute('kind') == 'package']: -+ out[solvable.getAttribute('name')] = {'summary': solvable.getAttribute('summary')} -+ - return out - - -@@ -1309,16 +1450,14 @@ def list_products(all=False, refresh=False): - - ret = list() - OEM_PATH = "/var/lib/suseRegister/OEM" -- cmd = _zypper() -+ cmd = list() - if not all: - cmd.append('--disable-repos') -- cmd.extend(['-x', 'products']) -+ cmd.append('products') - if not all: - cmd.append('-i') - -- call = __salt__['cmd.run_all'](cmd, output_loglevel='trace') -- doc = dom.parseString(_zypper_check_result(call, xml=True)) -- product_list = doc.getElementsByTagName('product-list') -+ product_list = __zypper__.nolock.xml.call(*cmd).getElementsByTagName('product-list') - if not product_list: - return ret # No products found - -@@ -1371,10 +1510,8 @@ def download(*packages, **kwargs): - if refresh: - refresh_db() - -- ret = __salt__['cmd.run_all'](_zypper('-x', 'download', *packages), output_loglevel='trace') -- doc = dom.parseString(_zypper_check_result(ret, xml=True)) - pkg_ret = {} -- for dld_result in doc.getElementsByTagName("download-result"): -+ for dld_result in __zypper__.xml.call('download', *packages).getElementsByTagName("download-result"): - repo = dld_result.getElementsByTagName("repository")[0] - pkg_info = { - 'repository-name': repo.getAttribute("name"), -diff --git a/tests/unit/modules/zypper_test.py b/tests/unit/modules/zypper_test.py -index 97e42ef..16e8542 100644 ---- a/tests/unit/modules/zypper_test.py -+++ b/tests/unit/modules/zypper_test.py -@@ -23,6 +23,17 @@ from salttesting.helpers import ensure_in_syspath - ensure_in_syspath('../../') - - -+class ZyppCallMock(object): -+ def __init__(self, return_value=None): -+ self.__return_value = return_value -+ -+ def __getattr__(self, item): -+ return self -+ -+ def __call__(self, *args, **kwargs): -+ return MagicMock(return_value=self.__return_value)() -+ -+ - def get_test_data(filename): - ''' - Return static test data -@@ -64,56 +75,63 @@ class ZypperTestCase(TestCase): - self.assertIn(pkg, upgrades) - self.assertEqual(upgrades[pkg], version) - -- def test_zypper_check_result(self): -+ def test_zypper_caller(self): - ''' -- Test zypper check result function -+ Test Zypper caller. -+ :return: - ''' -- cmd_out = { -- 'retcode': 1, -- 'stdout': '', -- 'stderr': 'This is an error' -- } -- with self.assertRaisesRegexp(CommandExecutionError, "^zypper command failed: This is an error$"): -- zypper._zypper_check_result(cmd_out) -- -- cmd_out = { -- 'retcode': 0, -- 'stdout': 'result', -- 'stderr': '' -- } -- out = zypper._zypper_check_result(cmd_out) -- self.assertEqual(out, "result") -- -- cmd_out = { -- 'retcode': 1, -- 'stdout': '', -- 'stderr': 'This is an error' -- } -- with self.assertRaisesRegexp(CommandExecutionError, "^zypper command failed: This is an error$"): -- zypper._zypper_check_result(cmd_out, xml=True) -- -- cmd_out = { -- 'retcode': 1, -- 'stdout': '', -- 'stderr': '' -- } -- with self.assertRaisesRegexp(CommandExecutionError, "^zypper command failed: Check zypper logs$"): -- zypper._zypper_check_result(cmd_out, xml=True) -- -- cmd_out = { -- 'stdout': ''' -- -- Refreshing service 'container-suseconnect'. -- Some handled zypper internal error -- Another zypper internal error -- -- ''', -- 'stderr': '', -- 'retcode': 1 -- } -- with self.assertRaisesRegexp(CommandExecutionError, -- "^zypper command failed: Some handled zypper internal error\nAnother zypper internal error$"): -- zypper._zypper_check_result(cmd_out, xml=True) -+ class RunSniffer(object): -+ def __init__(self, stdout=None, stderr=None, retcode=None): -+ self.calls = list() -+ self._stdout = stdout or '' -+ self._stderr = stderr or '' -+ self._retcode = retcode or 0 -+ -+ def __call__(self, *args, **kwargs): -+ self.calls.append({'args': args, 'kwargs': kwargs}) -+ return {'stdout': self._stdout, -+ 'stderr': self._stderr, -+ 'retcode': self._retcode} -+ -+ stdout_xml_snippet = '' -+ sniffer = RunSniffer(stdout=stdout_xml_snippet) -+ with patch.dict('salt.modules.zypper.__salt__', {'cmd.run_all': sniffer}): -+ self.assertEqual(zypper.__zypper__.call('foo'), stdout_xml_snippet) -+ self.assertEqual(len(sniffer.calls), 1) -+ -+ zypper.__zypper__.call('bar') -+ self.assertEqual(len(sniffer.calls), 2) -+ self.assertEqual(sniffer.calls[0]['args'][0], ['zypper', '--non-interactive', '--no-refresh', 'foo']) -+ self.assertEqual(sniffer.calls[1]['args'][0], ['zypper', '--non-interactive', '--no-refresh', 'bar']) -+ -+ dom = zypper.__zypper__.xml.call('xml-test') -+ self.assertEqual(sniffer.calls[2]['args'][0], ['zypper', '--non-interactive', '--xmlout', -+ '--no-refresh', 'xml-test']) -+ self.assertEqual(dom.getElementsByTagName('test')[0].getAttribute('foo'), 'bar') -+ -+ zypper.__zypper__.refreshable.call('refresh-test') -+ self.assertEqual(sniffer.calls[3]['args'][0], ['zypper', '--non-interactive', 'refresh-test']) -+ -+ zypper.__zypper__.nolock.call('no-locking-test') -+ self.assertEqual(sniffer.calls[4].get('kwargs', {}).get('env', {}).get('ZYPP_READONLY_HACK'), "1") -+ self.assertEqual(sniffer.calls[4].get('kwargs', {}).get('env', {}).get('SALT_RUNNING'), "1") -+ -+ zypper.__zypper__.call('locking-test') -+ self.assertEqual(sniffer.calls[5].get('kwargs', {}).get('env', {}).get('ZYPP_READONLY_HACK'), None) -+ self.assertEqual(sniffer.calls[5].get('kwargs', {}).get('env', {}).get('SALT_RUNNING'), "1") -+ -+ # Test exceptions -+ stdout_xml_snippet = 'Booya!' -+ sniffer = RunSniffer(stdout=stdout_xml_snippet, retcode=1) -+ with patch.dict('salt.modules.zypper.__salt__', {'cmd.run_all': sniffer}): -+ with self.assertRaisesRegexp(CommandExecutionError, '^Zypper command failure: Booya!$'): -+ zypper.__zypper__.xml.call('crashme') -+ -+ with self.assertRaisesRegexp(CommandExecutionError, "^Zypper command failure: Check Zypper's logs.$"): -+ zypper.__zypper__.call('crashme again') -+ -+ zypper.__zypper__.noraise.call('stay quiet') -+ self.assertEqual(zypper.__zypper__.error_msg, "Check Zypper's logs.") - - def test_list_upgrades_error_handling(self): - ''' -@@ -129,11 +147,12 @@ class ZypperTestCase(TestCase): - Another zypper internal error - - ''', -- 'retcode': 1 -+ 'stderr': '', -+ 'retcode': 1, - } -- with patch.dict(zypper.__salt__, {'cmd.run_all': MagicMock(return_value=ref_out)}): -+ with patch.dict('salt.modules.zypper.__salt__', {'cmd.run_all': MagicMock(return_value=ref_out)}): - with self.assertRaisesRegexp(CommandExecutionError, -- "^zypper command failed: Some handled zypper internal error\nAnother zypper internal error$"): -+ "^Zypper command failure: Some handled zypper internal error\nAnother zypper internal error$"): - zypper.list_upgrades(refresh=False) - - # Test unhandled error -@@ -142,8 +161,8 @@ class ZypperTestCase(TestCase): - 'stdout': '', - 'stderr': '' - } -- with patch.dict(zypper.__salt__, {'cmd.run_all': MagicMock(return_value=ref_out)}): -- with self.assertRaisesRegexp(CommandExecutionError, '^zypper command failed: Check zypper logs$'): -+ with patch.dict('salt.modules.zypper.__salt__', {'cmd.run_all': MagicMock(return_value=ref_out)}): -+ with self.assertRaisesRegexp(CommandExecutionError, "^Zypper command failure: Check Zypper's logs.$"): - zypper.list_upgrades(refresh=False) - - def test_list_products(self): -@@ -260,8 +279,7 @@ class ZypperTestCase(TestCase): - :return: - ''' - test_pkgs = ['vim', 'emacs', 'python'] -- ref_out = get_test_data('zypper-available.txt') -- with patch.dict(zypper.__salt__, {'cmd.run_stdout': MagicMock(return_value=ref_out)}): -+ with patch('salt.modules.zypper.__zypper__', ZyppCallMock(return_value=get_test_data('zypper-available.txt'))): - available = zypper.info_available(*test_pkgs, refresh=False) - self.assertEqual(len(available), 3) - for pkg_name, pkg_info in available.items(): -@@ -286,8 +304,7 @@ class ZypperTestCase(TestCase): - - :return: - ''' -- ref_out = get_test_data('zypper-available.txt') -- with patch.dict(zypper.__salt__, {'cmd.run_stdout': MagicMock(return_value=ref_out)}): -+ with patch('salt.modules.zypper.__zypper__', ZyppCallMock(return_value=get_test_data('zypper-available.txt'))): - self.assertEqual(zypper.latest_version('vim'), '7.4.326-2.62') - - @patch('salt.modules.zypper.refresh_db', MagicMock(return_value=True)) -@@ -298,7 +315,7 @@ class ZypperTestCase(TestCase): - :return: - ''' - ref_out = get_test_data('zypper-available.txt') -- with patch.dict(zypper.__salt__, {'cmd.run_stdout': MagicMock(return_value=ref_out)}): -+ with patch('salt.modules.zypper.__zypper__', ZyppCallMock(return_value=get_test_data('zypper-available.txt'))): - for pkg_name in ['emacs', 'python']: - self.assertFalse(zypper.upgrade_available(pkg_name)) - self.assertTrue(zypper.upgrade_available('vim')) --- -2.8.1 - diff --git a/0016-Bugfix-Restore-boolean-values-from-the-repo-configur.patch b/0016-Bugfix-Restore-boolean-values-from-the-repo-configur.patch deleted file mode 100644 index 02223e5..0000000 --- a/0016-Bugfix-Restore-boolean-values-from-the-repo-configur.patch +++ /dev/null @@ -1,118 +0,0 @@ -From e52b55979bdc0734c2e452dd2fd67fb56a3fb37b Mon Sep 17 00:00:00 2001 -From: Bo Maryniuk -Date: Fri, 6 May 2016 12:29:48 +0200 -Subject: [PATCH 16/16] Bugfix: Restore boolean values from the repo - configuration - -* Add test data for repos - -* Add repo config test - -* Bugfix (follow-up): setting priority requires non-positive integer ---- - salt/modules/zypper.py | 16 +++++++++------- - tests/unit/modules/zypp/zypper-repo-1.cfg | 5 +++++ - tests/unit/modules/zypp/zypper-repo-2.cfg | 5 +++++ - tests/unit/modules/zypper_test.py | 21 +++++++++++++++++++++ - 4 files changed, 40 insertions(+), 7 deletions(-) - create mode 100644 tests/unit/modules/zypp/zypper-repo-1.cfg - create mode 100644 tests/unit/modules/zypp/zypper-repo-2.cfg - -diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py -index 53b5d9f..c37b382 100644 ---- a/salt/modules/zypper.py -+++ b/salt/modules/zypper.py -@@ -602,12 +602,14 @@ def _get_repo_info(alias, repos_cfg=None): - Get one repo meta-data. - ''' - try: -- ret = dict((repos_cfg or _get_configured_repos()).items(alias)) -- ret['alias'] = alias -- for key, val in six.iteritems(ret): -- if val == 'NONE': -- ret[key] = None -- return ret -+ meta = dict((repos_cfg or _get_configured_repos()).items(alias)) -+ meta['alias'] = alias -+ for key, val in six.iteritems(meta): -+ if val in ['0', '1']: -+ meta[key] = int(meta[key]) == 1 -+ elif val == 'NONE': -+ meta[key] = None -+ return meta - except (ValueError, configparser.NoSectionError): - return {} - -@@ -769,7 +771,7 @@ def mod_repo(repo, **kwargs): - cmd_opt.append('--gpg-auto-import-keys') - - if 'priority' in kwargs: -- cmd_opt.append("--priority='{0}'".format(kwargs.get('priority', DEFAULT_PRIORITY))) -+ cmd_opt.append("--priority={0}".format(kwargs.get('priority', DEFAULT_PRIORITY))) - - if 'humanname' in kwargs: - cmd_opt.append("--name='{0}'".format(kwargs.get('humanname'))) -diff --git a/tests/unit/modules/zypp/zypper-repo-1.cfg b/tests/unit/modules/zypp/zypper-repo-1.cfg -new file mode 100644 -index 0000000..958718c ---- /dev/null -+++ b/tests/unit/modules/zypp/zypper-repo-1.cfg -@@ -0,0 +1,5 @@ -+[SLE12-SP1-x86_64-Update] -+enabled=1 -+autorefresh=1 -+baseurl=http://somehost.com/SUSE/Updates/SLE-SERVER/12-SP1/x86_64/update/ -+type=NONE -diff --git a/tests/unit/modules/zypp/zypper-repo-2.cfg b/tests/unit/modules/zypp/zypper-repo-2.cfg -new file mode 100644 -index 0000000..f55cf18 ---- /dev/null -+++ b/tests/unit/modules/zypp/zypper-repo-2.cfg -@@ -0,0 +1,5 @@ -+[SLE12-SP1-x86_64-Update-disabled] -+enabled=0 -+autorefresh=0 -+baseurl=http://somehost.com/SUSE/Updates/SLE-SERVER/12-SP1/x86_64/update/ -+type=NONE -diff --git a/tests/unit/modules/zypper_test.py b/tests/unit/modules/zypper_test.py -index 16e8542..4e735cd 100644 ---- a/tests/unit/modules/zypper_test.py -+++ b/tests/unit/modules/zypper_test.py -@@ -17,6 +17,8 @@ from salttesting.mock import ( - from salt.exceptions import CommandExecutionError - - import os -+from salt.ext.six.moves import configparser -+import StringIO - - from salttesting.helpers import ensure_in_syspath - -@@ -391,6 +393,25 @@ class ZypperTestCase(TestCase): - self.assertTrue(diff[pkg_name]['old']) - self.assertFalse(diff[pkg_name]['new']) - -+ def test_repo_value_info(self): -+ ''' -+ Tests if repo info is properly parsed. -+ -+ :return: -+ ''' -+ repos_cfg = configparser.ConfigParser() -+ for cfg in ['zypper-repo-1.cfg', 'zypper-repo-2.cfg']: -+ repos_cfg.readfp(StringIO.StringIO(get_test_data(cfg))) -+ -+ for alias in repos_cfg.sections(): -+ r_info = zypper._get_repo_info(alias, repos_cfg=repos_cfg) -+ self.assertEqual(type(r_info['type']), type(None)) -+ self.assertEqual(type(r_info['enabled']), bool) -+ self.assertEqual(type(r_info['autorefresh']), bool) -+ self.assertEqual(type(r_info['baseurl']), str) -+ self.assertEqual(r_info['type'], None) -+ self.assertEqual(r_info['enabled'], alias == 'SLE12-SP1-x86_64-Update') -+ self.assertEqual(r_info['autorefresh'], alias == 'SLE12-SP1-x86_64-Update') - - if __name__ == '__main__': - from integration import run_tests --- -2.8.1 - diff --git a/0017-Add-SUSE-Manager-plugin.patch b/0017-Add-SUSE-Manager-plugin.patch deleted file mode 100644 index 4cc5571..0000000 --- a/0017-Add-SUSE-Manager-plugin.patch +++ /dev/null @@ -1,103 +0,0 @@ -From 92f17a79c53bb5b75b9dac4aa0add94dfe2f447f Mon Sep 17 00:00:00 2001 -From: Bo Maryniuk -Date: Mon, 9 May 2016 10:33:44 +0200 -Subject: [PATCH 17/17] Add SUSE Manager plugin - ---- - scripts/zypper/plugins/commit/README.md | 3 ++ - scripts/zypper/plugins/commit/susemanager | 73 +++++++++++++++++++++++++++++++ - 2 files changed, 76 insertions(+) - create mode 100644 scripts/zypper/plugins/commit/README.md - create mode 100755 scripts/zypper/plugins/commit/susemanager - -diff --git a/scripts/zypper/plugins/commit/README.md b/scripts/zypper/plugins/commit/README.md -new file mode 100644 -index 0000000..01c8917 ---- /dev/null -+++ b/scripts/zypper/plugins/commit/README.md -@@ -0,0 +1,3 @@ -+# Zypper plugins -+ -+Plugins here are required to interact with SUSE Manager in conjunction of SaltStack and Zypper. -diff --git a/scripts/zypper/plugins/commit/susemanager b/scripts/zypper/plugins/commit/susemanager -new file mode 100755 -index 0000000..e64d683 ---- /dev/null -+++ b/scripts/zypper/plugins/commit/susemanager -@@ -0,0 +1,73 @@ -+#!/usr/bin/python -+# -+# Copyright (c) 2016 SUSE Linux LLC -+# All Rights Reserved. -+# -+# This software is licensed to you under the GNU General Public License, -+# version 2 (GPLv2). There is NO WARRANTY for this software, express or -+# implied, including the implied warranties of MERCHANTABILITY or FITNESS -+# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 -+# along with this software; if not, see -+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. -+# -+# Author: Bo Maryniuk -+ -+import sys -+import os -+ -+import salt.client -+import salt.utils -+ -+from zypp_plugin import Plugin -+ -+ -+class SpacewalkDriftDetector(Plugin): -+ """ -+ Return diff of the installed packages outside the Salt. -+ """ -+ def __init__(self): -+ Plugin.__init__(self) -+ self.salt = salt.client.Caller().sminion.functions -+ -+ def _within_salt(self): -+ """ -+ Return true, if Zypper is running from within the SaltStack. -+ """ -+ return 'SALT_RUNNING' in os.environ -+ -+ def _get_packages(self): -+ """ -+ Get the list of the packages at the current time. -+ """ -+ ret = dict() -+ cmd = "rpm -qa --queryformat '%{NAME}_|-%{VERSION}_|-%{RELEASE}_|-%|EPOCH?{%{EPOCH}}:{}|\\n'" -+ for line in os.popen(cmd).read().split("\n"): -+ if not line: -+ continue -+ name, pkgver, rel, epoch = line.split('_|-') -+ if epoch: -+ pkgver = '{0}:{1}'.format(epoch, pkgver) -+ if rel: -+ pkgver += '-{0}'.format(rel) -+ ret[name] = pkgver -+ -+ return ret -+ -+ def PLUGINBEGIN(self, headers, body): -+ """ -+ Hook when plugin begins Zypper's transaction. -+ """ -+ if not self._within_salt(): -+ self._pkg_before = self._get_packages() -+ self.ack() -+ -+ def PLUGINEND(self, headers, body): -+ """ -+ Hook when plugin closes Zypper's transaction. -+ """ -+ if not self._within_salt(): -+ self.salt['event.send']('zypper/changed', salt.utils.compare_dicts(self._pkg_before, self._get_packages())) -+ self.ack() -+ -+ -+SpacewalkDriftDetector().main() --- -2.8.2 - diff --git a/salt-2015.8.10.tar.gz b/salt-2015.8.10.tar.gz new file mode 100644 index 0000000..598236b --- /dev/null +++ b/salt-2015.8.10.tar.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a81273ab4b01e0f1d29b28ab99a16eb94e5c430107b05491c94f3baf8b95c99 +size 6972776 diff --git a/salt-2015.8.8.tar.gz b/salt-2015.8.8.tar.gz deleted file mode 100644 index 4c5f94d..0000000 --- a/salt-2015.8.8.tar.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b2ecce7bf562cfcd6586d66ade278f268bb89023f0fa0accaa55f90b8a668ef5 -size 6982904 diff --git a/salt.changes b/salt.changes index 9278333..33eadb4 100644 --- a/salt.changes +++ b/salt.changes @@ -1,3 +1,98 @@ +------------------------------------------------------------------- +Wed Jun 1 09:52:40 UTC 2016 - mihai.dinca@suse.com + +- Fix pkgrepo.managed gpgkey argument doesn't work (bsc#979448) + Add: + * 0012-Fix-pkgrepo.managed-gpgkey-argument-bsc-979448.patch + +------------------------------------------------------------------- +Thu May 26 11:05:04 UTC 2016 - pablo.suarezhernandez@suse.com + +- Package checksum validation for zypper pkg.download + Add: + * 0009-checksum-validation-when-zypper-pkg.download.patch + * 0010-unit-tests-for-rpm.checksum-and-zypper.download.patch +- Check if a job has executed and returned successfully + Add: + * 0011-jobs.exit_success-allow-to-check-if-a-job-has-execut.patch + +------------------------------------------------------------------- +Tue May 24 11:06:12 UTC 2016 - bmaryniuk@suse.com + +- Prevent several minion processes on the same machine (bsc#975733) + + Add: + * 0008-Prevent-several-minion-processes-on-the-same-machine.patch + +------------------------------------------------------------------- +Mon May 23 15:11:42 UTC 2016 - bmaryniuk@suse.com + +- Changed Zypper's plugin. Added Unit test and related to that + data (bsc#980313). + + Add: + * 0006-Create-salt-proxy-instantiated-service-file.patch + * 0007-Add-SUSE-Manager-plugin.patch + + Remove: + * 0006-Add-SUSE-Manager-plugin.patch + * 0007-Create-salt-proxy-instantiated-service-file.patch + * 0008-Alter-the-event-name.patch + + +------------------------------------------------------------------- +Mon May 23 09:52:29 UTC 2016 - tampakrap@opensuse.org + +- Update to 2015.8.10 + see https://docs.saltstack.com/en/latest/topics/releases/2015.8.10.html + +------------------------------------------------------------------- +Fri May 20 10:54:42 UTC 2016 - tampakrap@opensuse.org + +- Update to 2015.8.9 + see https://docs.saltstack.com/en/latest/topics/releases/2015.8.9.html + Patches renamed: + * 0006-Add-SUSE-Manager-plugin.patch + * 0007-Create-salt-proxy-instantiated-service-file.patch + * 0008-Alter-the-event-name.patch + Patches removed: + * 0006-Update-to-2015.8.8.2.patch + * 0007-Force-sort-the-RPM-output-to-ensure-latest-version-o.patch + * 0008-Cleaner-deprecation-process-with-decorators.patch + * 0009-fix-sorting-by-latest-version-when-called-with-an-at.patch + * 0010-Prevent-metadata-download-when-getting-installed-pro.patch + * 0011-Check-if-EOL-is-available-in-a-particular-product-bs.patch + * 0012-Bugfix-salt-key-crashes-if-tries-to-generate-keys-to.patch + * 0013-Prevent-crash-if-pygit2-package-is-requesting-re-com.patch + * 0014-align-OS-grains-from-older-SLES-with-current-one-326.patch + * 0015-Unblock-Zypper.-Modify-environment.patch + * 0016-Bugfix-Restore-boolean-values-from-the-repo-configur.patch + * 0017-Add-SUSE-Manager-plugin.patch + * 0018-Create-salt-proxy-instantiated-service-file.patch + * 0019-Alter-the-event-name.patch + +------------------------------------------------------------------- +Tue May 17 10:08:57 UTC 2016 - bmaryniuk@suse.com + +- Zypper plugin: alter the generated event name on package set + change. + Add: + * 0019-Alter-the-event-name.patch + +------------------------------------------------------------------- +Thu May 12 08:52:26 UTC 2016 - pablo.suarezhernandez@suse.com + +- salt-proxy .service file created (bsc#975306) + Add: + * 0018-Create-salt-proxy-instantiated-service-file.patch + +------------------------------------------------------------------- +Thu May 12 00:42:34 UTC 2016 - tserong@suse.com + +- Fix file ownership on master keys and cache directories during upgrade + (handles upgrading from salt 2014, where the daemon ran as root, to 2015 + where it runs as the salt user, bsc#979676). + ------------------------------------------------------------------- Wed May 11 07:20:40 UTC 2016 - bmaryniuk@suse.com diff --git a/salt.spec b/salt.spec index 98217ca..19e8a97 100644 --- a/salt.spec +++ b/salt.spec @@ -36,7 +36,7 @@ %bcond_without docs Name: salt -Version: 2015.8.8 +Version: 2015.8.10 Release: 0 Summary: A parallel remote execution system License: Apache-2.0 @@ -57,31 +57,22 @@ Patch3: 0003-Check-if-byte-strings-are-properly-encoded-in-UTF-8.patch Patch4: 0004-do-not-generate-a-date-in-a-comment-to-prevent-rebui.patch # PATCH-FIX-OPENSUSE - Upstream default hash type is set to MD5, while we require SHA256 (bsc#955373) Patch5: 0005-Use-SHA256-hash-type-by-default.patch -# PATCH-FIX-UPSTREAM https://docs.saltstack.com/en/latest/topics/releases/2015.8.8.html#salt-2015-8-8-2 -Patch6: 0006-Update-to-2015.8.8.2.patch -# PATCH-FIX-UPSTREAM https://github.com/saltstack/salt/pull/32243 -Patch7: 0007-Force-sort-the-RPM-output-to-ensure-latest-version-o.patch -# PATCH-FIX-UPSTREAM https://github.com/saltstack/salt/pull/32068 -Patch8: 0008-Cleaner-deprecation-process-with-decorators.patch -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/32323 -Patch9: 0009-fix-sorting-by-latest-version-when-called-with-an-at.patch -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/32353 -Patch10: 0010-Prevent-metadata-download-when-getting-installed-pro.patch -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/32505 -Patch11: 0011-Check-if-EOL-is-available-in-a-particular-product-bs.patch -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/32436 -Patch12: 0012-Bugfix-salt-key-crashes-if-tries-to-generate-keys-to.patch -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/32652 -Patch13: 0013-Prevent-crash-if-pygit2-package-is-requesting-re-com.patch -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/32649 -Patch14: 0014-align-OS-grains-from-older-SLES-with-current-one-326.patch -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/32892 -Patch15: 0015-Unblock-Zypper.-Modify-environment.patch -# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/33088 -Patch16: 0016-Bugfix-Restore-boolean-values-from-the-repo-configur.patch +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/31798 +Patch6: 0006-Create-salt-proxy-instantiated-service-file.patch # PATCH-FIX-OPENSUSE Generate events from the Salt minion, # if Zypper has been used outside the Salt infrastructure -Patch17: 0017-Add-SUSE-Manager-plugin.patch +Patch7: 0007-Add-SUSE-Manager-plugin.patch +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/33464 +Patch8: 0008-Prevent-several-minion-processes-on-the-same-machine.patch +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/33469 +Patch9: 0009-checksum-validation-when-zypper-pkg.download.patch +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/33501 +Patch10: 0010-unit-tests-for-rpm.checksum-and-zypper.download.patch +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/33465 +Patch11: 0011-jobs.exit_success-allow-to-check-if-a-job-has-execut.patch +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/33432 +# https://github.com/saltstack/salt/pull/33581 +Patch12: 0012-Fix-pkgrepo.managed-gpgkey-argument-bsc-979448.patch BuildRoot: %{_tmppath}/%{name}-%{version}-build BuildRequires: logrotate @@ -432,21 +423,17 @@ cp %{S:1} . %patch4 -p1 %patch5 -p1 %patch6 -p1 + +# This is SUSE-only patch +%if 0%{?suse_version} %patch7 -p1 %patch8 -p1 +%endif + %patch9 -p1 %patch10 -p1 %patch11 -p1 %patch12 -p1 -%patch13 -p1 -%patch14 -p1 -%patch15 -p1 -%patch16 -p1 - -# This is SUSE-only patch -%if 0%{?suse_version} -%patch17 -p1 -%endif %build python setup.py --salt-transport=both build @@ -507,6 +494,7 @@ install -Dpm 0644 pkg/salt-master.service %{buildroot}%{_unitdir}/salt-master.se install -Dpm 0644 pkg/salt-minion.service %{buildroot}%{_unitdir}/salt-minion.service install -Dpm 0644 pkg/salt-syndic.service %{buildroot}%{_unitdir}/salt-syndic.service install -Dpm 0644 pkg/salt-api.service %{buildroot}%{_unitdir}/salt-api.service +install -Dpm 0644 pkg/salt-proxy@.service %{buildroot}%{_unitdir}/salt-proxy@.service ln -s service %{buildroot}%{_sbindir}/rcsalt-master ln -s service %{buildroot}%{_sbindir}/rcsalt-syndic ln -s service %{buildroot}%{_sbindir}/rcsalt-minion @@ -631,6 +619,20 @@ systemd-tmpfiles --create /usr/lib/tmpfiles.d/salt.conf || true %endif %post master +if [ $1 -eq 2 ] ; then + # Upgrading from an earlier version. If this is from 2014, where daemons + # ran as root, we need to chown some stuff to salt in order for the new + # version to actually work. It seems a manual restart of salt-master may + # still be required, but at least this will actually work given the file + # ownership is correct. + for file in master.{pem,pub} ; do + [ -f /etc/salt/pki/master/$file ] && chown salt /etc/salt/pki/master/$file + done + for dir in file_lists minions jobs ; do + [ -d /var/cache/salt/master/$dir ] && chown -R salt:salt /var/cache/salt/master/$dir + done + true +fi %if %{with systemd} %service_add_post salt-master.service %fillup_only @@ -796,6 +798,9 @@ systemd-tmpfiles --create /usr/lib/tmpfiles.d/salt.conf || true %defattr(-,root,root) %{_bindir}/salt-proxy %{_mandir}/man1/salt-proxy.1.gz +%if %{with systemd} +%{_unitdir}/salt-proxy@.service +%endif %files master %defattr(-,root,root)