Accepting request 399636 from systemsmanagement:saltstack:testing

OBS-URL: https://build.opensuse.org/request/show/399636
OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=70
This commit is contained in:
Klaus Kämpf 2016-06-06 10:54:59 +00:00 committed by Git OBS Bridge
parent 47fccf7d16
commit a10aa73e42
23 changed files with 975 additions and 3018 deletions

View File

@ -0,0 +1,36 @@
From e2236cc77888d3c359c23dfb47a57e1e057864bb Mon Sep 17 00:00:00 2001
From: Christian McHugh <mchugh19@hotmail.com>
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

View File

@ -1,168 +0,0 @@
From 00600229ac41ae618bf01e8af6e2c0183d924204 Mon Sep 17 00:00:00 2001
From: Theo Chatzimichos <tampakrap@gmail.com>
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

View File

@ -0,0 +1,221 @@
From df87ac3485ff8b5013e720435905afda6b53ada8 Mon Sep 17 00:00:00 2001
From: Bo Maryniuk <bo@suse.de>
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 <bo@suse.de>
+
+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 <bo@suse.de>`
+'''
+
+# 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

View File

@ -1,350 +0,0 @@
From e3a599712daafb88b6b77ebf6c7684fdd10ffedf Mon Sep 17 00:00:00 2001
From: Bo Maryniuk <bo@suse.de>
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

View File

@ -1,922 +0,0 @@
From 2dcc979ab2897619baebfef5779120a98284d408 Mon Sep 17 00:00:00 2001
From: Bo Maryniuk <bo@maryniuk.net>
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

View File

@ -0,0 +1,25 @@
From 46713681bda8e45667691fbda6efe808c81574b1 Mon Sep 17 00:00:00 2001
From: Bo Maryniuk <bo@suse.de>
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

View File

@ -0,0 +1,74 @@
From d27af7dee61e83165bbd9adb9f0b6dc467907faa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?=
<psuarezhernandez@suse.com>
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

View File

@ -1,48 +0,0 @@
From cb588505919b6c74ed824d26a184eec0f47a585b Mon Sep 17 00:00:00 2001
From: Michael Calmer <mc@suse.de>
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

View File

@ -1,29 +0,0 @@
From 336929a4cadca55b00dbf1cd33eb35d19f420c73 Mon Sep 17 00:00:00 2001
From: Michael Calmer <mc@suse.de>
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

View File

@ -0,0 +1,105 @@
From 2742ee76ccc50cd4f84e44861ef82ec5f3b5234a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?=
<psuarezhernandez@suse.com>
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 @@
+<?xml version='1.0'?>
+<stream>
+ <message type="info">Loading repository data...</message>
+ <message type="info">Reading installed packages...</message>
+ <message type="warning">Argument resolves to no package: foo</message>
+ <progress id="" name="(1/1) /var/cache/zypp/packages/SLE-12-x86_64-Pool/x86_64/nmap-6.46-1.72.x86_64.rpm"/>
+ <download-result>
+ <solvable>
+ <kind>package</kind>
+ <name>nmap</name>
+ <edition epoch="0" version="6.46" release="1.72"/>
+ <arch>x86_64</arch>
+ <repository name="SLE-12-x86_64-Pool" alias="SLE-12-x86_64-Pool"/>
+ </solvable>
+ <localfile path="/var/cache/zypp/packages/SLE-12-x86_64-Pool/x86_64/nmap-6.46-1.72.x86_64.rpm"/>
+ </download-result>
+ <progress id="" name="(1/1) /var/cache/zypp/packages/SLE-12-x86_64-Pool/x86_64/nmap-6.46-1.72.x86_64.rpm" done="0"/>
+ <message type="info">download: Done.</message>
+</stream>
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

View File

@ -1,146 +0,0 @@
From aae1c09957eab3c89a6c8f78a579cdf9dcfbe188 Mon Sep 17 00:00:00 2001
From: Bo Maryniuk <bo@suse.de>
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.</description></product>
+<product name="SUSE_SLES" version="11.3" release="1.201" epoch="0" arch="x86_64" productline="" registerrelease="" vendor="SUSE LINUX Products GmbH, Nuernberg, Germany" summary="SUSE Linux Enterprise Server 11 SP3 No EOL" shortname="" flavor="" isbase="0" repo="nu_novell_com:SLES11-SP3-Updates" installed="0">0x7ffdb538e948<description>SUSE 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 worlds
+ 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.</description></product>
<product name="SUSE-Manager-Server" version="2.1" release="1.2" epoch="0" arch="x86_64" productline="" registerrelease="" vendor="SUSE LINUX Products GmbH, Nuernberg, Germany" summary="SUSE Manager Server" shortname="" flavor="cd" isbase="0" repo="nu_novell_com:SUSE-Manager-Server-2.1-Pool" installed="0"><endoflife time_t="0" text="1970-01-01T01:00:00+0100"/>0x7ffdb538e948<description>SUSE Manager Server appliance</description></product>
<product name="SUSE-Manager-Server" version="2.1" release="1.2" epoch="0" arch="x86_64" productline="manager" registerrelease="" vendor="SUSE LINUX Products GmbH, Nuernberg, Germany" summary="SUSE Manager Server" shortname="" flavor="cd" isbase="1" repo="@System" installed="1"><endoflife time_t="0" text="1970-01-01T01:00:00+0100"/>0x7ffdb538e948<description>SUSE Manager Server appliance</description></product>
+<product name="SUSE-Manager-Server-Broken-EOL" version="2.1" release="1.2" epoch="0" arch="x86_64" productline="manager" registerrelease="" vendor="SUSE LINUX Products GmbH, Nuernberg, Germany" summary="SUSE Manager Server" shortname="" flavor="cd" isbase="1" repo="@System" installed="1"><endoflife wrong="attribute"/>0x7ffdb538e948<description>SUSE Manager Server appliance</description></product>
</product-list>
</stream>
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.</description></product>
SUSE Manager Tools provide packages required to connect to a
SUSE Manager Server.
&lt;p&gt;</description></product>
+<product name="sle-manager-tools-beta-no-eol" version="12" release="0" epoch="0" arch="x86_64" vendor="obs://build.suse.de/Devel:Galaxy:Manager:Head" summary="SUSE Manager Tools" repo="SUSE-Manager-Head" productline="" registerrelease="" shortname="Manager-Tools" flavor="POOL" isbase="false" installed="false"><registerflavor>extension</registerflavor><description>&lt;p&gt;
+ SUSE Manager Tools provide packages required to connect to a
+ SUSE Manager Server.
+ &lt;p&gt;</description></product>
+<product name="sle-manager-tools-beta-broken-eol" version="12" release="0" epoch="0" arch="x86_64" vendor="obs://build.suse.de/Devel:Galaxy:Manager:Head" summary="SUSE Manager Tools" repo="SUSE-Manager-Head" productline="" registerrelease="" shortname="Manager-Tools" flavor="POOL" isbase="false" installed="false"><endoflife wrong="attribute"/><registerflavor>extension</registerflavor><description>&lt;p&gt;
+ SUSE Manager Tools provide packages required to connect to a
+ SUSE Manager Server.
+ &lt;p&gt;</description></product>
<product name="SLES" version="12.1" release="0" epoch="0" arch="x86_64" vendor="SUSE" summary="SUSE Linux Enterprise Server 12 SP1" repo="@System" productline="sles" registerrelease="" shortname="SLES12-SP1" flavor="DVD" isbase="true" installed="true"><endoflife time_t="1730332800" text="2024-10-31T01:00:00+01"/><registerflavor/><description>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 <https://www.suse.com/>',
- '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

View File

@ -0,0 +1,53 @@
From e0f15c5292869549b5c80997ccb3282961be8e49 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?=
<psuarezhernandez@suse.com>
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

View File

@ -1,69 +0,0 @@
From 5e99ee2bec1139b1944284975454c716d477f3e0 Mon Sep 17 00:00:00 2001
From: Bo Maryniuk <bo@maryniuk.net>
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

View File

@ -0,0 +1,325 @@
From b0e1ba5158cc4b54102bac200ae343935eeb2db5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mihai=20Dinc=C4=83?= <dincamihai@users.noreply.github.com>
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 <repo-name>`
+
+ :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 <repo-name>`
+
+ :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 <repo-name>`
+
+ :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 <repo-name>`
+
+ :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

View File

@ -1,86 +0,0 @@
From f187ee058eb221eb5a34d51ca5db53bb8eeea5e1 Mon Sep 17 00:00:00 2001
From: Bo Maryniuk <bo@maryniuk.net>
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

View File

@ -1,39 +0,0 @@
From 0961f5bd3e3b7aa3ebd75fe064044d078df62724 Mon Sep 17 00:00:00 2001
From: Michael Calmer <mc@suse.de>
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

View File

@ -1,904 +0,0 @@
From e52c7926a699bdee3fad2767c8aa755ee115c5d7 Mon Sep 17 00:00:00 2001
From: Bo Maryniuk <bo@suse.de>
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 <package name>
'''
# 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': '''<?xml version='1.0'?>
-<stream>
- <message type="info">Refreshing service &apos;container-suseconnect&apos;.</message>
- <message type="error">Some handled zypper internal error</message>
- <message type="error">Another zypper internal error</message>
-</stream>
- ''',
- '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 = '<?xml version="1.0"?><test foo="bar"/>'
+ 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 = '<?xml version="1.0"?><stream><message type="error">Booya!</message></stream>'
+ 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):
<message type="error">Another zypper internal error</message>
</stream>
''',
- '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

View File

@ -1,118 +0,0 @@
From e52b55979bdc0734c2e452dd2fd67fb56a3fb37b Mon Sep 17 00:00:00 2001
From: Bo Maryniuk <bo@suse.de>
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

View File

@ -1,103 +0,0 @@
From 92f17a79c53bb5b75b9dac4aa0add94dfe2f447f Mon Sep 17 00:00:00 2001
From: Bo Maryniuk <bo@suse.de>
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 <bo@suse.de>
+
+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

3
salt-2015.8.10.tar.gz Normal file
View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4a81273ab4b01e0f1d29b28ab99a16eb94e5c430107b05491c94f3baf8b95c99
size 6972776

View File

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b2ecce7bf562cfcd6586d66ade278f268bb89023f0fa0accaa55f90b8a668ef5
size 6982904

View File

@ -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

View File

@ -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)