Accepting request 431193 from systemsmanagement:saltstack

1

OBS-URL: https://build.opensuse.org/request/show/431193
OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/salt?expand=0&rev=61
This commit is contained in:
Dominique Leuenberger 2016-09-30 13:34:47 +00:00 committed by Git OBS Bridge
commit a3840b9cc1
39 changed files with 3683 additions and 3053 deletions

View File

@ -1,7 +1,7 @@
From f9dbfde1c3e7782d78f6b0b2b6b564f61749941f Mon Sep 17 00:00:00 2001
From 3f8257601cb1224221e10e712c0377254714f6fc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Klaus=20K=C3=A4mpf?= <kkaempf@suse.de>
Date: Wed, 20 Jan 2016 11:00:15 +0100
Subject: [PATCH 01/12] tserong@suse.com -- We don't have python-systemd, so
Subject: [PATCH 01/13] tserong@suse.com -- We don't have python-systemd, so
notify can't work
---
@ -9,7 +9,7 @@ Subject: [PATCH 01/12] tserong@suse.com -- We don't have python-systemd, so
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/pkg/salt-master.service b/pkg/salt-master.service
index 0eadf88..2b0f326 100644
index 0eadf88a3837..2b0f32623571 100644
--- a/pkg/salt-master.service
+++ b/pkg/salt-master.service
@@ -4,8 +4,7 @@ After=network.target
@ -23,5 +23,5 @@ index 0eadf88..2b0f326 100644
KillMode=process
--
2.1.4
2.8.3

View File

@ -1,7 +1,7 @@
From af193a109fcae502c4cdd47507aea9f67d809b4b Mon Sep 17 00:00:00 2001
From e86b0bf279c7faea457dfb8152fe9f5c829e42be Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Klaus=20K=C3=A4mpf?= <kkaempf@suse.de>
Date: Wed, 20 Jan 2016 11:01:06 +0100
Subject: [PATCH 02/12] Run salt master as dedicated salt user
Subject: [PATCH 02/13] Run salt master as dedicated salt user
---
conf/master | 3 ++-
@ -9,7 +9,7 @@ Subject: [PATCH 02/12] Run salt master as dedicated salt user
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/conf/master b/conf/master
index aae46ef..064828a 100644
index 1bbeb50c51b4..54d5f8f3ca75 100644
--- a/conf/master
+++ b/conf/master
@@ -25,7 +25,8 @@
@ -23,7 +23,7 @@ index aae46ef..064828a 100644
# The port used by the communication interface. The ret (return) port is the
# interface used for the file server, authentication, job returns, etc.
diff --git a/pkg/salt-common.logrotate b/pkg/salt-common.logrotate
index 3cd0023..8d970c0 100644
index 3cd002308e83..8d970c0a64d0 100644
--- a/pkg/salt-common.logrotate
+++ b/pkg/salt-common.logrotate
@@ -1,4 +1,5 @@
@ -49,5 +49,5 @@ index 3cd0023..8d970c0 100644
missingok
rotate 7
--
2.1.4
2.8.3

View File

@ -1,4 +1,4 @@
From 6035aef0c80ae12a068bee7613c5b7f7f48aa9d3 Mon Sep 17 00:00:00 2001
From cad9f1a8fda2a4d8c666abcf74e03e7c6a8eb6be Mon Sep 17 00:00:00 2001
From: Bo Maryniuk <bo@suse.de>
Date: Mon, 18 Jan 2016 16:28:48 +0100
Subject: [PATCH 03/12] Check if byte strings are properly encoded in UTF-8
@ -9,10 +9,10 @@ Rename keywords arguments variable to a default name.
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py
index fecb671..27b00d5 100644
index 7dd73dd..92c604e 100644
--- a/salt/modules/zypper.py
+++ b/salt/modules/zypper.py
@@ -164,9 +164,9 @@ def info_installed(*names, **kwargs):
@@ -335,9 +335,9 @@ def info_installed(*names, **kwargs):
summary, description.
:param errors:
@ -25,7 +25,7 @@ index fecb671..27b00d5 100644
Valid attributes are:
ignore, report
@@ -179,7 +179,8 @@ def info_installed(*names, **kwargs):
@@ -350,7 +350,8 @@ def info_installed(*names, **kwargs):
salt '*' pkg.info_installed <package1> <package2> <package3> ...
salt '*' pkg.info_installed <package1> attr=version,vendor
salt '*' pkg.info_installed <package1> <package2> <package3> ... attr=version,vendor
@ -35,7 +35,7 @@ index fecb671..27b00d5 100644
'''
ret = dict()
for pkg_name, pkg_nfo in __salt__['lowpkg.info'](*names, **kwargs).items():
@@ -190,7 +191,7 @@ def info_installed(*names, **kwargs):
@@ -361,7 +362,7 @@ def info_installed(*names, **kwargs):
# Check, if string is encoded in a proper UTF-8
value_ = value.decode('UTF-8', 'ignore').encode('UTF-8', 'ignore')
if value != value_:
@ -45,5 +45,5 @@ index fecb671..27b00d5 100644
if key == 'source_rpm':
t_nfo['source'] = value
--
2.1.4
2.9.2

View File

@ -1,7 +1,7 @@
From a2ffa8e54f3cd8dba3c4b73cad086a6b93fb3a41 Mon Sep 17 00:00:00 2001
From a831380a6d7f323c9824d26d6f6e5966b10cbc6e Mon Sep 17 00:00:00 2001
From: Michael Calmer <mc@suse.de>
Date: Fri, 4 Mar 2016 09:51:22 +0100
Subject: [PATCH 04/12] do not generate a date in a comment to prevent rebuilds
Subject: [PATCH 04/13] do not generate a date in a comment to prevent rebuilds
(bsc#969407)
---
@ -9,10 +9,10 @@ Subject: [PATCH 04/12] do not generate a date in a comment to prevent rebuilds
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/setup.py b/setup.py
index 742eae5..d2dd8f7 100755
index c84c19824a6f..819ec2b875d1 100755
--- a/setup.py
+++ b/setup.py
@@ -605,8 +605,7 @@ class Clean(clean):
@@ -618,8 +618,7 @@ class Clean(clean):
INSTALL_VERSION_TEMPLATE = '''\
@ -23,5 +23,5 @@ index 742eae5..d2dd8f7 100755
from salt.version import SaltStackVersion
--
2.1.4
2.8.3

View File

@ -1,4 +1,4 @@
From d5fc00efc2f73018c4c6bf3bea03648dfd1340fc Mon Sep 17 00:00:00 2001
From 03732c6879c45596b0a9a0e6500957ec4f9d8415 Mon Sep 17 00:00:00 2001
From: Bo Maryniuk <bo@suse.de>
Date: Thu, 17 Mar 2016 12:30:23 +0100
Subject: [PATCH 05/12] Use SHA256 hash type by default
@ -10,10 +10,10 @@ Subject: [PATCH 05/12] Use SHA256 hash type by default
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/conf/master b/conf/master
index 064828a..5e75b15 100644
index 8bbe490..118f53b 100644
--- a/conf/master
+++ b/conf/master
@@ -474,7 +474,7 @@ syndic_user: salt
@@ -509,7 +509,7 @@ syndic_user: salt
#
# Prior to changing this value, the master should be stopped and all Salt
# caches should be cleared.
@ -23,10 +23,10 @@ index 064828a..5e75b15 100644
# The buffer size in the file server can be adjusted here:
#file_buffer_size: 1048576
diff --git a/conf/minion b/conf/minion
index b408942..32b0d0a 100644
index 3ba8ba0..8e4dbd0 100644
--- a/conf/minion
+++ b/conf/minion
@@ -451,7 +451,7 @@
@@ -542,7 +542,7 @@
#
# Warning: Prior to changing this value, the minion should be stopped and all
# Salt caches should be cleared.
@ -36,10 +36,10 @@ index b408942..32b0d0a 100644
# The Salt pillar is searched for locally if file_client is set to local. If
# this is the case, and pillar data is defined, then the pillar_roots need to
diff --git a/conf/proxy b/conf/proxy
index e6ca631..e697357 100644
index 7b835e2..ab500f9 100644
--- a/conf/proxy
+++ b/conf/proxy
@@ -427,7 +427,7 @@
@@ -435,7 +435,7 @@
#
# Warning: Prior to changing this value, the minion should be stopped and all
# Salt caches should be cleared.
@ -49,5 +49,5 @@ index e6ca631..e697357 100644
# The Salt pillar is searched for locally if file_client is set to local. If
# this is the case, and pillar data is defined, then the pillar_roots need to
--
2.1.4
2.9.2

View File

@ -0,0 +1,36 @@
From 46476c37aa00d9cbbd0ac58e56b7e1c134d33b13 Mon Sep 17 00:00:00 2001
From: Christian McHugh <mchugh19@hotmail.com>
Date: Thu, 10 Mar 2016 13:25:01 -0600
Subject: [PATCH 06/13] 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 000000000000..f97120a05589
--- /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.3

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 c0aacf83fa51015fb6e50ab96204a7b3c31413a8 Mon Sep 17 00:00:00 2001
From: Bo Maryniuk <bo@suse.de>
Date: Mon, 9 May 2016 10:33:44 +0200
Subject: [PATCH 07/21] Add zypp-notify plugin
* Add unit test to the libzypp drift detector plugin
---
scripts/zypper/plugins/commit/README.md | 3 ++
scripts/zypper/plugins/commit/zyppnotify | 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/zyppnotify
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/zyppnotify b/scripts/zypper/plugins/commit/zyppnotify
new file mode 100755
index 0000000..268298b
--- /dev/null
+++ b/scripts/zypper/plugins/commit/zyppnotify
@@ -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..550403c
--- /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
+
+zyppnotify = imp.load_source('zyppnotify', os.path.sep.join(os.path.dirname(__file__).split(
+ os.path.sep)[:-2] + ['scripts', 'zypper', 'plugins', 'commit', 'zyppnotify']))
+
+@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 = zyppnotify.DriftDetector()
+ drift._get_mtime = MagicMock(return_value=123)
+ drift._get_checksum = MagicMock(return_value='deadbeef')
+ bogus_io = BogusIO()
+ with patch('zyppnotify.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.10.0

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,43 @@
From c80528b9aad5305b06a07cadf752a45392d4a147 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 08/12] 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/zypper.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py
index 92c604e..547d72b 100644
--- a/salt/modules/zypper.py
+++ b/salt/modules/zypper.py
@@ -1596,14 +1596,17 @@ def download(*packages, **kwargs):
pkg_ret = {}
for dld_result in __zypper__.xml.call('download', *packages).getElementsByTagName("download-result"):
repo = dld_result.getElementsByTagName("repository")[0]
+ path = dld_result.getElementsByTagName("localfile")[0].getAttribute("path")
pkg_info = {
'repository-name': repo.getAttribute('name'),
'repository-alias': repo.getAttribute('alias'),
+ 'path': path,
}
key = _get_first_aggregate_text(
dld_result.getElementsByTagName('name')
)
- pkg_ret[key] = pkg_info
+ if __salt__['lowpkg.checksum'](pkg_info['path']):
+ pkg_ret[key] = pkg_info
if pkg_ret:
failed = [pkg for pkg in packages if pkg not in pkg_ret]
--
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

@ -0,0 +1,26 @@
From f186137c0eb7a14697037bf46ec6a12287b9e0c9 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 09/12] unit tests for rpm.checksum() and zypper.download()
lint issue fixed
---
tests/unit/modules/zypper_test.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/tests/unit/modules/zypper_test.py b/tests/unit/modules/zypper_test.py
index 308bf94..2f2b323 100644
--- a/tests/unit/modules/zypper_test.py
+++ b/tests/unit/modules/zypper_test.py
@@ -387,6 +387,7 @@ class ZypperTestCase(TestCase):
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'
}
--
2.9.2

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

File diff suppressed because it is too large Load Diff

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,29 @@
From e3969a80bdf2d0af0c87463af859d8daf314f018 Mon Sep 17 00:00:00 2001
From: Michael Calmer <mc@suse.de>
Date: Tue, 21 Jun 2016 13:12:48 +0200
Subject: [PATCH 11/12] fix salt --summary to count not responding minions
correctly (bsc#972311)
In case a minion is not responding a dict is returned instead of a string.
---
salt/cli/salt.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/salt/cli/salt.py b/salt/cli/salt.py
index baeaf23..ec18f2e 100644
--- a/salt/cli/salt.py
+++ b/salt/cli/salt.py
@@ -264,7 +264,9 @@ class SaltCMD(parsers.SaltCMDOptionParser):
not_connected_minions = []
failed_minions = []
for each_minion in ret:
- minion_ret = ret[each_minion].get('ret')
+ minion_ret = ret[each_minion]
+ if (isinstance(minion_ret, dict) and 'ret' in minion_ret):
+ minion_ret = ret[each_minion].get('ret')
if (
isinstance(minion_ret, string_types)
and minion_ret.startswith("Minion did not return")
--
2.9.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,24 @@
From af2ff55739187c59dc04e396b7787301a49a9dba Mon Sep 17 00:00:00 2001
From: Michael Calmer <mc@suse.de>
Date: Fri, 29 Jul 2016 10:50:21 +0200
Subject: [PATCH 12/12] Run salt-api as user salt (bsc#990029)
---
pkg/salt-api.service | 1 +
1 file changed, 1 insertion(+)
diff --git a/pkg/salt-api.service b/pkg/salt-api.service
index 72379ba..9be2cb8 100644
--- a/pkg/salt-api.service
+++ b/pkg/salt-api.service
@@ -3,6 +3,7 @@ Description=The Salt API
After=network.target
[Service]
+User=salt
Type=simple
LimitNOFILE=8192
ExecStart=/usr/bin/salt-api
--
2.9.2

View File

@ -0,0 +1,25 @@
From d19c9591c1dfbafec24a7d76402dcc9e2b17b047 Mon Sep 17 00:00:00 2001
From: Bo Maryniuk <bo@suse.de>
Date: Thu, 4 Aug 2016 11:28:19 +0200
Subject: [PATCH 13/13] Deprecate status.uptime one version later
---
salt/modules/status.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/salt/modules/status.py b/salt/modules/status.py
index 029426b..cebd57b 100644
--- a/salt/modules/status.py
+++ b/salt/modules/status.py
@@ -132,7 +132,7 @@ def custom():
return ret
-@with_deprecated(globals(), "Boron")
+@with_deprecated(globals(), "Carbon")
def uptime():
'''
Return the uptime for this system.
--
2.9.2

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

@ -0,0 +1,82 @@
From f860f7ccb3dba6b8f0cef61e2d9658a3116e3c3c Mon Sep 17 00:00:00 2001
From: Bo Maryniuk <bo@suse.de>
Date: Mon, 15 Aug 2016 15:03:53 +0200
Subject: [PATCH 14/15] Add ignore_repo_failure option to suppress zypper's
exit code 106 on unavailable repos
---
salt/modules/zypper.py | 23 ++++++++++++++++++++---
1 file changed, 20 insertions(+), 3 deletions(-)
diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py
index 547d72b..1c60f0f 100644
--- a/salt/modules/zypper.py
+++ b/salt/modules/zypper.py
@@ -100,6 +100,18 @@ class _Zypper(object):
self.__no_lock = False
self.__no_raise = False
self.__refresh = False
+ self.__ignore_repo_failure = False
+
+ def __call__(self, *args, **kwargs):
+ '''
+ :param args:
+ :param kwargs:
+ :return:
+ '''
+ # Ignore exit code for 106 (repo is not available)
+ if 'no_repo_failure' in kwargs:
+ self.__ignore_repo_failure = kwargs['no_repo_failure']
+ return self
def __getattr__(self, item):
'''
@@ -275,7 +287,7 @@ class _Zypper(object):
__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:
+ if self.error_msg and not self.__no_raise and not self.__ignore_repo_failure:
raise CommandExecutionError('Zypper command failure: {0}'.format(self.error_msg))
return self._is_xml_mode() and dom.parseString(self.__call_result['stdout']) or self.__call_result['stdout']
@@ -863,6 +875,7 @@ def install(name=None,
downloadonly=None,
skip_verify=False,
version=None,
+ ignore_repo_failure=False,
**kwargs):
'''
Install the passed package(s), add refresh=True to force a 'zypper refresh'
@@ -929,6 +942,10 @@ def install(name=None,
salt '*' pkg.install sources='[{"foo": "salt://foo.rpm"},{"bar": "salt://bar.rpm"}]'
+ ignore_repo_failure
+ Zypper returns error code 106 if one of the repositories are not available for various reasons.
+ In case to set strict check, this parameter needs to be set to True. Default: False.
+
Returns a dict containing the new package names and versions::
@@ -1000,7 +1017,7 @@ def install(name=None,
while targets:
cmd = cmd_install + targets[:500]
targets = targets[500:]
- for line in __zypper__.call(*cmd).splitlines():
+ for line in __zypper__(no_repo_failure=ignore_repo_failure).call(*cmd).splitlines():
match = re.match(r"^The selected package '([^']+)'.+has lower version", line)
if match:
downgrades.append(match.group(1))
@@ -1008,7 +1025,7 @@ def install(name=None,
while downgrades:
cmd = cmd_install + ['--force'] + downgrades[:500]
downgrades = downgrades[500:]
- __zypper__.call(*cmd)
+ __zypper__(no_repo_failure=ignore_repo_failure).call(*cmd)
__context__.pop('pkg.list_pkgs', None)
new = list_pkgs()
--
2.9.3

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

@ -0,0 +1,78 @@
From 0809c60c693eb5d2e9569c24d995818097c6920d Mon Sep 17 00:00:00 2001
From: Bo Maryniuk <bo@suse.de>
Date: Mon, 15 Aug 2016 17:35:01 +0200
Subject: [PATCH 15/15] Remove zypper's raise exception if mod_repo has no
arguments and/or no changes
* Fix Unit test for suppressing the exception removal on non-modified repos
---
salt/modules/zypper.py | 11 +++++++----
tests/unit/modules/zypper_test.py | 15 +++------------
2 files changed, 10 insertions(+), 16 deletions(-)
diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py
index 1c60f0f..1ec903e 100644
--- a/salt/modules/zypper.py
+++ b/salt/modules/zypper.py
@@ -824,6 +824,7 @@ def mod_repo(repo, **kwargs):
cmd_opt = global_cmd_opt + ['mr'] + cmd_opt + [repo]
__zypper__.refreshable.xml.call(*cmd_opt)
+ comment = None
if call_refresh:
# when used with "zypper ar --refresh" or "zypper mr --refresh"
# --gpg-auto-import-keys is not doing anything
@@ -831,11 +832,13 @@ def mod_repo(repo, **kwargs):
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'
- )
+ comment = 'Specified arguments did not result in modification of repo'
+
+ repo = get_repo(repo)
+ if comment:
+ repo['comment'] = comment
- return get_repo(repo)
+ return repo
def refresh_db():
diff --git a/tests/unit/modules/zypper_test.py b/tests/unit/modules/zypper_test.py
index 2f2b323..7caa707 100644
--- a/tests/unit/modules/zypper_test.py
+++ b/tests/unit/modules/zypper_test.py
@@ -73,7 +73,7 @@ class ZypperTestCase(TestCase):
self.zypper_patcher_config = {
'_get_configured_repos': Mock(side_effect=side_effect),
'__zypper__': Mock(),
- 'get_repo': Mock()
+ 'get_repo': Mock(return_value={})
}
def test_list_upgrades(self):
@@ -493,17 +493,8 @@ class ZypperTestCase(TestCase):
'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'})
-
+ self.assertEqual(zypper.mod_repo(name, **{'url': url}),
+ {'comment': 'Specified arguments did not result in modification of repo'})
zypper.__zypper__.xml.call.assert_not_called()
zypper.__zypper__.refreshable.xml.call.assert_not_called()
--
2.9.3

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

@ -0,0 +1,83 @@
From 50865e300e6e90c5cc80c8878949a2f3bcaaeeec Mon Sep 17 00:00:00 2001
From: Bo Maryniuk <bo@suse.de>
Date: Thu, 25 Aug 2016 16:47:08 +0200
Subject: [PATCH 16/16] Improve Mock to be flexible and able to mock methods
from the mocked modules
* Configure importing Mock to handle 'total' method from psutils properly
---
doc/conf.py | 37 +++++++++++++++++++++++++------------
1 file changed, 25 insertions(+), 12 deletions(-)
diff --git a/doc/conf.py b/doc/conf.py
index 9cefed8..b73ca2a 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -15,31 +15,40 @@ from sphinx.directives import TocTree
# pylint: disable=R0903
class Mock(object):
'''
- Mock out specified imports
+ Mock out specified imports.
This allows autodoc to do its thing without having oodles of req'd
installed libs. This doesn't work with ``import *`` imports.
+ This Mock class can be configured to return a specific values at specific names, if required.
+
http://read-the-docs.readthedocs.org/en/latest/faq.html#i-get-import-errors-on-libraries-that-depend-on-c-modules
'''
- def __init__(self, *args, **kwargs):
- pass
+ def __init__(self, mapping=None, *args, **kwargs):
+ """
+ Mapping allows to bypass the Mock object, but actually assign
+ a specific value, expected by a specific attribute returned.
+ """
+ self.__mapping = mapping or {}
__all__ = []
def __call__(self, *args, **kwargs):
- ret = Mock()
# If mocked function is used as a decorator, expose decorated function.
# if args and callable(args[-1]):
# functools.update_wrapper(ret, args[0])
- return ret
-
- @classmethod
- def __getattr__(cls, name):
- if name in ('__file__', '__path__'):
- return '/dev/null'
+ return Mock(mapping=self.__mapping)
+
+ def __getattr__(self, name):
+ #__mapping = {'total': 0}
+ data = None
+ if name in self.__mapping:
+ data = self.__mapping.get(name)
+ elif name in ('__file__', '__path__'):
+ data = '/dev/null'
else:
- return Mock()
+ data = Mock(mapping=self.__mapping)
+ return data
# pylint: enable=R0903
MOCK_MODULES = [
@@ -133,7 +142,11 @@ MOCK_MODULES = [
]
for mod_name in MOCK_MODULES:
- sys.modules[mod_name] = Mock()
+ if mod_name == 'psutil':
+ mock = Mock(mapping={'total': 0}) # Otherwise it will crash Sphinx
+ else:
+ mock = Mock()
+ sys.modules[mod_name] = mock
def mock_decorator_with_params(*oargs, **okwargs):
'''
--
2.9.3

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

View File

@ -0,0 +1,35 @@
From e9b4a199f48abc94be71082c56b6b059c6694dc0 Mon Sep 17 00:00:00 2001
From: Eric Jackson <swiftgist@gmail.com>
Date: Tue, 30 Aug 2016 15:47:07 -0400
Subject: [PATCH 17/17] Check for single quote before splitting on single quote
Signed-off-by: Eric Jackson <ejackson@suse.com>
Lint for #35916
Merges #35916
---
salt/modules/zypper.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py
index 1ec903e..4bb9a09 100644
--- a/salt/modules/zypper.py
+++ b/salt/modules/zypper.py
@@ -859,11 +859,11 @@ def refresh_db():
for line in out.splitlines():
if not line:
continue
- if line.strip().startswith('Repository'):
+ if line.strip().startswith('Repository') and '\'' in line:
key = line.split('\'')[1].strip()
if 'is up to date' in line:
ret[key] = False
- elif line.strip().startswith('Building'):
+ elif line.strip().startswith('Building') and '\'' in line:
key = line.split('\'')[1].strip()
if 'done' in line:
ret[key] = True
--
2.8.2

View File

@ -0,0 +1,927 @@
From e5fc36b5fad0683f57022bf2f3c63f453cda5e8d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?=
<psuarezhernandez@suse.com>
Date: Tue, 6 Sep 2016 11:21:05 +0100
Subject: [PATCH 18/19] Unit tests fixes for 2016.3.2
* Fixing skipped boto tests to prevent errors if boto3 does not exists.
* Fix tests that assert CommandExecutionError (#32485)
Trying to assert that an exception was raised using
helper_open.write.assertRaises() is bogus--there is no such method. Use
standard unittest.assertRaises() instead.
* Skip utils_test if timelib is not installed (#32699)
date_cast() throws a RuntimeError, not an ImportError
* Fix tests (#35693)
Fix tests/unit/modules/useradd_test.py::UserAddTestCase::test_info
Fix unit/pyobjects_test.py::MapTests::test_map
Fix tests/unit/pyobjects_test.py::RendererTests::test_extend
Fix tests/unit/pyobjects_test.py::RendererTests::test_requisite_implicit_list
* Fix tests to prevent errors when libcloud is not present
* Fixed _interfaces_ifconfig output for SunOS test
* Fix PortageConfigTestCase in case of portage is not present
* Rename dockerio.py unit tests to dockerio_test.py
These tests have never run automatically because of an incorrect file name.
Added a skipIf on these tests as they are currently non-functioning and the
module they're testing has been deprecated.
* Prevent tests failures if boto does not exists
---
salt/modules/boto_elb.py | 2 +-
salt/modules/linux_sysctl.py | 6 +-
tests/unit/cloud/clouds/dimensiondata_test.py | 10 ++-
tests/unit/cloud/clouds/gce_test.py | 10 ++-
tests/unit/modules/boto_cloudtrail_test.py | 10 +--
tests/unit/modules/boto_iot_test.py | 10 +--
tests/unit/modules/boto_lambda_test.py | 10 +--
tests/unit/modules/boto_s3_bucket_test.py | 10 +--
tests/unit/modules/boto_secgroup_test.py | 1 +
tests/unit/modules/boto_vpc_test.py | 14 ++--
tests/unit/modules/linux_sysctl_test.py | 19 +++--
tests/unit/modules/mac_sysctl_test.py | 10 +--
tests/unit/modules/mount_test.py | 14 ++--
tests/unit/modules/portage_config.py | 10 ++-
tests/unit/modules/puppet_test.py | 15 ++--
tests/unit/modules/useradd_test.py | 6 +-
tests/unit/pyobjects_test.py | 11 +++
tests/unit/states/boto_cloudtrail_test.py | 10 +--
tests/unit/states/boto_iot_test.py | 10 +--
tests/unit/states/boto_lambda_test.py | 10 +--
tests/unit/states/boto_s3_bucket_test.py | 10 +--
tests/unit/states/dockerio.py | 112 -------------------------
tests/unit/states/dockerio_test.py | 113 ++++++++++++++++++++++++++
tests/unit/utils/network.py | 12 +--
tests/unit/utils/utils_test.py | 11 +--
25 files changed, 244 insertions(+), 212 deletions(-)
delete mode 100644 tests/unit/states/dockerio.py
create mode 100644 tests/unit/states/dockerio_test.py
diff --git a/salt/modules/boto_elb.py b/salt/modules/boto_elb.py
index 31df1fc..162abcd 100644
--- a/salt/modules/boto_elb.py
+++ b/salt/modules/boto_elb.py
@@ -57,6 +57,7 @@ log = logging.getLogger(__name__)
# Import third party libs
try:
import boto
+ import boto.ec2 # pylint: enable=unused-import
# connection settings were added in 2.33.0
required_boto_version = '2.33.0'
if (_LooseVersion(boto.__version__) <
@@ -64,7 +65,6 @@ try:
msg = 'boto_elb requires boto {0}.'.format(required_boto_version)
logging.debug(msg)
raise ImportError()
- import boto.ec2
from boto.ec2.elb import HealthCheck
from boto.ec2.elb.attributes import AccessLogAttribute
from boto.ec2.elb.attributes import ConnectionDrainingAttribute
diff --git a/salt/modules/linux_sysctl.py b/salt/modules/linux_sysctl.py
index b016ca6..7702d52 100644
--- a/salt/modules/linux_sysctl.py
+++ b/salt/modules/linux_sysctl.py
@@ -41,7 +41,11 @@ def _check_systemd_salt_config():
sysctl_dir = os.path.split(conf)[0]
if not os.path.exists(sysctl_dir):
os.makedirs(sysctl_dir)
- salt.utils.fopen(conf, 'w').close()
+ try:
+ salt.utils.fopen(conf, 'w').close()
+ except (IOError, OSError):
+ msg = 'Could not create file: {0}'
+ raise CommandExecutionError(msg.format(conf))
return conf
diff --git a/tests/unit/cloud/clouds/dimensiondata_test.py b/tests/unit/cloud/clouds/dimensiondata_test.py
index aa7f2c0..ee01d65 100644
--- a/tests/unit/cloud/clouds/dimensiondata_test.py
+++ b/tests/unit/cloud/clouds/dimensiondata_test.py
@@ -8,7 +8,13 @@
# Import Python libs
from __future__ import absolute_import
-import libcloud.security
+
+try:
+ import libcloud.security
+ HAS_LIBCLOUD = True
+except ImportError:
+ HAS_LIBCLOUD = False
+
import platform
import os
@@ -44,7 +50,7 @@ ON_SUSE = True if 'SuSE' in platform.dist() else False
ON_MAC = True if 'Darwin' in platform.system() else False
if not os.path.exists('/etc/ssl/certs/YaST-CA.pem') and ON_SUSE:
- if os.path.isfile('/etc/ssl/ca-bundle.pem'):
+ if os.path.isfile('/etc/ssl/ca-bundle.pem') and HAS_LIBCLOUD:
libcloud.security.CA_CERTS_PATH.append('/etc/ssl/ca-bundle.pem')
else:
HAS_CERTS = False
diff --git a/tests/unit/cloud/clouds/gce_test.py b/tests/unit/cloud/clouds/gce_test.py
index 87824eb..c90f8ab 100644
--- a/tests/unit/cloud/clouds/gce_test.py
+++ b/tests/unit/cloud/clouds/gce_test.py
@@ -8,7 +8,13 @@
# Import Python libs
from __future__ import absolute_import
-import libcloud.security
+
+try:
+ import libcloud.security
+ HAS_LIBCLOUD = True
+except ImportError:
+ HAS_LIBCLOUD = False
+
import platform
import os
@@ -51,7 +57,7 @@ ON_SUSE = True if 'SuSE' in platform.dist() else False
ON_MAC = True if 'Darwin' in platform.system() else False
if not os.path.exists('/etc/ssl/certs/YaST-CA.pem') and ON_SUSE:
- if os.path.isfile('/etc/ssl/ca-bundle.pem'):
+ if os.path.isfile('/etc/ssl/ca-bundle.pem') and HAS_LIBCLOUD:
libcloud.security.CA_CERTS_PATH.append('/etc/ssl/ca-bundle.pem')
else:
HAS_CERTS = False
diff --git a/tests/unit/modules/boto_cloudtrail_test.py b/tests/unit/modules/boto_cloudtrail_test.py
index 2f86101..264a795 100644
--- a/tests/unit/modules/boto_cloudtrail_test.py
+++ b/tests/unit/modules/boto_cloudtrail_test.py
@@ -103,6 +103,11 @@ if _has_required_boto():
StopLoggingTime=None)
+@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
+@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
+ ' or equal to version {0}'
+ .format(required_boto3_version))
+@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoCloudTrailTestCaseBase(TestCase):
conn = None
@@ -128,11 +133,6 @@ class BotoCloudTrailTestCaseMixin(object):
pass
-@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
-@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
- ' or equal to version {0}'
- .format(required_boto3_version))
-@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoCloudTrailTestCase(BotoCloudTrailTestCaseBase, BotoCloudTrailTestCaseMixin):
'''
TestCase for salt.modules.boto_cloudtrail module
diff --git a/tests/unit/modules/boto_iot_test.py b/tests/unit/modules/boto_iot_test.py
index 73c362f..520bfe9 100644
--- a/tests/unit/modules/boto_iot_test.py
+++ b/tests/unit/modules/boto_iot_test.py
@@ -103,6 +103,11 @@ if _has_required_boto():
ruleDisabled=True)
+@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
+@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
+ ' or equal to version {0}'
+ .format(required_boto3_version))
+@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoIoTTestCaseBase(TestCase):
conn = None
@@ -128,11 +133,6 @@ class BotoIoTTestCaseMixin(object):
pass
-@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
-@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
- ' or equal to version {0}'
- .format(required_boto3_version))
-@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoIoTPolicyTestCase(BotoIoTTestCaseBase, BotoIoTTestCaseMixin):
'''
TestCase for salt.modules.boto_iot module
diff --git a/tests/unit/modules/boto_lambda_test.py b/tests/unit/modules/boto_lambda_test.py
index 01ca245..ad7fb33 100644
--- a/tests/unit/modules/boto_lambda_test.py
+++ b/tests/unit/modules/boto_lambda_test.py
@@ -109,6 +109,11 @@ def _has_required_boto():
return True
+@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
+@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
+ ' or equal to version {0}'
+ .format(required_boto3_version))
+@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoLambdaTestCaseBase(TestCase):
conn = None
@@ -145,11 +150,6 @@ class BotoLambdaTestCaseMixin(object):
pass
-@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
-@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
- ' or equal to version {0}'
- .format(required_boto3_version))
-@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoLambdaFunctionTestCase(BotoLambdaTestCaseBase, BotoLambdaTestCaseMixin):
'''
TestCase for salt.modules.boto_lambda module
diff --git a/tests/unit/modules/boto_s3_bucket_test.py b/tests/unit/modules/boto_s3_bucket_test.py
index f4b1992..5e7d6be 100644
--- a/tests/unit/modules/boto_s3_bucket_test.py
+++ b/tests/unit/modules/boto_s3_bucket_test.py
@@ -205,6 +205,11 @@ if _has_required_boto():
}
+@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
+@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
+ ' or equal to version {0}'
+ .format(required_boto3_version))
+@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoS3BucketTestCaseBase(TestCase):
conn = None
@@ -230,11 +235,6 @@ class BotoS3BucketTestCaseMixin(object):
pass
-@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
-@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
- ' or equal to version {0}'
- .format(required_boto3_version))
-@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoS3BucketTestCase(BotoS3BucketTestCaseBase, BotoS3BucketTestCaseMixin):
'''
TestCase for salt.modules.boto_s3_bucket module
diff --git a/tests/unit/modules/boto_secgroup_test.py b/tests/unit/modules/boto_secgroup_test.py
index cc88568..7fd51ad 100644
--- a/tests/unit/modules/boto_secgroup_test.py
+++ b/tests/unit/modules/boto_secgroup_test.py
@@ -23,6 +23,7 @@ import salt.loader
from salt.ext.six.moves import range # pylint: disable=redefined-builtin
try:
import boto
+ import boto.ec2 # pylint: enable=unused-import
HAS_BOTO = True
except ImportError:
HAS_BOTO = False
diff --git a/tests/unit/modules/boto_vpc_test.py b/tests/unit/modules/boto_vpc_test.py
index 64c7976..162bcae 100644
--- a/tests/unit/modules/boto_vpc_test.py
+++ b/tests/unit/modules/boto_vpc_test.py
@@ -124,6 +124,13 @@ def _has_required_moto():
context = {}
+@skipIf(NO_MOCK, NO_MOCK_REASON)
+@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
+@skipIf(HAS_MOTO is False, 'The moto module must be installed.')
+@skipIf(_has_required_boto() is False, 'The boto module must be greater than'
+ ' or equal to version {0}'
+ .format(required_boto_version))
+@skipIf(_has_required_moto() is False, 'The moto version must be >= to version {0}'.format(required_moto_version))
class BotoVpcTestCaseBase(TestCase):
def setUp(self):
boto_vpc.__context__ = {}
@@ -249,13 +256,6 @@ class BotoVpcTestCaseMixin(object):
return rtbl
-@skipIf(NO_MOCK, NO_MOCK_REASON)
-@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
-@skipIf(HAS_MOTO is False, 'The moto module must be installed.')
-@skipIf(_has_required_boto() is False, 'The boto module must be greater than'
- ' or equal to version {0}'
- .format(required_boto_version))
-@skipIf(_has_required_moto() is False, 'The moto version must be >= to version {0}'.format(required_moto_version))
class BotoVpcTestCase(BotoVpcTestCaseBase, BotoVpcTestCaseMixin):
'''
TestCase for salt.modules.boto_vpc module
diff --git a/tests/unit/modules/linux_sysctl_test.py b/tests/unit/modules/linux_sysctl_test.py
index 89bea83..1eca7d5 100644
--- a/tests/unit/modules/linux_sysctl_test.py
+++ b/tests/unit/modules/linux_sysctl_test.py
@@ -84,17 +84,22 @@ class LinuxSysctlTestCase(TestCase):
self.assertEqual(linux_sysctl.assign(
'net.ipv4.ip_forward', 1), ret)
- @patch('os.path.isfile', MagicMock(return_value=False))
def test_persist_no_conf_failure(self):
'''
Tests adding of config file failure
'''
- with patch('salt.utils.fopen', mock_open()) as m_open:
- helper_open = m_open()
- helper_open.write.assertRaises(CommandExecutionError,
- linux_sysctl.persist,
- 'net.ipv4.ip_forward',
- 1, config=None)
+ asn_cmd = {'pid': 1337, 'retcode': 0,
+ 'stderr': "sysctl: permission denied", 'stdout': ''}
+ mock_asn_cmd = MagicMock(return_value=asn_cmd)
+ cmd = "sysctl -w net.ipv4.ip_forward=1"
+ mock_cmd = MagicMock(return_value=cmd)
+ with patch.dict(linux_sysctl.__salt__, {'cmd.run_stdout': mock_cmd,
+ 'cmd.run_all': mock_asn_cmd}):
+ with patch('salt.utils.fopen', mock_open()) as m_open:
+ self.assertRaises(CommandExecutionError,
+ linux_sysctl.persist,
+ 'net.ipv4.ip_forward',
+ 1, config=None)
@patch('os.path.isfile', MagicMock(return_value=False))
@patch('os.path.exists', MagicMock(return_value=True))
diff --git a/tests/unit/modules/mac_sysctl_test.py b/tests/unit/modules/mac_sysctl_test.py
index e90ec64..533397b 100644
--- a/tests/unit/modules/mac_sysctl_test.py
+++ b/tests/unit/modules/mac_sysctl_test.py
@@ -72,11 +72,11 @@ class DarwinSysctlTestCase(TestCase):
Tests adding of config file failure
'''
with patch('salt.utils.fopen', mock_open()) as m_open:
- helper_open = m_open()
- helper_open.write.assertRaises(CommandExecutionError,
- mac_sysctl.persist,
- 'net.inet.icmp.icmplim',
- 50, config=None)
+ m_open.side_effect = IOError(13, 'Permission denied', '/file')
+ self.assertRaises(CommandExecutionError,
+ mac_sysctl.persist,
+ 'net.inet.icmp.icmplim',
+ 50, config=None)
@patch('os.path.isfile', MagicMock(return_value=False))
def test_persist_no_conf_success(self):
diff --git a/tests/unit/modules/mount_test.py b/tests/unit/modules/mount_test.py
index 290c368..b2cf904 100644
--- a/tests/unit/modules/mount_test.py
+++ b/tests/unit/modules/mount_test.py
@@ -141,10 +141,10 @@ class MountTestCase(TestCase):
with patch.dict(mount.__grains__, {'kernel': ''}):
with patch.object(mount, 'fstab', mock_fstab):
with patch('salt.utils.fopen', mock_open()) as m_open:
- helper_open = m_open()
- helper_open.write.assertRaises(CommandExecutionError,
- mount.rm_fstab,
- config=None)
+ m_open.side_effect = IOError(13, 'Permission denied:', '/file')
+ self.assertRaises(CommandExecutionError,
+ mount.rm_fstab,
+ 'name', 'device')
def test_set_fstab(self):
'''
@@ -180,11 +180,7 @@ class MountTestCase(TestCase):
mock = MagicMock(return_value={'name': 'name'})
with patch.object(mount, 'fstab', mock):
- with patch('salt.utils.fopen', mock_open()) as m_open:
- helper_open = m_open()
- helper_open.write.assertRaises(CommandExecutionError,
- mount.rm_automaster,
- 'name', 'device')
+ self.assertTrue(mount.rm_automaster('name', 'device'))
def test_set_automaster(self):
'''
diff --git a/tests/unit/modules/portage_config.py b/tests/unit/modules/portage_config.py
index 8da1ebe..6275442 100644
--- a/tests/unit/modules/portage_config.py
+++ b/tests/unit/modules/portage_config.py
@@ -11,7 +11,7 @@ from __future__ import absolute_import
# Import Salt Testing libs
from salttesting import skipIf, TestCase
from salttesting.helpers import ensure_in_syspath
-from salttesting.mock import NO_MOCK, NO_MOCK_REASON
+from salttesting.mock import NO_MOCK, NO_MOCK_REASON, MagicMock
ensure_in_syspath('../../')
# Import salt libs
@@ -20,6 +20,10 @@ from salt.modules import portage_config
@skipIf(NO_MOCK, NO_MOCK_REASON)
class PortageConfigTestCase(TestCase):
+ class DummyAtom(object):
+ def __init__(self, atom):
+ self.cp, self.repo = atom.split("::") if "::" in atom else (atom, None)
+
def test_get_config_file_wildcards(self):
pairs = [
('*/*::repo', '/etc/portage/package.mask/repo'),
@@ -29,7 +33,11 @@ class PortageConfigTestCase(TestCase):
('cat/pkg::repo', '/etc/portage/package.mask/cat/pkg'),
]
+ portage_config.portage = MagicMock()
for (atom, expected) in pairs:
+ dummy_atom = self.DummyAtom(atom)
+ portage_config.portage.dep.Atom = MagicMock(return_value=dummy_atom)
+ portage_config._p_to_cp = MagicMock(return_value=dummy_atom.cp)
self.assertEqual(portage_config._get_config_file('mask', atom), expected)
if __name__ == '__main__':
diff --git a/tests/unit/modules/puppet_test.py b/tests/unit/modules/puppet_test.py
index 02bc2e1..2cdd696 100644
--- a/tests/unit/modules/puppet_test.py
+++ b/tests/unit/modules/puppet_test.py
@@ -85,10 +85,12 @@ class PuppetTestCase(TestCase):
with patch('salt.utils.fopen', mock_open()):
self.assertTrue(puppet.disable())
- with patch('salt.utils.fopen', mock_open()) as m_open:
- helper_open = m_open()
- helper_open.write.assertRaises(CommandExecutionError,
- puppet.disable)
+ try:
+ with patch('salt.utils.fopen', mock_open()) as m_open:
+ m_open.side_effect = IOError(13, 'Permission denied:', '/file')
+ self.assertRaises(CommandExecutionError, puppet.disable)
+ except StopIteration:
+ pass
def test_status(self):
'''
@@ -145,9 +147,8 @@ class PuppetTestCase(TestCase):
self.assertDictEqual(puppet.summary(), {'resources': 1})
with patch('salt.utils.fopen', mock_open()) as m_open:
- helper_open = m_open()
- helper_open.write.assertRaises(CommandExecutionError,
- puppet.summary)
+ m_open.side_effect = IOError(13, 'Permission denied:', '/file')
+ self.assertRaises(CommandExecutionError, puppet.summary)
def test_plugin_sync(self):
'''
diff --git a/tests/unit/modules/useradd_test.py b/tests/unit/modules/useradd_test.py
index 7e646b6..cc9e610 100644
--- a/tests/unit/modules/useradd_test.py
+++ b/tests/unit/modules/useradd_test.py
@@ -326,7 +326,7 @@ class UserAddTestCase(TestCase):
'''
Test the user information
'''
- self.assertEqual(useradd.info('salt'), {})
+ self.assertEqual(useradd.info('username-that-doesnt-exist'), {})
mock = MagicMock(return_value=pwd.struct_passwd(('_TEST_GROUP',
'*',
@@ -336,9 +336,7 @@ class UserAddTestCase(TestCase):
'/var/virusmails',
'/usr/bin/false')))
with patch.object(pwd, 'getpwnam', mock):
- mock = MagicMock(return_value='Group Name')
- with patch.object(useradd, 'list_groups', mock):
- self.assertEqual(useradd.info('salt')['name'], '_TEST_GROUP')
+ self.assertEqual(useradd.info('username-that-doesnt-exist')['name'], '_TEST_GROUP')
# 'list_groups' function tests: 1
diff --git a/tests/unit/pyobjects_test.py b/tests/unit/pyobjects_test.py
index f1c3e29..3eb4bd5 100644
--- a/tests/unit/pyobjects_test.py
+++ b/tests/unit/pyobjects_test.py
@@ -54,10 +54,18 @@ include('http')
extend_template = '''#!pyobjects
include('http')
+
+from salt.utils.pyobjects import StateFactory
+Service = StateFactory('service')
+
Service.running(extend('apache'), watch=[{'file': '/etc/file'}])
'''
map_template = '''#!pyobjects
+from salt.utils.pyobjects import StateFactory
+Service = StateFactory('service')
+
+
class Samba(Map):
__merge__ = 'samba:lookup'
@@ -127,6 +135,9 @@ from salt://password.sls import password
'''
requisite_implicit_list_template = '''#!pyobjects
+from salt.utils.pyobjects import StateFactory
+Service = StateFactory('service')
+
with Pkg.installed("pkg"):
Service.running("service", watch=File("file"), require=Cmd("cmd"))
'''
diff --git a/tests/unit/states/boto_cloudtrail_test.py b/tests/unit/states/boto_cloudtrail_test.py
index 48fbd32..9e6dd95 100644
--- a/tests/unit/states/boto_cloudtrail_test.py
+++ b/tests/unit/states/boto_cloudtrail_test.py
@@ -104,6 +104,11 @@ if _has_required_boto():
StopLoggingTime=None)
+@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
+@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
+ ' or equal to version {0}'
+ .format(required_boto3_version))
+@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoCloudTrailStateTestCaseBase(TestCase):
conn = None
@@ -124,11 +129,6 @@ class BotoCloudTrailStateTestCaseBase(TestCase):
session_instance.client.return_value = self.conn
-@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
-@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
- ' or equal to version {0}'
- .format(required_boto3_version))
-@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoCloudTrailTestCase(BotoCloudTrailStateTestCaseBase, BotoCloudTrailTestCaseMixin):
'''
TestCase for salt.modules.boto_cloudtrail state.module
diff --git a/tests/unit/states/boto_iot_test.py b/tests/unit/states/boto_iot_test.py
index 8c2549d..81d68c8 100644
--- a/tests/unit/states/boto_iot_test.py
+++ b/tests/unit/states/boto_iot_test.py
@@ -103,6 +103,11 @@ if _has_required_boto():
principal = 'arn:aws:iot:us-east-1:1234:cert/21fc104aaaf6043f5756c1b57bda84ea8395904c43f28517799b19e4c42514'
+@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
+@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
+ ' or equal to version {0}'
+ .format(required_boto3_version))
+@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoIoTStateTestCaseBase(TestCase):
conn = None
@@ -123,11 +128,6 @@ class BotoIoTStateTestCaseBase(TestCase):
session_instance.client.return_value = self.conn
-@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
-@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
- ' or equal to version {0}'
- .format(required_boto3_version))
-@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoIoTPolicyTestCase(BotoIoTStateTestCaseBase, BotoIoTTestCaseMixin):
'''
TestCase for salt.modules.boto_iot state.module
diff --git a/tests/unit/states/boto_lambda_test.py b/tests/unit/states/boto_lambda_test.py
index 4557aed..7b02391 100644
--- a/tests/unit/states/boto_lambda_test.py
+++ b/tests/unit/states/boto_lambda_test.py
@@ -101,6 +101,11 @@ def _has_required_boto():
return True
+@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
+@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
+ ' or equal to version {0}'
+ .format(required_boto3_version))
+@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoLambdaStateTestCaseBase(TestCase):
conn = None
@@ -121,11 +126,6 @@ class BotoLambdaStateTestCaseBase(TestCase):
session_instance.client.return_value = self.conn
-@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
-@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
- ' or equal to version {0}'
- .format(required_boto3_version))
-@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoLambdaFunctionTestCase(BotoLambdaStateTestCaseBase, BotoLambdaTestCaseMixin):
'''
TestCase for salt.modules.boto_lambda state.module
diff --git a/tests/unit/states/boto_s3_bucket_test.py b/tests/unit/states/boto_s3_bucket_test.py
index 4049e9a..03c406f 100644
--- a/tests/unit/states/boto_s3_bucket_test.py
+++ b/tests/unit/states/boto_s3_bucket_test.py
@@ -277,6 +277,11 @@ if _has_required_boto():
}
+@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
+@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
+ ' or equal to version {0}'
+ .format(required_boto3_version))
+@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoS3BucketStateTestCaseBase(TestCase):
conn = None
@@ -297,11 +302,6 @@ class BotoS3BucketStateTestCaseBase(TestCase):
session_instance.client.return_value = self.conn
-@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
-@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
- ' or equal to version {0}'
- .format(required_boto3_version))
-@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoS3BucketTestCase(BotoS3BucketStateTestCaseBase, BotoS3BucketTestCaseMixin):
'''
TestCase for salt.modules.boto_s3_bucket state.module
diff --git a/tests/unit/states/dockerio.py b/tests/unit/states/dockerio.py
deleted file mode 100644
index c73b633..0000000
--- a/tests/unit/states/dockerio.py
+++ /dev/null
@@ -1,112 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Import Python libs
-from __future__ import absolute_import
-from contextlib import contextmanager
-
-# Import Salt Testing libs
-from salttesting import skipIf, TestCase
-from salttesting.mock import NO_MOCK, NO_MOCK_REASON, MagicMock
-
-
-@contextmanager
-def provision_state(module, fixture):
- previous_dict = getattr(module, '__salt__', {}).copy()
- try:
- module.__dict__.setdefault('__salt__', {}).update(fixture)
- yield
- finally:
- setattr(module, '__salt__', previous_dict)
-
-
-@skipIf(NO_MOCK, NO_MOCK_REASON)
-class DockerStateTestCase(TestCase):
- def test_docker_run_success(self):
- from salt.states import dockerio
- salt_fixture = {'docker.retcode': MagicMock(return_value=0),
- 'docker.run_all': MagicMock(
- return_value={'stdout': '.\n..\n',
- 'stderr': '',
- 'status': True,
- 'comment': 'Success',
- 'retcode': 0})}
-
- with provision_state(dockerio, salt_fixture):
- result = dockerio.run('ls /', 'ubuntu')
-
- self.assertEqual(result, {'name': 'ls /',
- 'result': True,
- 'comment': 'Success',
- 'changes': {}})
-
- def test_docker_run_failure(self):
- from salt.states import dockerio
- salt_fixture = {'docker.retcode': MagicMock(return_value=0),
- 'docker.run_all': MagicMock(
- return_value={'stdout': '',
- 'stderr': 'Error',
- 'status': False,
- 'comment': 'Failure',
- 'retcode': 1})}
-
- with provision_state(dockerio, salt_fixture):
- result = dockerio.run('ls /', 'ubuntu')
-
- self.assertEqual(result, {'name': 'ls /',
- 'result': False,
- 'comment': 'Failure',
- 'changes': {}})
-
- def test_docker_run_onlyif(self):
- from salt.states import dockerio
- salt_fixture = {'docker.retcode': MagicMock(return_value=1),
- 'docker.run_all': None}
- with provision_state(dockerio, salt_fixture):
- result = dockerio.run('ls /', 'ubuntu',
- onlyif='ls -l')
- self.assertEqual(result, {'name': 'ls /',
- 'result': True,
- 'comment': 'onlyif execution failed',
- 'changes': {}})
-
- def test_docker_run_unless(self):
- from salt.states import dockerio
- salt_fixture = {'docker.retcode': MagicMock(return_value=0),
- 'docker.run_all': None}
- with provision_state(dockerio, salt_fixture):
- result = dockerio.run('ls /', 'ubuntu',
- unless='ls -l')
- self.assertEqual(result, {'name': 'ls /',
- 'result': True,
- 'comment': 'unless execution succeeded',
- 'changes': {}})
-
- def test_docker_run_docked_onlyif(self):
- from salt.states import dockerio
- salt_fixture = {'docker.retcode': MagicMock(return_value=1),
- 'docker.run_all': None}
- with provision_state(dockerio, salt_fixture):
- result = dockerio.run('ls /', 'ubuntu',
- docked_onlyif='ls -l')
- self.assertEqual(result, {'name': 'ls /',
- 'result': True,
- 'comment': 'docked_onlyif execution failed',
- 'changes': {}})
-
- def test_docker_run_docked_unless(self):
- from salt.states import dockerio
- salt_fixture = {'docker.retcode': MagicMock(return_value=0),
- 'docker.run_all': None}
- with provision_state(dockerio, salt_fixture):
- result = dockerio.run('ls /', 'ubuntu',
- docked_unless='ls -l')
- self.assertEqual(result, {'name': 'ls /',
- 'result': True,
- 'comment': ('docked_unless execution'
- ' succeeded'),
- 'changes': {}})
-
-
-if __name__ == '__main__':
- from integration import run_tests
- run_tests(DockerStateTestCase, needs_daemon=False)
diff --git a/tests/unit/states/dockerio_test.py b/tests/unit/states/dockerio_test.py
new file mode 100644
index 0000000..54f51be
--- /dev/null
+++ b/tests/unit/states/dockerio_test.py
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+
+# Import Python libs
+from __future__ import absolute_import
+from contextlib import contextmanager
+
+# Import Salt Testing libs
+from salttesting import skipIf, TestCase
+from salttesting.mock import NO_MOCK, NO_MOCK_REASON, MagicMock
+
+
+@contextmanager
+def provision_state(module, fixture):
+ previous_dict = getattr(module, '__salt__', {}).copy()
+ try:
+ module.__dict__.setdefault('__salt__', {}).update(fixture)
+ yield
+ finally:
+ setattr(module, '__salt__', previous_dict)
+
+
+@skipIf(NO_MOCK, NO_MOCK_REASON)
+@skipIf(True, 'Skipped: This module has been deprecated.')
+class DockerStateTestCase(TestCase):
+ def test_docker_run_success(self):
+ from salt.states import dockerio
+ salt_fixture = {'docker.retcode': MagicMock(return_value=0),
+ 'docker.run_all': MagicMock(
+ return_value={'stdout': '.\n..\n',
+ 'stderr': '',
+ 'status': True,
+ 'comment': 'Success',
+ 'retcode': 0})}
+
+ with provision_state(dockerio, salt_fixture):
+ result = dockerio.run('ls /', 'ubuntu')
+
+ self.assertEqual(result, {'name': 'ls /',
+ 'result': True,
+ 'comment': 'Success',
+ 'changes': {}})
+
+ def test_docker_run_failure(self):
+ from salt.states import dockerio
+ salt_fixture = {'docker.retcode': MagicMock(return_value=0),
+ 'docker.run_all': MagicMock(
+ return_value={'stdout': '',
+ 'stderr': 'Error',
+ 'status': False,
+ 'comment': 'Failure',
+ 'retcode': 1})}
+
+ with provision_state(dockerio, salt_fixture):
+ result = dockerio.run('ls /', 'ubuntu')
+
+ self.assertEqual(result, {'name': 'ls /',
+ 'result': False,
+ 'comment': 'Failure',
+ 'changes': {}})
+
+ def test_docker_run_onlyif(self):
+ from salt.states import dockerio
+ salt_fixture = {'docker.retcode': MagicMock(return_value=1),
+ 'docker.run_all': None}
+ with provision_state(dockerio, salt_fixture):
+ result = dockerio.run('ls /', 'ubuntu',
+ onlyif='ls -l')
+ self.assertEqual(result, {'name': 'ls /',
+ 'result': True,
+ 'comment': 'onlyif execution failed',
+ 'changes': {}})
+
+ def test_docker_run_unless(self):
+ from salt.states import dockerio
+ salt_fixture = {'docker.retcode': MagicMock(return_value=0),
+ 'docker.run_all': None}
+ with provision_state(dockerio, salt_fixture):
+ result = dockerio.run('ls /', 'ubuntu',
+ unless='ls -l')
+ self.assertEqual(result, {'name': 'ls /',
+ 'result': True,
+ 'comment': 'unless execution succeeded',
+ 'changes': {}})
+
+ def test_docker_run_docked_onlyif(self):
+ from salt.states import dockerio
+ salt_fixture = {'docker.retcode': MagicMock(return_value=1),
+ 'docker.run_all': None}
+ with provision_state(dockerio, salt_fixture):
+ result = dockerio.run('ls /', 'ubuntu',
+ docked_onlyif='ls -l')
+ self.assertEqual(result, {'name': 'ls /',
+ 'result': True,
+ 'comment': 'docked_onlyif execution failed',
+ 'changes': {}})
+
+ def test_docker_run_docked_unless(self):
+ from salt.states import dockerio
+ salt_fixture = {'docker.retcode': MagicMock(return_value=0),
+ 'docker.run_all': None}
+ with provision_state(dockerio, salt_fixture):
+ result = dockerio.run('ls /', 'ubuntu',
+ docked_unless='ls -l')
+ self.assertEqual(result, {'name': 'ls /',
+ 'result': True,
+ 'comment': ('docked_unless execution'
+ ' succeeded'),
+ 'changes': {}})
+
+
+if __name__ == '__main__':
+ from integration import run_tests
+ run_tests(DockerStateTestCase, needs_daemon=False)
diff --git a/tests/unit/utils/network.py b/tests/unit/utils/network.py
index 89db848..72ca857 100644
--- a/tests/unit/utils/network.py
+++ b/tests/unit/utils/network.py
@@ -151,15 +151,16 @@ class NetworkTestCase(TestCase):
self.assertEqual(interfaces,
{'ilbext0': {'inet': [{'address': '10.10.11.11',
'broadcast': '10.10.11.31',
+ 'netmask': '255.255.255.224'},
+ {'address': '10.10.11.12',
+ 'broadcast': '10.10.11.31',
'netmask': '255.255.255.224'}],
- 'inet6': [{'address': '::',
- 'prefixlen': '0'}],
+ 'inet6': [],
'up': True},
'ilbint0': {'inet': [{'address': '10.6.0.11',
'broadcast': '10.6.0.255',
'netmask': '255.255.255.0'}],
- 'inet6': [{'address': '::',
- 'prefixlen': '0'}],
+ 'inet6': [],
'up': True},
'lo0': {'inet': [{'address': '127.0.0.1',
'netmask': '255.0.0.0'}],
@@ -174,8 +175,7 @@ class NetworkTestCase(TestCase):
'up': True},
'vpn0': {'inet': [{'address': '10.6.0.14',
'netmask': '255.0.0.0'}],
- 'inet6': [{'address': '::',
- 'prefixlen': '0'}],
+ 'inet6': [],
'up': True}}
)
diff --git a/tests/unit/utils/utils_test.py b/tests/unit/utils/utils_test.py
index 261af69..11f0baf 100644
--- a/tests/unit/utils/utils_test.py
+++ b/tests/unit/utils/utils_test.py
@@ -527,14 +527,9 @@ class UtilsTestCase(TestCase):
ret = utils.date_cast('Mon Dec 23 10:19:15 MST 2013')
expected_ret = datetime.datetime(2013, 12, 23, 10, 19, 15)
self.assertEqual(ret, expected_ret)
- except ImportError:
- try:
- ret = utils.date_cast('Mon Dec 23 10:19:15 MST 2013')
- expected_ret = datetime.datetime(2013, 12, 23, 10, 19, 15)
- self.assertEqual(ret, expected_ret)
- except RuntimeError:
- # Unparseable without timelib installed
- self.skipTest('\'timelib\' is not installed')
+ except RuntimeError:
+ # Unparseable without timelib installed
+ self.skipTest('\'timelib\' is not installed')
@skipIf(not HAS_TIMELIB, '\'timelib\' is not installed')
def test_date_format(self):
--
2.8.2

View File

@ -0,0 +1,113 @@
From df1f88c51a40e69935830d1664a46dadf514dc69 Mon Sep 17 00:00:00 2001
From: Justin Anderson <janderson@saltstack.com>
Date: Tue, 23 Aug 2016 15:02:31 -0600
Subject: [PATCH 19/19] Fix snapper_test for python26
* Use assertCountEqual instead of assertItemsEqual for Python 3
* Skip one Snapper test on 2.6
There's a slight difference in the diff comparison but we should be
able to catch true failures here with 2.7.
---
tests/unit/modules/snapper_test.py | 41 ++++++++++++++++++++++++++++++--------
1 file changed, 33 insertions(+), 8 deletions(-)
diff --git a/tests/unit/modules/snapper_test.py b/tests/unit/modules/snapper_test.py
index f27b2ba..43f8898 100644
--- a/tests/unit/modules/snapper_test.py
+++ b/tests/unit/modules/snapper_test.py
@@ -6,19 +6,26 @@ Unit tests for the Snapper module
:codeauthor: Pablo Suárez Hernández <psuarezhernandez@suse.de>
'''
+# Import Python libs
from __future__ import absolute_import
+import sys
-from salttesting import TestCase
+# Import Salt Testing libs
+from salttesting import TestCase, skipIf
from salttesting.mock import (
+ NO_MOCK,
+ NO_MOCK_REASON,
MagicMock,
patch,
mock_open,
)
-
-from salt.exceptions import CommandExecutionError
from salttesting.helpers import ensure_in_syspath
+
ensure_in_syspath('../../')
+# Import Salt libs
+import salt.ext.six as six
+from salt.exceptions import CommandExecutionError
from salt.modules import snapper
# Globals
@@ -123,6 +130,13 @@ MODULE_RET = {
"@@ -0,0 +1 @@\n"
"+another foobar",
},
+ '/tmp/foo26': {
+ 'comment': 'text file created',
+ 'diff': "--- /.snapshots/55/snapshot/tmp/foo2 \n"
+ "+++ /tmp/foo2 \n"
+ "@@ -1,0 +1,1 @@\n"
+ "+another foobar",
+ },
'/tmp/foo3': {
'comment': 'binary file changed',
'old_sha256_digest': 'e61f8b762d83f3b4aeb3689564b0ffbe54fa731a69a1e208dc9440ce0f69d19b',
@@ -132,6 +146,7 @@ MODULE_RET = {
}
+@skipIf(NO_MOCK, NO_MOCK_REASON)
class SnapperTestCase(TestCase):
def setUp(self):
self.dbus_mock = MagicMock()
@@ -220,10 +235,16 @@ class SnapperTestCase(TestCase):
@patch('salt.modules.snapper.snapper.GetComparison', MagicMock())
@patch('salt.modules.snapper.snapper.GetFiles', MagicMock(return_value=DBUS_RET['GetFiles']))
def test_status(self):
- self.assertItemsEqual(snapper.status(), MODULE_RET['GETFILES'])
- self.assertItemsEqual(snapper.status(num_pre="42", num_post=43), MODULE_RET['GETFILES'])
- self.assertItemsEqual(snapper.status(num_pre=42), MODULE_RET['GETFILES'])
- self.assertItemsEqual(snapper.status(num_post=43), MODULE_RET['GETFILES'])
+ if six.PY3:
+ self.assertCountEqual(snapper.status(), MODULE_RET['GETFILES'])
+ self.assertCountEqual(snapper.status(num_pre="42", num_post=43), MODULE_RET['GETFILES'])
+ self.assertCountEqual(snapper.status(num_pre=42), MODULE_RET['GETFILES'])
+ self.assertCountEqual(snapper.status(num_post=43), MODULE_RET['GETFILES'])
+ else:
+ self.assertItemsEqual(snapper.status(), MODULE_RET['GETFILES'])
+ self.assertItemsEqual(snapper.status(num_pre="42", num_post=43), MODULE_RET['GETFILES'])
+ self.assertItemsEqual(snapper.status(num_pre=42), MODULE_RET['GETFILES'])
+ self.assertItemsEqual(snapper.status(num_post=43), MODULE_RET['GETFILES'])
@patch('salt.modules.snapper.status', MagicMock(return_value=MODULE_RET['GETFILES']))
def test_changed_files(self):
@@ -268,7 +289,10 @@ class SnapperTestCase(TestCase):
@patch('os.path.isfile', MagicMock(side_effect=[False, True]))
@patch('salt.utils.fopen', mock_open(read_data=FILE_CONTENT["/tmp/foo2"]['post']))
def test_diff_text_file(self):
- self.assertEqual(snapper.diff(), {"/tmp/foo2": MODULE_RET['DIFF']['/tmp/foo2']})
+ if sys.version_info < (2, 7):
+ self.assertEqual(snapper.diff(), {"/tmp/foo2": MODULE_RET['DIFF']['/tmp/foo26']})
+ else:
+ self.assertEqual(snapper.diff(), {"/tmp/foo2": MODULE_RET['DIFF']['/tmp/foo2']})
@patch('salt.modules.snapper._get_num_interval', MagicMock(return_value=(55, 0)))
@patch('salt.modules.snapper.snapper.MountSnapshot', MagicMock(
@@ -278,6 +302,7 @@ class SnapperTestCase(TestCase):
@patch('salt.modules.snapper._is_text_file', MagicMock(return_value=True))
@patch('os.path.isfile', MagicMock(side_effect=[True, True, False, True]))
@patch('os.path.isdir', MagicMock(return_value=False))
+ @skipIf(sys.version_info < (2, 7), 'Python 2.7 required to compare diff properly')
def test_diff_text_files(self):
fopen_effect = [
mock_open(read_data=FILE_CONTENT["/tmp/foo"]['pre']).return_value,
--
2.8.2

View File

@ -0,0 +1,107 @@
From 65dba3e9ab088087943f67367dc27fce00e4cd92 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?=
<psuarezhernandez@suse.com>
Date: Tue, 13 Sep 2016 11:05:35 +0100
Subject: [PATCH 20/20] Integration tests fixes for 2016.3.2
* Fix PillarModuleTest::test_pillar_items: 'info' does not exist in pillar
* Fixing integration tests if azure is not present
* Fixing integration tests failures if 'git' command is missing
Skip git state integration tests if 'git' does not exists
Prevent OSError if 'git' command not found during _git_version()
---
tests/integration/cloud/providers/msazure.py | 17 +++++++++--------
tests/integration/modules/git.py | 15 +++++++++------
tests/integration/modules/pillar.py | 1 -
tests/integration/states/git.py | 2 +-
4 files changed, 19 insertions(+), 16 deletions(-)
diff --git a/tests/integration/cloud/providers/msazure.py b/tests/integration/cloud/providers/msazure.py
index c4934e4..4e53add 100644
--- a/tests/integration/cloud/providers/msazure.py
+++ b/tests/integration/cloud/providers/msazure.py
@@ -53,14 +53,15 @@ def __has_required_azure():
'''
Returns True/False if the required version of the Azure SDK is installed.
'''
- if hasattr(azure, '__version__'):
- version = LooseVersion(azure.__version__)
- else:
- version = LooseVersion(azure.common.__version__)
- if HAS_AZURE is True and REQUIRED_AZURE <= version:
- return True
- else:
- return False
+ if HAS_AZURE:
+ if hasattr(azure, '__version__'):
+ version = LooseVersion(azure.__version__)
+ else:
+ version = LooseVersion(azure.common.__version__)
+
+ if REQUIRED_AZURE <= version:
+ return True
+ return False
@skipIf(HAS_AZURE is False, 'These tests require the Azure Python SDK to be installed.')
diff --git a/tests/integration/modules/git.py b/tests/integration/modules/git.py
index 7c40664..f5c15f3 100644
--- a/tests/integration/modules/git.py
+++ b/tests/integration/modules/git.py
@@ -36,12 +36,15 @@ log = logging.getLogger(__name__)
def _git_version():
- git_version = subprocess.Popen(
- ['git', '--version'],
- shell=False,
- close_fds=True,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE).communicate()[0]
+ try:
+ git_version = subprocess.Popen(
+ ['git', '--version'],
+ shell=False,
+ close_fds=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE).communicate()[0]
+ except OSError:
+ return False
if not git_version:
log.debug('Git not installed')
return False
diff --git a/tests/integration/modules/pillar.py b/tests/integration/modules/pillar.py
index b081f76..3d55b4c 100644
--- a/tests/integration/modules/pillar.py
+++ b/tests/integration/modules/pillar.py
@@ -119,7 +119,6 @@ class PillarModuleTest(integration.ModuleCase):
from pillar.items
'''
get_items = self.run_function('pillar.items')
- self.assertDictContainsSubset({'info': 'bar'}, get_items)
self.assertDictContainsSubset({'monty': 'python'}, get_items)
self.assertDictContainsSubset(
{'knights': ['Lancelot', 'Galahad', 'Bedevere', 'Robin']},
diff --git a/tests/integration/states/git.py b/tests/integration/states/git.py
index a78b271..87681ba 100644
--- a/tests/integration/states/git.py
+++ b/tests/integration/states/git.py
@@ -20,6 +20,7 @@ import integration
import salt.utils
+@skip_if_binaries_missing('git')
class GitTest(integration.ModuleCase, integration.SaltReturnAssertsMixIn):
'''
Validate the git state
@@ -254,7 +255,6 @@ class GitTest(integration.ModuleCase, integration.SaltReturnAssertsMixIn):
finally:
shutil.rmtree(name, ignore_errors=True)
- @skip_if_binaries_missing('git')
def test_config_set_value_with_space_character(self):
'''
git.config
--
2.8.2

View File

@ -0,0 +1,31 @@
From 2cac0c3839af12b0a474f4cb0c0854995cd8dc2a Mon Sep 17 00:00:00 2001
From: "C. R. Oldham" <cro@ncbt.org>
Date: Wed, 21 Sep 2016 20:05:33 -0600
Subject: [PATCH 21/21] Fix pkg.upgrade for zypper
---
salt/modules/zypper.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py
index 4bb9a09..21b87b0 100644
--- a/salt/modules/zypper.py
+++ b/salt/modules/zypper.py
@@ -1079,10 +1079,11 @@ def upgrade(refresh=True, skip_verify=False):
refresh_db()
old = list_pkgs()
- to_append = ''
if skip_verify:
- to_append = '--no-gpg-checks'
- __zypper__.noraise.call('update', '--auto-agree-with-licenses', to_append)
+ __zypper__.noraise.call('update', '--auto-agree-with-licenses', '--no-gpg-checks')
+ else:
+ __zypper__.noraise.call('update', '--auto-agree-with-licenses')
+
if __zypper__.exit_code not in __zypper__.SUCCESS_EXIT_CODES:
ret['result'] = False
ret['comment'] = (__zypper__.stdout() + os.linesep + __zypper__.stderr()).strip()
--
2.10.0

3
html.tar.bz2 Normal file
View File

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

View File

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

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

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

View File

@ -1,3 +1,291 @@
-------------------------------------------------------------------
Wed Sep 28 12:35:32 UTC 2016 - tampakrap@opensuse.org
- Rename susemanager plugin to zyppnotify, as it is not SUSE Manager specific
- Remove the subpackage and put the plugin back to the main package
according to upstream
Add:
* 0007-Add-zypp-notify-plugin.patch
Remove:
* 0007-Add-SUSE-Manager-plugin.patch
-------------------------------------------------------------------
Wed Sep 28 11:40:36 UTC 2016 - tampakrap@opensuse.org
- Add upstream patch to fix pkg.upgrade for zypper
Add:
* 0021-Fix-pkg.upgrade-for-zypper.patch
-------------------------------------------------------------------
Tue Sep 27 23:55:03 UTC 2016 - mrueckert@suse.de
- splitting out the susemanager integration plugins into their own
subpackages. ATM this only contains the zypp plugin to tell
susemanager about manually installed packages.
-------------------------------------------------------------------
Tue Sep 13 15:07:44 UTC 2016 - pablo.suarezhernandez@suse.com
- Unit and integration tests fixes for 2016.3.2
Add:
* 0018-Unit-tests-fixes-for-2016.3.2.patch
* 0019-Fix-snapper_test-for-python26.patch
* 0020-Integration-tests-fixes-for-2016.3.2.patch
-------------------------------------------------------------------
Fri Sep 2 09:50:57 UTC 2016 - pablo.suarezhernandez@suse.com
- Prevent pkg.install failure for expired keys (bsc#996455)
Add:
* 0017-Check-for-single-quote-before-splitting-on-single-qu.patch
-------------------------------------------------------------------
Tue Aug 30 12:24:45 UTC 2016 - bmaryniuk@suse.com
- Required D-Bus and generating machine ID where it is missing
-------------------------------------------------------------------
Fri Aug 26 13:11:58 UTC 2016 - bmaryniuk@suse.com
- Fix sphinx crashes when documentation is being generated
Add script for documentation update.
Add:
* 0016-Improve-Mock-to-be-flexible-and-able-to-mock-methods.patch
* update-documentation.sh
-------------------------------------------------------------------
Tue Aug 16 12:01:59 UTC 2016 - bmaryniuk@suse.com
- Fix pkg.installed refresh repo failure (bsc#993549)
Fix salt.states.pkgrepo.management no change failure (bsc#990440)
Add:
* 0014-Add-ignore_repo_failure-option-to-suppress-zypper-s-.patch
* 0015-Remove-zypper-s-raise-exception-if-mod_repo-has-no-a.patch
-------------------------------------------------------------------
Tue Aug 9 13:40:37 UTC 2016 - bmaryniuk@suse.com
- Deprecate status.uptime one version later
Add:
* 0013-Deprecate-status.uptime-one-version-later.patch
-------------------------------------------------------------------
Mon Aug 8 16:25:04 UTC 2016 - pablo.suarezhernandez@suse.com
- Fix for 0008-checksum-validation-when-zypper-pkg.download.patch
after upstream merging conflict. Prevent zypper test failure.
Update:
* 0008-checksum-validation-when-zypper-pkg.download.patch
-------------------------------------------------------------------
Thu Aug 4 14:56:42 UTC 2016 - mihai.dinca@suse.com
- Update to v2016.3.2
see https://docs.saltstack.com/en/latest/topics/releases/2016.3.2.html
- Removed Patches, applied upstream
* 0010-Getting-the-os-grain-from-CPE_NAME-inside-etc-os-rel.patch
* 0011-snapper-execution-module.patch
* 0012-Force-minion-exit-on-mis-configuration-read.patch
* 0013-fix-salt-summary-to-count-not-responding-minions-cor.patch
* 0014-Move-log-message-from-INFO-to-DEBUG.patch
* 0016-Run-salt-api-as-user-salt-bsc-990029.patch
* 0017-fix-beacon-list-to-include-all-beacons-being-process.patch
- Added Patches back
* 0010-snapper-execution-module.patch
* 0011-fix-salt-summary-to-count-not-responding-minions-cor.patch
* 0012-Run-salt-api-as-user-salt-bsc-990029.patch
-------------------------------------------------------------------
Wed Aug 3 14:50:35 UTC 2016 - pablo.suarezhernandez@suse.com
- Fix beacon list to include all beacons being process
Add:
* 0017-fix-beacon-list-to-include-all-beacons-being-process.patch
-------------------------------------------------------------------
Fri Jul 29 11:48:37 UTC 2016 - kkaempf@suse.com
- Redo patches, sync with github.com/openSUSE/salt
- Renamed patches
* 0016-Getting-the-os-grain-from-CPE_NAME-inside-etc-os-rel.patch
-> 0010-Getting-the-os-grain-from-CPE_NAME-inside-etc-os-rel.patch
* 0017-snapper-execution-module.patch
-> 0011-snapper-execution-module.patch
* 0018-Force-minion-exit-on-mis-configuration-read.patch
-> 0012-Force-minion-exit-on-mis-configuration-read.patch
- Added back (fix upstream merge problems):
* 0008-checksum-validation-when-zypper-pkg.download.patch
* 0009-unit-tests-for-rpm.checksum-and-zypper.download.patch
- fix salt --summary to count not responding minions correctly
(bsc#972311)
* 0013-fix-salt-summary-to-count-not-responding-minions-cor.patch
- Move log message from INFO to DEBUG (bsc#985661)
* 0014-Move-log-message-from-INFO-to-DEBUG.patch
- pkg.list_products on "registerrelease" and "productline"
returns boolean.False if empty (bsc#989193)
* 0015-Bugfix-return-boolean-only-for-isbase-and-installed-.patch
- Run salt-api as user salt like the master (bsc#990029)
* 0016-Run-salt-api-as-user-salt-bsc-990029.patch
-------------------------------------------------------------------
Fri Jul 22 15:16:16 UTC 2016 - darin@darins.net
- Update to v2016.3.1
see https://docs.saltstack.com/en/latest/topics/releases/2016.3.1.html
- Removed Patches, applied upstream
* 0008-Prevent-several-minion-processes-on-the-same-machine.patch
* 0009-checksum-validation-when-zypper-pkg.download.patch
* 0010-unit-tests-for-rpm.checksum-and-zypper.download.patch
* 0011-jobs.exit_success-allow-to-check-if-a-job-has-execut.patch
* 0012-Fix-pkgrepo.managed-gpgkey-argument-bsc-979448.patch
* 0013-fix-groupadd-module-for-sles11-systems.patch
* 0014-Fix-crashing-Maintenence-process.patch
* 0015-Insert-no-refresh-before-install-in-Zypper.patch
-------------------------------------------------------------------
Tue Jul 19 15:03:10 UTC 2016 - bmaryniuk@suse.com
- Bugfix: Exit on configuration read (bsc#985667)
Add:
* 0018-Force-minion-exit-on-mis-configuration-read.patch
-------------------------------------------------------------------
Mon Jul 18 08:32:04 UTC 2016 - pablo.suarezhernandez@suse.com
- Backport: Snapper module for Salt.
Add:
* 0017-snapper-execution-module.patch
-------------------------------------------------------------------
Wed Jun 22 07:56:44 UTC 2016 - pablo.suarezhernandez@suse.com
- Bugfix: Fixed behavior for SUSE OS grains (bsc#970669)
Bugfix: Salt os_family does not detect SLES for SAP (bsc#983017)
Add:
* 0016-Getting-the-os-grain-from-CPE_NAME-inside-etc-os-rel.patch
-------------------------------------------------------------------
Mon Jun 20 11:26:29 UTC 2016 - thipp@suse.com
- Insert --no-refresh before install in Zypper
Add:
* 0015-Insert-no-refresh-before-install-in-Zypper.patch
-------------------------------------------------------------------
Wed Jun 15 11:38:26 UTC 2016 - kkaempf@suse.com
- Update to v2016.3.0
see https://docs.saltstack.com/en/latest/topics/releases/2016.3.0.html
* backwards-incompatible changes:
- The default path for the extension_modules master config option
has been changed.
- add 0014-Fix-crashing-Maintenence-process.patch
see release notes
-------------------------------------------------------------------
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

199
salt.spec
View File

@ -34,54 +34,83 @@
%bcond_with test
%bcond_with raet
%bcond_without docs
%bcond_with builddocs
Name: salt
Version: 2015.8.8
Version: 2016.3.2
Release: 0
Summary: A parallel remote execution system
License: Apache-2.0
Group: System/Monitoring
Url: http://saltstack.org/
# Git: https://github.com/openSUSE/salt.git
Source0: http://pypi.python.org/packages/source/s/%{name}/%{name}-%{version}.tar.gz
Source0: https://pypi.io/packages/source/s/%{name}/%{name}-%{version}.tar.gz
Source1: README.SUSE
Source2: salt-tmpfiles.d
Source3: html.tar.bz2
Source4: update-documentation.sh
# PATCH-FIX-OPENSUSE use-forking-daemon.patch tserong@suse.com -- We don't have python-systemd, so notify can't work
# We do not upstream this patch because this is something that we have to fix on our side
Patch1: 0001-tserong-suse.com-We-don-t-have-python-systemd-so-not.patch
# PATCH-FIX-OPENSUSE use-salt-user-for-master.patch -- Run salt master as dedicated salt user
# We do not upstream this patch because this is suse custom configuration
Patch2: 0002-Run-salt-master-as-dedicated-salt-user.patch
# PATCH-FIX-OPENSUSE https://github.com/saltstack/salt/pull/30424
# We do not upstream this patch because it has been fixed upstream
# (see: https://trello.com/c/wh96lCD4/1528-get-rid-of-0003-check-if-byte-strings-are-properly-encoded-in-utf-8-patch-in-the-salt-package)
Patch3: 0003-Check-if-byte-strings-are-properly-encoded-in-UTF-8.patch
# PATCH-FIX-OPENSUSE prevent rebuilds in OBS
# We do not upstream this patch because the issue is on our side
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)
# PR https://github.com/saltstack/salt/pull/35341 (15.08.2016 - not merged yet)
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
# PR already merged. This will be gone in the next version
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
# We do not upstream this because this is for SUSE only (15.08.2016)
Patch7: 0007-Add-zypp-notify-plugin.patch
# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/33469
# PR already merged. This will be gone in the next version
Patch8: 0008-checksum-validation-when-zypper-pkg.download.patch
# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/33501
# PR already merged. This will be gone in the next version
Patch9: 0009-unit-tests-for-rpm.checksum-and-zypper.download.patch
# PATCH-FIX-UPSTREAM https://github.com/saltstack/salt/pull/34599 (backport from dev)
# PR already merged. This will be gone in the next version
Patch10: 0010-snapper-execution-module.patch
# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/34165
Patch11: 0011-fix-salt-summary-to-count-not-responding-minions-cor.patch
# PATCH-FIX-OPENSUSE
# We do not upstream this patch because this is suse custom configuration
Patch12: 0012-Run-salt-api-as-user-salt-bsc-990029.patch
# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/35196
# PR already merged. This will be gone in the next version
Patch13: 0013-Deprecate-status.uptime-one-version-later.patch
# PATCH-FIX-UPSTREAM https://github.com/saltstack/salt/pull/35448
Patch14: 0014-Add-ignore_repo_failure-option-to-suppress-zypper-s-.patch
# PATCH-FIX-UPSTREAM https://github.com/saltstack/salt/pull/35451
Patch15: 0015-Remove-zypper-s-raise-exception-if-mod_repo-has-no-a.patch
# PATCH-FIX-UPSTREAM https://github.com/saltstack/salt/pull/35763
Patch16: 0016-Improve-Mock-to-be-flexible-and-able-to-mock-methods.patch
# PATCH-FIX-UPSTREAM https://github.com/saltstack/salt/pull/36000
Patch17: 0017-Check-for-single-quote-before-splitting-on-single-qu.patch
# PATCH-FIX-UPSTREAM https://github.com/saltstack/salt/pull/36139
# https://github.com/saltstack/salt/pull/36158
# https://github.com/saltstack/salt/pull/36227
Patch18: 0018-Unit-tests-fixes-for-2016.3.2.patch
# PATCH-FIX-UPSTREAM https://github.com/saltstack/salt/pull/35715
# https://github.com/saltstack/salt/pull/35983
# https://github.com/saltstack/salt/pull/34826
Patch19: 0019-Fix-snapper_test-for-python26.patch
# PATCH-FIX-UPSTREAM https://github.com/saltstack/salt/pull/36263
Patch20: 0020-Integration-tests-fixes-for-2016.3.2.patch
# PATCH-FIX-UPSTREAM https://github.com/saltstack/salt/pull/36495
Patch21: 0021-Fix-pkg.upgrade-for-zypper.patch
BuildRoot: %{_tmppath}/%{name}-%{version}-build
BuildRequires: logrotate
@ -120,17 +149,27 @@ BuildRequires: python-salt-testing >= 2015.2.16
BuildRequires: python-unittest2
BuildRequires: python-xml
%endif
%if %{with docs}
#for docs
%if %{with builddocs}
BuildRequires: python-sphinx
%endif
%if 0%{?suse_version} > 1010
BuildRequires: fdupes
%endif
Requires(pre): %{_sbindir}/groupadd
Requires(pre): %{_sbindir}/useradd
%if 0%{?suse_version}
Requires(pre): %fillup_prereq
Requires(pre): pwdutils
%endif
%if 0%{?suse_version}
Requires(pre): dbus-1
%else
Requires(pre): dbus
%endif
Requires: logrotate
Requires: python
#
@ -432,7 +471,11 @@ cp %{S:1} .
%patch4 -p1
%patch5 -p1
%patch6 -p1
# This is SUSE-only patch
%if 0%{?suse_version}
%patch7 -p1
%endif
%patch8 -p1
%patch9 -p1
%patch10 -p1
@ -442,16 +485,24 @@ cp %{S:1} .
%patch14 -p1
%patch15 -p1
%patch16 -p1
# This is SUSE-only patch
%if 0%{?suse_version}
%patch17 -p1
%endif
%patch18 -p1
%patch19 -p1
%patch20 -p1
%patch21 -p1
%build
python setup.py --salt-transport=both build
%if %{with docs}
%if %{with docs} && %{without builddocs}
# extract docs from the tarball
mkdir -p doc/_build
pushd doc/_build/
tar xfv %{S:3}
popd
%endif
%if %{with docs} && %{with builddocs}
## documentation
cd doc && make html && rm _build/html/.buildinfo && rm _build/html/_images/proxy_minions.png && cd _build/html && chmod -R -x+X *
%endif
@ -498,7 +549,7 @@ install -Dd -m 0750 %{buildroot}%{_sysconfdir}/salt/pki/minion
## Install Zypper plugins only on SUSE machines
%if 0%{?suse_version}
install -Dd -m 0750 %{buildroot}%{_prefix}/lib/zypp/plugins/commit
%{__install} scripts/zypper/plugins/commit/susemanager %{buildroot}%{_prefix}/lib/zypp/plugins/commit/susemanager
%{__install} scripts/zypper/plugins/commit/zyppnotify %{buildroot}%{_prefix}/lib/zypp/plugins/commit/zyppnotify
%endif
## install init and systemd scripts
@ -507,6 +558,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
@ -558,6 +610,11 @@ install -Dpm 0644 pkg/fish-completions/* %{buildroot}%{fish_completions_dir}
echo "transport: raet" > %{buildroot}%{_sysconfdir}/salt/master.d/transport-raet.conf
echo "transport: raet" > %{buildroot}%{_sysconfdir}/salt/minion.d/transport-raet.conf
%if 0%{?suse_version} > 1020
%fdupes %{buildroot}%{_docdir}
%fdupes %{buildroot}%{python_sitelib}
%endif
%check
%if %{with test}
python setup.py test --runtests-opts=-u
@ -567,9 +624,50 @@ python setup.py test --runtests-opts=-u
getent group salt >/dev/null || %{_sbindir}/groupadd -r salt
getent passwd salt >/dev/null || %{_sbindir}/useradd -r -g salt -d /srv/salt -s /bin/false -c "salt-master daemon" salt
%if %{with systemd}
%post
%if %{with systemd}
systemd-tmpfiles --create /usr/lib/tmpfiles.d/salt.conf || true
%else
dbus-uuidgen --ensure
%endif
%preun proxy
%if %{with systemd}
%service_del_preun salt-proxy@.service
%else
%if 0%{?suse_version}
%stop_on_removal salt-proxy
%else
if [ $1 -eq 0 ] ; then
/sbin/service salt-proxy stop >/dev/null 2>&1
/sbin/chkconfig --del salt-proxy
fi
%endif
%endif
%pre proxy
%if %{with systemd}
%service_add_pre salt-proxy@.service
%endif
%post proxy
%if %{with systemd}
%service_add_post salt-proxy@.service
%fillup_only
%else
%if 0%{?suse_version}
%fillup_and_insserv
%endif
%endif
%postun proxy
%if %{with systemd}
%service_del_postun salt-proxy@.service
%else
%if 0%{?suse_version}
%insserv_cleanup
%restart_on_update salt-proxy
%endif
%endif
%preun syndic
@ -631,6 +729,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
@ -779,11 +891,12 @@ systemd-tmpfiles --create /usr/lib/tmpfiles.d/salt.conf || true
%dir %attr(0750, root, root) %{_sysconfdir}/salt/minion.d/
%dir %attr(0750, root, root) %{_sysconfdir}/salt/pki/minion/
%dir %attr(0750, root, root) %{_localstatedir}/cache/salt/minion/
#%dir %ghost %attr(0750, root, salt) %{_localstatedir}/run/salt/minion
%{_sbindir}/rcsalt-minion
# Install plugin only on SUSE machines
%if 0%{?suse_version}
%{_prefix}/lib/zypp/plugins/commit/susemanager
%{_prefix}/lib/zypp/plugins/commit/zyppnotify
%endif
%if %{with systemd}
@ -796,6 +909,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)
@ -835,6 +951,7 @@ systemd-tmpfiles --create /usr/lib/tmpfiles.d/salt.conf || true
%dir %attr(0750, salt, salt) %{_localstatedir}/cache/salt/master/roots/
%dir %attr(0750, salt, salt) %{_localstatedir}/cache/salt/master/syndics/
%dir %attr(0750, salt, salt) %{_localstatedir}/cache/salt/master/tokens/
#%dir %ghost %attr(0750, salt, salt) %{_localstatedir}/run/salt/master/
%files raet
%defattr(-,root,root,-)
@ -848,17 +965,19 @@ systemd-tmpfiles --create /usr/lib/tmpfiles.d/salt.conf || true
%{_bindir}/salt-unity
%{_mandir}/man1/salt-unity.1.gz
%{_mandir}/man1/salt-call.1.gz
%{_mandir}/man1/spm.1.gz
%config(noreplace) %{_sysconfdir}/logrotate.d/salt
%{python_sitelib}/*
%exclude %{python_sitelib}/salt/cloud/deploy/*.sh
%attr(755,root,root)%{python_sitelib}/salt/cloud/deploy/*.sh
%doc LICENSE AUTHORS README.rst HACKING.rst README.SUSE
#
%dir %attr(0750, root, salt) %{_sysconfdir}/salt
%dir %attr(0750, root, salt) %{_sysconfdir}/salt/pki
%dir %attr(0750, salt, salt) %{_localstatedir}/log/salt
%dir %attr(0750, root, salt) %{_localstatedir}/cache/salt
%dir %attr(0750, root, salt) /srv/spm
%dir %attr(0750, root, salt) %{_sysconfdir}/salt
%dir %attr(0750, root, salt) %{_sysconfdir}/salt/pki
%dir %attr(0750, salt, salt) %{_localstatedir}/log/salt
%dir %attr(0750, root, salt) %{_localstatedir}/cache/salt
#%dir %ghost %attr(0750, root, salt) %{_localstatedir}/run/salt
%dir %attr(0750, root, salt) /srv/spm
%if %{with systemd}
/usr/lib/tmpfiles.d/salt.conf
%endif

64
update-documentation.sh Normal file
View File

@ -0,0 +1,64 @@
#!/bin/bash
#
# Update html.tar.bz2 documentation tarball
# Author: Bo Maryniuk <bo@suse.de>
#
function check_env() {
for cmd in "sphinx-build" "make" "quilt"; do
if [ -z "$(which $cmd 2>/dev/null)" ]; then
echo "Error: '$cmd' is missing."
exit 1;
fi
done
}
function quilt_setup() {
quilt setup salt.spec
cd $1
quilt push -a
}
function build_docs() {
cd $1
make html
rm _build/html/.buildinfo
cd _build/html
chmod -R -x+X *
cd ..
tar cvf - html | bzip2 > /tmp/html.tar.bz2
}
function write_changelog() {
mv salt.changes salt.changes.previous
TIME=$(date -u +'%a %b %d %T %Z %Y')
MAIL=$1
SEP="-------------------------------------------------------------------"
cat <<EOF > salt.changes
$SEP
$TIME - $MAIL
- Updated html.tar.bz2 documentation tarball.
EOF
cat salt.changes.previous >> salt.changes
rm salt.changes.previous
}
if [ -z "$1" ]; then
echo "Usage: $0 <your e-mail>"
exit 1;
fi
check_env;
START=$(pwd)
SRC_DIR="salt-$(cat salt.spec | grep ^Version: | cut -d: -f2 | sed -e 's/[[:blank:]]//g')";
quilt_setup $SRC_DIR
build_docs doc
cd $START
rm -rf $SRC_DIR
mv /tmp/html.tar.bz2 $START
echo "Done"
echo "---------------"