From a10aa73e422adcfe78d4f600c6abc14915445020a76d31270204c45d9a8f3272 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Klaus=20K=C3=A4mpf?= <kkaempf@suse.com>
Date: Mon, 6 Jun 2016 10:54:59 +0000
Subject: [PATCH] Accepting request 399636 from
 systemsmanagement:saltstack:testing

OBS-URL: https://build.opensuse.org/request/show/399636
OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=70
---
 ...salt-proxy-instantiated-service-file.patch |  36 +
 0006-Update-to-2015.8.8.2.patch               | 168 ----
 0007-Add-SUSE-Manager-plugin.patch            | 221 +++++
 ...PM-output-to-ensure-latest-version-o.patch | 350 -------
 ...-deprecation-process-with-decorators.patch | 922 ------------------
 ...minion-processes-on-the-same-machine.patch |  25 +
 ...-validation-when-zypper-pkg.download.patch |  74 ++
 ...atest-version-when-called-with-an-at.patch |  48 -
 ...-download-when-getting-installed-pro.patch |  29 -
 ...for-rpm.checksum-and-zypper.download.patch | 105 ++
 ...available-in-a-particular-product-bs.patch | 146 ---
 ...s-allow-to-check-if-a-job-has-execut.patch |  53 +
 ...crashes-if-tries-to-generate-keys-to.patch |  69 --
 ...o.managed-gpgkey-argument-bsc-979448.patch | 325 ++++++
 ...-pygit2-package-is-requesting-re-com.patch |  86 --
 ...from-older-SLES-with-current-one-326.patch |  39 -
 0015-Unblock-Zypper.-Modify-environment.patch | 904 -----------------
 ...oolean-values-from-the-repo-configur.patch | 118 ---
 0017-Add-SUSE-Manager-plugin.patch            | 103 --
 salt-2015.8.10.tar.gz                         |   3 +
 salt-2015.8.8.tar.gz                          |   3 -
 salt.changes                                  |  95 ++
 salt.spec                                     |  71 +-
 23 files changed, 975 insertions(+), 3018 deletions(-)
 create mode 100644 0006-Create-salt-proxy-instantiated-service-file.patch
 delete mode 100644 0006-Update-to-2015.8.8.2.patch
 create mode 100644 0007-Add-SUSE-Manager-plugin.patch
 delete mode 100644 0007-Force-sort-the-RPM-output-to-ensure-latest-version-o.patch
 delete mode 100644 0008-Cleaner-deprecation-process-with-decorators.patch
 create mode 100644 0008-Prevent-several-minion-processes-on-the-same-machine.patch
 create mode 100644 0009-checksum-validation-when-zypper-pkg.download.patch
 delete mode 100644 0009-fix-sorting-by-latest-version-when-called-with-an-at.patch
 delete mode 100644 0010-Prevent-metadata-download-when-getting-installed-pro.patch
 create mode 100644 0010-unit-tests-for-rpm.checksum-and-zypper.download.patch
 delete mode 100644 0011-Check-if-EOL-is-available-in-a-particular-product-bs.patch
 create mode 100644 0011-jobs.exit_success-allow-to-check-if-a-job-has-execut.patch
 delete mode 100644 0012-Bugfix-salt-key-crashes-if-tries-to-generate-keys-to.patch
 create mode 100644 0012-Fix-pkgrepo.managed-gpgkey-argument-bsc-979448.patch
 delete mode 100644 0013-Prevent-crash-if-pygit2-package-is-requesting-re-com.patch
 delete mode 100644 0014-align-OS-grains-from-older-SLES-with-current-one-326.patch
 delete mode 100644 0015-Unblock-Zypper.-Modify-environment.patch
 delete mode 100644 0016-Bugfix-Restore-boolean-values-from-the-repo-configur.patch
 delete mode 100644 0017-Add-SUSE-Manager-plugin.patch
 create mode 100644 salt-2015.8.10.tar.gz
 delete mode 100644 salt-2015.8.8.tar.gz

diff --git a/0006-Create-salt-proxy-instantiated-service-file.patch b/0006-Create-salt-proxy-instantiated-service-file.patch
new file mode 100644
index 0000000..45c9aa6
--- /dev/null
+++ b/0006-Create-salt-proxy-instantiated-service-file.patch
@@ -0,0 +1,36 @@
+From e2236cc77888d3c359c23dfb47a57e1e057864bb Mon Sep 17 00:00:00 2001
+From: Christian McHugh <mchugh19@hotmail.com>
+Date: Thu, 10 Mar 2016 13:25:01 -0600
+Subject: [PATCH 6/7] Create salt-proxy instantiated service file
+
+Add a systemd service file for salt-proxy.
+
+Instantiate a new proxy service with proxyid=p8000:
+# systemctl enable salt-proxy\@p8000.service
+# systemctl start salt-proxy\@p8000.service
+---
+ pkg/salt-proxy@.service | 12 ++++++++++++
+ 1 file changed, 12 insertions(+)
+ create mode 100644 pkg/salt-proxy@.service
+
+diff --git a/pkg/salt-proxy@.service b/pkg/salt-proxy@.service
+new file mode 100644
+index 0000000..f97120a
+--- /dev/null
++++ b/pkg/salt-proxy@.service
+@@ -0,0 +1,12 @@
++[Unit]
++Description=salt-proxy service
++After=network.target
++
++[Service]
++ExecStart=/usr/bin/salt-proxy --proxyid=%I
++Type=simple
++Restart=on-failure
++RestartSec=5s
++
++[Install]
++WantedBy=multi-user.target
+-- 
+2.8.2
+
diff --git a/0006-Update-to-2015.8.8.2.patch b/0006-Update-to-2015.8.8.2.patch
deleted file mode 100644
index 4536e12..0000000
--- a/0006-Update-to-2015.8.8.2.patch
+++ /dev/null
@@ -1,168 +0,0 @@
-From 00600229ac41ae618bf01e8af6e2c0183d924204 Mon Sep 17 00:00:00 2001
-From: Theo Chatzimichos <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
-
diff --git a/0007-Add-SUSE-Manager-plugin.patch b/0007-Add-SUSE-Manager-plugin.patch
new file mode 100644
index 0000000..a6d39f0
--- /dev/null
+++ b/0007-Add-SUSE-Manager-plugin.patch
@@ -0,0 +1,221 @@
+From df87ac3485ff8b5013e720435905afda6b53ada8 Mon Sep 17 00:00:00 2001
+From: Bo Maryniuk <bo@suse.de>
+Date: Mon, 9 May 2016 10:33:44 +0200
+Subject: [PATCH 7/7] Add SUSE Manager plugin
+
+* Add unit test to the libzypp drift detector plugin
+---
+ scripts/zypper/plugins/commit/README.md   |  3 ++
+ scripts/zypper/plugins/commit/susemanager | 59 ++++++++++++++++++++++++++++
+ tests/unit/zypp_plugins_test.py           | 51 ++++++++++++++++++++++++
+ tests/zypp_plugin.py                      | 64 +++++++++++++++++++++++++++++++
+ 4 files changed, 177 insertions(+)
+ create mode 100644 scripts/zypper/plugins/commit/README.md
+ create mode 100755 scripts/zypper/plugins/commit/susemanager
+ create mode 100644 tests/unit/zypp_plugins_test.py
+ create mode 100644 tests/zypp_plugin.py
+
+diff --git a/scripts/zypper/plugins/commit/README.md b/scripts/zypper/plugins/commit/README.md
+new file mode 100644
+index 0000000..01c8917
+--- /dev/null
++++ b/scripts/zypper/plugins/commit/README.md
+@@ -0,0 +1,3 @@
++# Zypper plugins
++
++Plugins here are required to interact with SUSE Manager in conjunction of SaltStack and Zypper.
+diff --git a/scripts/zypper/plugins/commit/susemanager b/scripts/zypper/plugins/commit/susemanager
+new file mode 100755
+index 0000000..268298b
+--- /dev/null
++++ b/scripts/zypper/plugins/commit/susemanager
+@@ -0,0 +1,59 @@
++#!/usr/bin/python
++#
++# Copyright (c) 2016 SUSE Linux LLC
++# All Rights Reserved.
++#
++# Author: Bo Maryniuk <bo@suse.de>
++
++import sys
++import os
++import hashlib
++
++from zypp_plugin import Plugin
++
++
++class DriftDetector(Plugin):
++    """
++    Return diff of the installed packages outside the Salt.
++    """
++    def __init__(self):
++        Plugin.__init__(self)
++        self.ck_path = "/var/cache/salt/minion/rpmdb.cookie"
++        self.rpm_path = "/var/lib/rpm/Packages"
++
++    def _get_mtime(self):
++        '''
++        Get the modified time of the RPM Database.
++        Returns:
++            Unix ticks
++        '''
++        return os.path.exists(self.rpm_path) and int(os.path.getmtime(self.rpm_path)) or 0
++
++    def _get_checksum(self):
++        '''
++        Get the checksum of the RPM Database.
++        Returns:
++            hexdigest
++        '''
++        digest = hashlib.md5()
++        with open(self.rpm_path, "rb") as rpm_db_fh:
++            while True:
++                buff = rpm_db_fh.read(0x1000)
++                if not buff:
++                    break
++                digest.update(buff)
++
++        return digest.hexdigest()
++
++    def PLUGINEND(self, headers, body):
++        """
++        Hook when plugin closes Zypper's transaction.        
++        """
++        if 'SALT_RUNNING' not in os.environ:
++            with open(self.ck_path, 'w') as ck_fh:
++                ck_fh.write('{chksum} {mtime}\n'.format(chksum=self._get_checksum(), mtime=self._get_mtime()))
++
++        self.ack()
++
++
++DriftDetector().main()
+diff --git a/tests/unit/zypp_plugins_test.py b/tests/unit/zypp_plugins_test.py
+new file mode 100644
+index 0000000..6075288
+--- /dev/null
++++ b/tests/unit/zypp_plugins_test.py
+@@ -0,0 +1,51 @@
++# -*- coding: utf-8 -*-
++'''
++    :codeauthor: :email:`Bo Maryniuk <bo@suse.de>`
++'''
++
++# Import Python Libs
++from __future__ import absolute_import
++
++# Import Salt Testing Libs
++from salttesting.helpers import ensure_in_syspath
++from salttesting import TestCase, skipIf
++from salttesting.mock import (
++    MagicMock,
++    patch,
++    NO_MOCK,
++    NO_MOCK_REASON
++)
++
++ensure_in_syspath('../')
++
++import os
++import imp
++from zypp_plugin import BogusIO
++
++susemanager = imp.load_source('susemanager', os.path.sep.join(os.path.dirname(__file__).split(
++    os.path.sep)[:-2] + ['scripts', 'zypper', 'plugins', 'commit', 'susemanager']))
++
++@skipIf(NO_MOCK, NO_MOCK_REASON)
++class ZyppPluginsTestCase(TestCase):
++    '''
++    Test shipped libzypp plugins.
++    '''
++    def test_drift_detector(self):
++        '''
++        Test drift detector for a correct cookie file.
++        Returns:
++
++        '''
++        drift = susemanager.DriftDetector()
++        drift._get_mtime = MagicMock(return_value=123)
++        drift._get_checksum = MagicMock(return_value='deadbeef')
++        bogus_io = BogusIO()
++        with patch('susemanager.open', bogus_io):
++            drift.PLUGINEND(None, None)
++        self.assertEqual(str(bogus_io), 'deadbeef 123\n')
++        self.assertEqual(bogus_io.mode, 'w')
++        self.assertEqual(bogus_io.path, '/var/cache/salt/minion/rpmdb.cookie')
++
++if __name__ == '__main__':
++    from integration import run_tests
++    run_tests(ZyppPluginsTestCase, needs_daemon=False)
+diff --git a/tests/zypp_plugin.py b/tests/zypp_plugin.py
+new file mode 100644
+index 0000000..218f703
+--- /dev/null
++++ b/tests/zypp_plugin.py
+@@ -0,0 +1,64 @@
++'''
++Related to zypp_plugins_test.py module.
++'''
++
++
++class Plugin(object):
++    '''
++    Bogus module for Zypp Plugins tests.
++    '''
++    def ack(self):
++        '''
++        Acknowledge that the plugin had finished the transaction
++        Returns:
++
++        '''
++
++    def main(self):
++        '''
++        Register plugin
++        Returns:
++
++        '''
++
++
++class BogusIO(object):
++    '''
++    Read/write logger.
++    '''
++
++    def __init__(self):
++        self.content = list()
++        self.closed = False
++
++    def __str__(self):
++        return '\n'.join(self.content)
++
++    def __call__(self, *args, **kwargs):
++        self.path, self.mode = args
++        return self
++
++    def __exit__(self, exc_type, exc_val, exc_tb):
++        self.close()
++
++    def __enter__(self):
++        return self
++
++    def write(self, data):
++        '''
++        Simulate writing data
++        Args:
++            data:
++
++        Returns:
++
++        '''
++        self.content.append(data)
++
++    def close(self):
++        '''
++        Simulate closing the IO object.
++        Returns:
++
++        '''
++        self.closed = True
+-- 
+2.8.2
+
diff --git a/0007-Force-sort-the-RPM-output-to-ensure-latest-version-o.patch b/0007-Force-sort-the-RPM-output-to-ensure-latest-version-o.patch
deleted file mode 100644
index 6774a4e..0000000
--- a/0007-Force-sort-the-RPM-output-to-ensure-latest-version-o.patch
+++ /dev/null
@@ -1,350 +0,0 @@
-From e3a599712daafb88b6b77ebf6c7684fdd10ffedf Mon Sep 17 00:00:00 2001
-From: Bo Maryniuk <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
-
diff --git a/0008-Cleaner-deprecation-process-with-decorators.patch b/0008-Cleaner-deprecation-process-with-decorators.patch
deleted file mode 100644
index c189641..0000000
--- a/0008-Cleaner-deprecation-process-with-decorators.patch
+++ /dev/null
@@ -1,922 +0,0 @@
-From 2dcc979ab2897619baebfef5779120a98284d408 Mon Sep 17 00:00:00 2001
-From: Bo Maryniuk <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
-
diff --git a/0008-Prevent-several-minion-processes-on-the-same-machine.patch b/0008-Prevent-several-minion-processes-on-the-same-machine.patch
new file mode 100644
index 0000000..07a4506
--- /dev/null
+++ b/0008-Prevent-several-minion-processes-on-the-same-machine.patch
@@ -0,0 +1,25 @@
+From 46713681bda8e45667691fbda6efe808c81574b1 Mon Sep 17 00:00:00 2001
+From: Bo Maryniuk <bo@suse.de>
+Date: Mon, 23 May 2016 17:29:15 +0200
+Subject: [PATCH 8/8] Prevent several minion processes on the same machine
+
+---
+ pkg/suse/salt-minion | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/pkg/suse/salt-minion b/pkg/suse/salt-minion
+index c476fab..28cf32c 100644
+--- a/pkg/suse/salt-minion
++++ b/pkg/suse/salt-minion
+@@ -55,7 +55,7 @@ RETVAL=0
+ start() {
+     echo -n $"Starting salt-minion daemon: "
+     if [ -f $SUSE_RELEASE ]; then
+-        startproc -f -p /var/run/$SERVICE.pid $SALTMINION -d $MINION_ARGS
++        startproc -p /var/run/$SERVICE.pid $SALTMINION -d $MINION_ARGS
+         rc_status -v
+     elif [ -e $DEBIAN_VERSION ]; then
+         if [ -f $LOCKFILE ]; then
+-- 
+2.8.3
+
diff --git a/0009-checksum-validation-when-zypper-pkg.download.patch b/0009-checksum-validation-when-zypper-pkg.download.patch
new file mode 100644
index 0000000..c8f8f63
--- /dev/null
+++ b/0009-checksum-validation-when-zypper-pkg.download.patch
@@ -0,0 +1,74 @@
+From d27af7dee61e83165bbd9adb9f0b6dc467907faa Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?=
+ <psuarezhernandez@suse.com>
+Date: Tue, 24 May 2016 11:01:55 +0100
+Subject: [PATCH 09/11] checksum validation when zypper pkg.download
+
+check the signature of downloaded RPM files
+
+bugfix: showing errors when a package download fails using zypper pkg.download
+
+Renamed check_sig to checksum and some refactoring
+
+simpler rpm.checksum function
+---
+ salt/modules/rpm.py    | 26 ++++++++++++++++++++++++++
+ salt/modules/zypper.py |  6 +++++-
+ 2 files changed, 31 insertions(+), 1 deletion(-)
+
+diff --git a/salt/modules/rpm.py b/salt/modules/rpm.py
+index 1469368..4991f24 100644
+--- a/salt/modules/rpm.py
++++ b/salt/modules/rpm.py
+@@ -602,3 +602,29 @@ def version_cmp(ver1, ver2):
+         log.warning("Failed to compare version '{0}' to '{1}' using RPM: {2}".format(ver1, ver2, exc))
+ 
+     return salt.utils.version_cmp(ver1, ver2)
++
++
++def checksum(*paths):
++    '''
++    Return if the signature of a RPM file is valid.
++
++    CLI Example:
++
++    .. code-block:: bash
++
++        salt '*' lowpkg.checksum /path/to/package1.rpm
++        salt '*' lowpkg.checksum /path/to/package1.rpm /path/to/package2.rpm
++    '''
++    ret = dict()
++
++    if not paths:
++        raise CommandExecutionError("No package files has been specified.")
++
++    for package_file in paths:
++        ret[package_file] = (bool(__salt__['file.file_exists'](package_file)) and
++                            not __salt__['cmd.retcode'](["rpm", "-K", "--quiet", package_file],
++                                                        ignore_retcode=True,
++                                                        output_loglevel='trace',
++                                                        python_shell=False))
++
++    return ret
+diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py
+index f9538e5..39b071b 100644
+--- a/salt/modules/zypper.py
++++ b/salt/modules/zypper.py
+@@ -1534,9 +1534,13 @@ def download(*packages, **kwargs):
+             'repository-alias': repo.getAttribute("alias"),
+             'path': dld_result.getElementsByTagName("localfile")[0].getAttribute("path"),
+         }
+-        pkg_ret[_get_first_aggregate_text(dld_result.getElementsByTagName("name"))] = pkg_info
++        if __salt__['lowpkg.checksum'](pkg_info['path']):
++            pkg_ret[_get_first_aggregate_text(dld_result.getElementsByTagName("name"))] = pkg_info
+ 
+     if pkg_ret:
++        failed = [pkg for pkg in packages if pkg not in pkg_ret]
++        if failed:
++            pkg_ret['_error'] = ('The following package(s) failed to download: {0}'.format(', '.join(failed)))
+         return pkg_ret
+ 
+     raise CommandExecutionError("Unable to download packages: {0}.".format(', '.join(packages)))
+-- 
+2.8.2
+
diff --git a/0009-fix-sorting-by-latest-version-when-called-with-an-at.patch b/0009-fix-sorting-by-latest-version-when-called-with-an-at.patch
deleted file mode 100644
index ef9298c..0000000
--- a/0009-fix-sorting-by-latest-version-when-called-with-an-at.patch
+++ /dev/null
@@ -1,48 +0,0 @@
-From cb588505919b6c74ed824d26a184eec0f47a585b Mon Sep 17 00:00:00 2001
-From: Michael Calmer <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
-
diff --git a/0010-Prevent-metadata-download-when-getting-installed-pro.patch b/0010-Prevent-metadata-download-when-getting-installed-pro.patch
deleted file mode 100644
index 822eae6..0000000
--- a/0010-Prevent-metadata-download-when-getting-installed-pro.patch
+++ /dev/null
@@ -1,29 +0,0 @@
-From 336929a4cadca55b00dbf1cd33eb35d19f420c73 Mon Sep 17 00:00:00 2001
-From: Michael Calmer <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
-
diff --git a/0010-unit-tests-for-rpm.checksum-and-zypper.download.patch b/0010-unit-tests-for-rpm.checksum-and-zypper.download.patch
new file mode 100644
index 0000000..9a3461e
--- /dev/null
+++ b/0010-unit-tests-for-rpm.checksum-and-zypper.download.patch
@@ -0,0 +1,105 @@
+From 2742ee76ccc50cd4f84e44861ef82ec5f3b5234a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?=
+ <psuarezhernandez@suse.com>
+Date: Wed, 25 May 2016 17:08:16 +0100
+Subject: [PATCH 10/11] unit tests for rpm.checksum() and zypper.download()
+
+lint issue fixed
+---
+ tests/unit/modules/rpm_test.py              | 16 ++++++++++++++++
+ tests/unit/modules/zypp/zypper-download.xml | 19 +++++++++++++++++++
+ tests/unit/modules/zypper_test.py           | 25 +++++++++++++++++++++++++
+ 3 files changed, 60 insertions(+)
+ create mode 100644 tests/unit/modules/zypp/zypper-download.xml
+
+diff --git a/tests/unit/modules/rpm_test.py b/tests/unit/modules/rpm_test.py
+index f180736..4042137 100644
+--- a/tests/unit/modules/rpm_test.py
++++ b/tests/unit/modules/rpm_test.py
+@@ -95,6 +95,22 @@ class RpmTestCase(TestCase):
+             self.assertDictEqual(rpm.owner('/usr/bin/python', '/usr/bin/vim'),
+                                  ret)
+ 
++    # 'checksum' function tests: 1
++
++    def test_checksum(self):
++        '''
++        Test if checksum validate as expected
++        '''
++        ret = {
++            "file1.rpm": True,
++            "file2.rpm": False,
++            "file3.rpm": False,
++        }
++
++        mock = MagicMock(side_effect=[True, 0, True, 1, False, 0])
++        with patch.dict(rpm.__salt__, {'file.file_exists': mock, 'cmd.retcode': mock}):
++            self.assertDictEqual(rpm.checksum("file1.rpm", "file2.rpm", "file3.rpm"), ret)
++
+     @patch('salt.modules.rpm.HAS_RPM', True)
+     def test_version_cmp_rpm(self):
+         '''
+diff --git a/tests/unit/modules/zypp/zypper-download.xml b/tests/unit/modules/zypp/zypper-download.xml
+new file mode 100644
+index 0000000..eeea0a5
+--- /dev/null
++++ b/tests/unit/modules/zypp/zypper-download.xml
+@@ -0,0 +1,19 @@
++<?xml version='1.0'?>
++<stream>
++  <message type="info">Loading repository data...</message>
++  <message type="info">Reading installed packages...</message>
++  <message type="warning">Argument resolves to no package: foo</message>
++  <progress id="" name="(1/1) /var/cache/zypp/packages/SLE-12-x86_64-Pool/x86_64/nmap-6.46-1.72.x86_64.rpm"/>
++  <download-result>
++    <solvable>
++      <kind>package</kind>
++      <name>nmap</name>
++      <edition epoch="0" version="6.46" release="1.72"/>
++      <arch>x86_64</arch>
++      <repository name="SLE-12-x86_64-Pool" alias="SLE-12-x86_64-Pool"/>
++    </solvable>
++    <localfile path="/var/cache/zypp/packages/SLE-12-x86_64-Pool/x86_64/nmap-6.46-1.72.x86_64.rpm"/>
++  </download-result>
++  <progress id="" name="(1/1) /var/cache/zypp/packages/SLE-12-x86_64-Pool/x86_64/nmap-6.46-1.72.x86_64.rpm" done="0"/>
++  <message type="info">download: Done.</message>
++</stream>
+diff --git a/tests/unit/modules/zypper_test.py b/tests/unit/modules/zypper_test.py
+index 4e735cd..9ec2b83 100644
+--- a/tests/unit/modules/zypper_test.py
++++ b/tests/unit/modules/zypper_test.py
+@@ -354,6 +354,31 @@ class ZypperTestCase(TestCase):
+                             self.assertTrue(pkgs.get(pkg_name))
+                             self.assertEqual(pkgs[pkg_name], pkg_version)
+ 
++    def test_download(self):
++        '''
++        Test package download
++        :return:
++        '''
++        download_out = {
++            'stdout': get_test_data('zypper-download.xml'),
++            'stderr': None,
++            'retcode': 0
++        }
++
++        test_out = {
++            'nmap': {
++                'path': u'/var/cache/zypp/packages/SLE-12-x86_64-Pool/x86_64/nmap-6.46-1.72.x86_64.rpm',
++                'repository-alias': u'SLE-12-x86_64-Pool',
++                'repository-name': u'SLE-12-x86_64-Pool'
++            }
++        }
++
++        with patch.dict(zypper.__salt__, {'cmd.run_all': MagicMock(return_value=download_out)}):
++            with patch.dict(zypper.__salt__, {'lowpkg.checksum': MagicMock(return_value=True)}):
++                self.assertEqual(zypper.download("nmap"), test_out)
++                test_out['_error'] = "The following package(s) failed to download: foo"
++                self.assertEqual(zypper.download("nmap", "foo"), test_out)
++
+     def test_remove_purge(self):
+         '''
+         Test package removal
+-- 
+2.8.2
+
diff --git a/0011-Check-if-EOL-is-available-in-a-particular-product-bs.patch b/0011-Check-if-EOL-is-available-in-a-particular-product-bs.patch
deleted file mode 100644
index 22a598f..0000000
--- a/0011-Check-if-EOL-is-available-in-a-particular-product-bs.patch
+++ /dev/null
@@ -1,146 +0,0 @@
-From aae1c09957eab3c89a6c8f78a579cdf9dcfbe188 Mon Sep 17 00:00:00 2001
-From: Bo Maryniuk <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 world’s
-+        most powerful high-performance computing
-+        and mainframe servers. SUSE Linux Enterprise
-+        offers common management tools and technology
-+        certifications across the platform, and
-+        each product is enterprise-class.</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
-
diff --git a/0011-jobs.exit_success-allow-to-check-if-a-job-has-execut.patch b/0011-jobs.exit_success-allow-to-check-if-a-job-has-execut.patch
new file mode 100644
index 0000000..2614e9f
--- /dev/null
+++ b/0011-jobs.exit_success-allow-to-check-if-a-job-has-execut.patch
@@ -0,0 +1,53 @@
+From e0f15c5292869549b5c80997ccb3282961be8e49 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?=
+ <psuarezhernandez@suse.com>
+Date: Tue, 24 May 2016 09:21:43 +0100
+Subject: [PATCH 11/11] jobs.exit_success allow to check if a job has executed
+ and exit successfully
+
+jobs.exit_success() now works parsing the results of jobs.lookup_id()
+---
+ salt/runners/jobs.py | 27 +++++++++++++++++++++++++++
+ 1 file changed, 27 insertions(+)
+
+diff --git a/salt/runners/jobs.py b/salt/runners/jobs.py
+index 57d0324..e2b8737 100644
+--- a/salt/runners/jobs.py
++++ b/salt/runners/jobs.py
+@@ -488,6 +488,33 @@ def print_job(jid, ext_source=None, outputter=None):
+         return ret
+ 
+ 
++def exit_success(jid, ext_source=None):
++    '''
++    Check if a job has been executed and exit successfully
++
++    jid
++        The jid to look up.
++    ext_source
++        The external job cache to use. Default: `None`.
++
++    CLI Example:
++    .. code-block:: bash
++        salt-run jobs.exit_success 20160520145827701627
++    '''
++    ret = dict()
++
++    data = lookup_jid(
++        jid,
++        ext_source=ext_source
++    )
++
++    for minion in data:
++        if "retcode" in data[minion]:
++            ret[minion] = True if not data[minion]['retcode'] else False
++
++    return ret
++
++
+ def last_run(ext_source=None,
+              outputter=None,
+              metadata=None,
+-- 
+2.8.2
+
diff --git a/0012-Bugfix-salt-key-crashes-if-tries-to-generate-keys-to.patch b/0012-Bugfix-salt-key-crashes-if-tries-to-generate-keys-to.patch
deleted file mode 100644
index 89c2824..0000000
--- a/0012-Bugfix-salt-key-crashes-if-tries-to-generate-keys-to.patch
+++ /dev/null
@@ -1,69 +0,0 @@
-From 5e99ee2bec1139b1944284975454c716d477f3e0 Mon Sep 17 00:00:00 2001
-From: Bo Maryniuk <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
-
diff --git a/0012-Fix-pkgrepo.managed-gpgkey-argument-bsc-979448.patch b/0012-Fix-pkgrepo.managed-gpgkey-argument-bsc-979448.patch
new file mode 100644
index 0000000..760aef7
--- /dev/null
+++ b/0012-Fix-pkgrepo.managed-gpgkey-argument-bsc-979448.patch
@@ -0,0 +1,325 @@
+From b0e1ba5158cc4b54102bac200ae343935eeb2db5 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Mihai=20Dinc=C4=83?= <dincamihai@users.noreply.github.com>
+Date: Mon, 23 May 2016 23:15:29 +0200
+Subject: [PATCH 12/12] Fix pkgrepo.managed gpgkey argument (bsc#979448)
+
+* Call zypper refresh after adding/modifying a repository
+
+* Calling `zypper --gpg-auto-import-keys refresh` is required after
+adding/modifying a repository because `--gpg-auto-import-keys` doesn't
+do anything when called with `zypper ar` or `zypper mr`.
+Without calling `zypper --gpg-auto-import-keys refresh` here, calling
+`zypper ref` after adding/removing would still ask for
+accepting/rejecting the gpg key.
+
+* Update test method names to pass pylint
+
+* Reduce dicts and lists to one line where possible
+
+* Reverse if conditions and rename variable
+
+* Assert only gpgautoimport: True works
+
+* Improve zypper_patcher_config looks
+
+* DRY test
+---
+ salt/modules/zypper.py            |  24 +++--
+ tests/unit/modules/zypper_test.py | 213 ++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 229 insertions(+), 8 deletions(-)
+
+diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py
+index 39b071b..2c72448 100644
+--- a/salt/modules/zypper.py
++++ b/salt/modules/zypper.py
+@@ -767,6 +767,8 @@ def mod_repo(repo, **kwargs):
+ 
+     # Modify added or existing repo according to the options
+     cmd_opt = []
++    global_cmd_opt = []
++    call_refresh = False
+ 
+     if 'enabled' in kwargs:
+         cmd_opt.append(kwargs['enabled'] and '--enable' or '--disable')
+@@ -780,21 +782,27 @@ def mod_repo(repo, **kwargs):
+     if 'gpgcheck' in kwargs:
+         cmd_opt.append(kwargs['gpgcheck'] and '--gpgcheck' or '--no-gpgcheck')
+ 
+-    if kwargs.get('gpgautoimport') is True:
+-        cmd_opt.append('--gpg-auto-import-keys')
+-
+     if 'priority' in kwargs:
+         cmd_opt.append("--priority={0}".format(kwargs.get('priority', DEFAULT_PRIORITY)))
+ 
+     if 'humanname' in kwargs:
+         cmd_opt.append("--name='{0}'".format(kwargs.get('humanname')))
+ 
+-    if cmd_opt:
+-        cmd_opt.append(repo)
+-        __zypper__.refreshable.xml.call('mr', *cmd_opt)
++    if kwargs.get('gpgautoimport') is True:
++        global_cmd_opt.append('--gpg-auto-import-keys')
++        call_refresh = True
+ 
+-    # If repo nor added neither modified, error should be thrown
+-    if not added and not cmd_opt:
++    if cmd_opt:
++        cmd_opt = global_cmd_opt + ['mr'] + cmd_opt + [repo]
++        __zypper__.refreshable.xml.call(*cmd_opt)
++
++    if call_refresh:
++        # when used with "zypper ar --refresh" or "zypper mr --refresh"
++        # --gpg-auto-import-keys is not doing anything
++        # so we need to specifically refresh here with --gpg-auto-import-keys
++        refresh_opts = global_cmd_opt + ['refresh'] + [repo]
++        __zypper__.xml.call(*refresh_opts)
++    elif not added and not cmd_opt:
+         raise CommandExecutionError(
+             'Specified arguments did not result in modification of repo'
+         )
+diff --git a/tests/unit/modules/zypper_test.py b/tests/unit/modules/zypper_test.py
+index 9ec2b83..c4f7597 100644
+--- a/tests/unit/modules/zypper_test.py
++++ b/tests/unit/modules/zypper_test.py
+@@ -9,7 +9,9 @@ from __future__ import absolute_import
+ # Import Salt Testing Libs
+ from salttesting import TestCase, skipIf
+ from salttesting.mock import (
++    Mock,
+     MagicMock,
++    call,
+     patch,
+     NO_MOCK,
+     NO_MOCK_REASON
+@@ -54,10 +56,26 @@ zypper.rpm = None
+ 
+ @skipIf(NO_MOCK, NO_MOCK_REASON)
+ class ZypperTestCase(TestCase):
++
+     '''
+     Test cases for salt.modules.zypper
+     '''
+ 
++    def setUp(self):
++        self.new_repo_config = dict(
++            name='mock-repo-name',
++            url='http://repo.url/some/path'
++        )
++        side_effect = [
++            Mock(**{'sections.return_value': []}),
++            Mock(**{'sections.return_value': [self.new_repo_config['name']]})
++        ]
++        self.zypper_patcher_config = {
++            '_get_configured_repos': Mock(side_effect=side_effect),
++            '__zypper__': Mock(),
++            'get_repo': Mock()
++        }
++
+     def test_list_upgrades(self):
+         '''
+         List package upgrades
+@@ -438,6 +456,201 @@ class ZypperTestCase(TestCase):
+             self.assertEqual(r_info['enabled'], alias == 'SLE12-SP1-x86_64-Update')
+             self.assertEqual(r_info['autorefresh'], alias == 'SLE12-SP1-x86_64-Update')
+ 
++    def test_repo_add_nomod_noref(self):
++        '''
++        Test mod_repo adds the new repo and nothing else
++
++        :return:
++        '''
++        zypper_patcher = patch.multiple(
++            'salt.modules.zypper', **self.zypper_patcher_config)
++
++        url = self.new_repo_config['url']
++        name = self.new_repo_config['name']
++        with zypper_patcher:
++            zypper.mod_repo(name, **{'url': url})
++            self.assertEqual(
++                zypper.__zypper__.xml.call.call_args_list,
++                [call('ar', url, name)]
++            )
++            zypper.__zypper__.refreshable.xml.call.assert_not_called()
++
++    def test_repo_noadd_nomod_noref(self):
++        '''
++        Test mod_repo detects the repo already exists,
++        no modification was requested and no refresh requested either
++
++        :return:
++        '''
++        url = self.new_repo_config['url']
++        name = self.new_repo_config['name']
++        self.zypper_patcher_config['_get_configured_repos'] = Mock(
++            **{'return_value.sections.return_value': [name]}
++        )
++        zypper_patcher = patch.multiple(
++            'salt.modules.zypper', **self.zypper_patcher_config)
++
++        with zypper_patcher:
++            with self.assertRaisesRegexp(
++                Exception,
++                'Specified arguments did not result in modification of repo'
++            ):
++                zypper.mod_repo(name, **{'url': url})
++            with self.assertRaisesRegexp(
++                Exception,
++                'Specified arguments did not result in modification of repo'
++            ):
++                zypper.mod_repo(name, **{'url': url, 'gpgautoimport': 'a'})
++
++            zypper.__zypper__.xml.call.assert_not_called()
++            zypper.__zypper__.refreshable.xml.call.assert_not_called()
++
++    def test_repo_add_mod_noref(self):
++        '''
++        Test mod_repo adds the new repo and call modify to update autorefresh
++
++        :return:
++        '''
++        zypper_patcher = patch.multiple(
++            'salt.modules.zypper', **self.zypper_patcher_config)
++
++        url = self.new_repo_config['url']
++        name = self.new_repo_config['name']
++        with zypper_patcher:
++            zypper.mod_repo(name, **{'url': url, 'refresh': True})
++            self.assertEqual(
++                zypper.__zypper__.xml.call.call_args_list,
++                [call('ar', url, name)]
++            )
++            zypper.__zypper__.refreshable.xml.call.assert_called_once_with(
++                'mr', '--refresh', name
++            )
++
++    def test_repo_noadd_mod_noref(self):
++        '''
++        Test mod_repo detects the repository exists,
++        calls modify to update 'autorefresh' but does not call refresh
++
++        :return:
++        '''
++        url = self.new_repo_config['url']
++        name = self.new_repo_config['name']
++        self.zypper_patcher_config['_get_configured_repos'] = Mock(
++            **{'return_value.sections.return_value': [name]})
++        zypper_patcher = patch.multiple(
++            'salt.modules.zypper', **self.zypper_patcher_config)
++        with zypper_patcher:
++            zypper.mod_repo(name, **{'url': url, 'refresh': True})
++            zypper.__zypper__.xml.call.assert_not_called()
++            zypper.__zypper__.refreshable.xml.call.assert_called_once_with(
++                'mr', '--refresh', name
++            )
++
++    def test_repo_add_nomod_ref(self):
++        '''
++        Test mod_repo adds the new repo and refreshes the repo with
++            `zypper --gpg-auto-import-keys refresh <repo-name>`
++
++        :return:
++        '''
++        zypper_patcher = patch.multiple(
++            'salt.modules.zypper', **self.zypper_patcher_config)
++
++        url = self.new_repo_config['url']
++        name = self.new_repo_config['name']
++        with zypper_patcher:
++            zypper.mod_repo(name, **{'url': url, 'gpgautoimport': True})
++            self.assertEqual(
++                zypper.__zypper__.xml.call.call_args_list,
++                [
++                    call('ar', url, name),
++                    call('--gpg-auto-import-keys', 'refresh', name)
++                ]
++            )
++            zypper.__zypper__.refreshable.xml.call.assert_not_called()
++
++    def test_repo_noadd_nomod_ref(self):
++        '''
++        Test mod_repo detects the repo already exists,
++        has nothing to modify and refreshes the repo with
++            `zypper --gpg-auto-import-keys refresh <repo-name>`
++
++        :return:
++        '''
++        url = self.new_repo_config['url']
++        name = self.new_repo_config['name']
++        self.zypper_patcher_config['_get_configured_repos'] = Mock(
++            **{'return_value.sections.return_value': [name]}
++        )
++        zypper_patcher = patch.multiple(
++            'salt.modules.zypper', **self.zypper_patcher_config)
++
++        with zypper_patcher:
++            zypper.mod_repo(name, **{'url': url, 'gpgautoimport': True})
++            self.assertEqual(
++                zypper.__zypper__.xml.call.call_args_list,
++                [call('--gpg-auto-import-keys', 'refresh', name)]
++            )
++            zypper.__zypper__.refreshable.xml.call.assert_not_called()
++
++    def test_repo_add_mod_ref(self):
++        '''
++        Test mod_repo adds the new repo,
++        calls modify to update 'autorefresh' and refreshes the repo with
++            `zypper --gpg-auto-import-keys refresh <repo-name>`
++
++        :return:
++        '''
++        zypper_patcher = patch.multiple(
++            'salt.modules.zypper', **self.zypper_patcher_config)
++
++        url = self.new_repo_config['url']
++        name = self.new_repo_config['name']
++        with zypper_patcher:
++            zypper.mod_repo(
++                name,
++                **{'url': url, 'refresh': True, 'gpgautoimport': True}
++            )
++            self.assertEqual(
++                zypper.__zypper__.xml.call.call_args_list,
++                [
++                    call('ar', url, name),
++                    call('--gpg-auto-import-keys', 'refresh', name)
++                ]
++            )
++            zypper.__zypper__.refreshable.xml.call.assert_called_once_with(
++                '--gpg-auto-import-keys', 'mr', '--refresh', name
++            )
++
++    def test_repo_noadd_mod_ref(self):
++        '''
++        Test mod_repo detects the repo already exists,
++        calls modify to update 'autorefresh' and refreshes the repo with
++            `zypper --gpg-auto-import-keys refresh <repo-name>`
++
++        :return:
++        '''
++        url = self.new_repo_config['url']
++        name = self.new_repo_config['name']
++        self.zypper_patcher_config['_get_configured_repos'] = Mock(
++            **{'return_value.sections.return_value': [name]}
++        )
++        zypper_patcher = patch.multiple(
++            'salt.modules.zypper', **self.zypper_patcher_config)
++
++        with zypper_patcher:
++            zypper.mod_repo(
++                name,
++                **{'url': url, 'refresh': True, 'gpgautoimport': True}
++            )
++            self.assertEqual(
++                zypper.__zypper__.xml.call.call_args_list,
++                [call('--gpg-auto-import-keys', 'refresh', name)]
++            )
++            zypper.__zypper__.refreshable.xml.call.assert_called_once_with(
++                '--gpg-auto-import-keys', 'mr', '--refresh', name
++            )
++
+ if __name__ == '__main__':
+     from integration import run_tests
+     run_tests(ZypperTestCase, needs_daemon=False)
+-- 
+2.8.3
+
diff --git a/0013-Prevent-crash-if-pygit2-package-is-requesting-re-com.patch b/0013-Prevent-crash-if-pygit2-package-is-requesting-re-com.patch
deleted file mode 100644
index 6b78d19..0000000
--- a/0013-Prevent-crash-if-pygit2-package-is-requesting-re-com.patch
+++ /dev/null
@@ -1,86 +0,0 @@
-From f187ee058eb221eb5a34d51ca5db53bb8eeea5e1 Mon Sep 17 00:00:00 2001
-From: Bo Maryniuk <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
-
diff --git a/0014-align-OS-grains-from-older-SLES-with-current-one-326.patch b/0014-align-OS-grains-from-older-SLES-with-current-one-326.patch
deleted file mode 100644
index f438967..0000000
--- a/0014-align-OS-grains-from-older-SLES-with-current-one-326.patch
+++ /dev/null
@@ -1,39 +0,0 @@
-From 0961f5bd3e3b7aa3ebd75fe064044d078df62724 Mon Sep 17 00:00:00 2001
-From: Michael Calmer <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
-
diff --git a/0015-Unblock-Zypper.-Modify-environment.patch b/0015-Unblock-Zypper.-Modify-environment.patch
deleted file mode 100644
index 93e59c8..0000000
--- a/0015-Unblock-Zypper.-Modify-environment.patch
+++ /dev/null
@@ -1,904 +0,0 @@
-From e52c7926a699bdee3fad2767c8aa755ee115c5d7 Mon Sep 17 00:00:00 2001
-From: Bo Maryniuk <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
-
diff --git a/0016-Bugfix-Restore-boolean-values-from-the-repo-configur.patch b/0016-Bugfix-Restore-boolean-values-from-the-repo-configur.patch
deleted file mode 100644
index 02223e5..0000000
--- a/0016-Bugfix-Restore-boolean-values-from-the-repo-configur.patch
+++ /dev/null
@@ -1,118 +0,0 @@
-From e52b55979bdc0734c2e452dd2fd67fb56a3fb37b Mon Sep 17 00:00:00 2001
-From: Bo Maryniuk <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
-
diff --git a/0017-Add-SUSE-Manager-plugin.patch b/0017-Add-SUSE-Manager-plugin.patch
deleted file mode 100644
index 4cc5571..0000000
--- a/0017-Add-SUSE-Manager-plugin.patch
+++ /dev/null
@@ -1,103 +0,0 @@
-From 92f17a79c53bb5b75b9dac4aa0add94dfe2f447f Mon Sep 17 00:00:00 2001
-From: Bo Maryniuk <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
-
diff --git a/salt-2015.8.10.tar.gz b/salt-2015.8.10.tar.gz
new file mode 100644
index 0000000..598236b
--- /dev/null
+++ b/salt-2015.8.10.tar.gz
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4a81273ab4b01e0f1d29b28ab99a16eb94e5c430107b05491c94f3baf8b95c99
+size 6972776
diff --git a/salt-2015.8.8.tar.gz b/salt-2015.8.8.tar.gz
deleted file mode 100644
index 4c5f94d..0000000
--- a/salt-2015.8.8.tar.gz
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:b2ecce7bf562cfcd6586d66ade278f268bb89023f0fa0accaa55f90b8a668ef5
-size 6982904
diff --git a/salt.changes b/salt.changes
index 9278333..33eadb4 100644
--- a/salt.changes
+++ b/salt.changes
@@ -1,3 +1,98 @@
+-------------------------------------------------------------------
+Wed Jun  1 09:52:40 UTC 2016 - mihai.dinca@suse.com
+
+- Fix pkgrepo.managed gpgkey argument doesn't work (bsc#979448)
+  Add:
+  * 0012-Fix-pkgrepo.managed-gpgkey-argument-bsc-979448.patch
+
+-------------------------------------------------------------------
+Thu May 26 11:05:04 UTC 2016 - pablo.suarezhernandez@suse.com
+
+- Package checksum validation for zypper pkg.download
+  Add:
+  * 0009-checksum-validation-when-zypper-pkg.download.patch
+  * 0010-unit-tests-for-rpm.checksum-and-zypper.download.patch
+- Check if a job has executed and returned successfully
+  Add:
+  * 0011-jobs.exit_success-allow-to-check-if-a-job-has-execut.patch
+
+-------------------------------------------------------------------
+Tue May 24 11:06:12 UTC 2016 - bmaryniuk@suse.com
+
+- Prevent several minion processes on the same machine (bsc#975733)
+
+  Add:
+  * 0008-Prevent-several-minion-processes-on-the-same-machine.patch
+
+-------------------------------------------------------------------
+Mon May 23 15:11:42 UTC 2016 - bmaryniuk@suse.com
+
+- Changed Zypper's plugin. Added Unit test and related to that 
+  data (bsc#980313).
+
+  Add:
+  * 0006-Create-salt-proxy-instantiated-service-file.patch
+  * 0007-Add-SUSE-Manager-plugin.patch
+
+  Remove:
+  * 0006-Add-SUSE-Manager-plugin.patch
+  * 0007-Create-salt-proxy-instantiated-service-file.patch
+  * 0008-Alter-the-event-name.patch
+
+
+-------------------------------------------------------------------
+Mon May 23 09:52:29 UTC 2016 - tampakrap@opensuse.org
+
+- Update to 2015.8.10
+  see https://docs.saltstack.com/en/latest/topics/releases/2015.8.10.html
+
+-------------------------------------------------------------------
+Fri May 20 10:54:42 UTC 2016 - tampakrap@opensuse.org
+
+- Update to 2015.8.9
+  see https://docs.saltstack.com/en/latest/topics/releases/2015.8.9.html
+  Patches renamed:
+  * 0006-Add-SUSE-Manager-plugin.patch
+  * 0007-Create-salt-proxy-instantiated-service-file.patch
+  * 0008-Alter-the-event-name.patch
+  Patches removed:
+  * 0006-Update-to-2015.8.8.2.patch
+  * 0007-Force-sort-the-RPM-output-to-ensure-latest-version-o.patch
+  * 0008-Cleaner-deprecation-process-with-decorators.patch
+  * 0009-fix-sorting-by-latest-version-when-called-with-an-at.patch
+  * 0010-Prevent-metadata-download-when-getting-installed-pro.patch
+  * 0011-Check-if-EOL-is-available-in-a-particular-product-bs.patch
+  * 0012-Bugfix-salt-key-crashes-if-tries-to-generate-keys-to.patch
+  * 0013-Prevent-crash-if-pygit2-package-is-requesting-re-com.patch
+  * 0014-align-OS-grains-from-older-SLES-with-current-one-326.patch
+  * 0015-Unblock-Zypper.-Modify-environment.patch
+  * 0016-Bugfix-Restore-boolean-values-from-the-repo-configur.patch
+  * 0017-Add-SUSE-Manager-plugin.patch
+  * 0018-Create-salt-proxy-instantiated-service-file.patch
+  * 0019-Alter-the-event-name.patch
+
+-------------------------------------------------------------------
+Tue May 17 10:08:57 UTC 2016 - bmaryniuk@suse.com
+
+- Zypper plugin: alter the generated event name on package set
+  change.
+  Add:
+  * 0019-Alter-the-event-name.patch
+
+-------------------------------------------------------------------
+Thu May 12 08:52:26 UTC 2016 - pablo.suarezhernandez@suse.com
+
+- salt-proxy .service file created (bsc#975306)
+  Add:
+  * 0018-Create-salt-proxy-instantiated-service-file.patch
+
+-------------------------------------------------------------------
+Thu May 12 00:42:34 UTC 2016 - tserong@suse.com
+
+- Fix file ownership on master keys and cache directories during upgrade
+  (handles upgrading from salt 2014, where the daemon ran as root, to 2015
+  where it runs as the salt user, bsc#979676).
+
 -------------------------------------------------------------------
 Wed May 11 07:20:40 UTC 2016 - bmaryniuk@suse.com
 
diff --git a/salt.spec b/salt.spec
index 98217ca..19e8a97 100644
--- a/salt.spec
+++ b/salt.spec
@@ -36,7 +36,7 @@
 %bcond_without docs
 
 Name:           salt
-Version:        2015.8.8
+Version:        2015.8.10
 Release:        0
 Summary:        A parallel remote execution system
 License:        Apache-2.0
@@ -57,31 +57,22 @@ Patch3:         0003-Check-if-byte-strings-are-properly-encoded-in-UTF-8.patch
 Patch4:         0004-do-not-generate-a-date-in-a-comment-to-prevent-rebui.patch
 # PATCH-FIX-OPENSUSE - Upstream default hash type is set to MD5, while we require SHA256 (bsc#955373)
 Patch5:         0005-Use-SHA256-hash-type-by-default.patch
-# PATCH-FIX-UPSTREAM https://docs.saltstack.com/en/latest/topics/releases/2015.8.8.html#salt-2015-8-8-2
-Patch6:         0006-Update-to-2015.8.8.2.patch
-# PATCH-FIX-UPSTREAM https://github.com/saltstack/salt/pull/32243
-Patch7:         0007-Force-sort-the-RPM-output-to-ensure-latest-version-o.patch
-# PATCH-FIX-UPSTREAM https://github.com/saltstack/salt/pull/32068
-Patch8:         0008-Cleaner-deprecation-process-with-decorators.patch
-# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/32323
-Patch9:         0009-fix-sorting-by-latest-version-when-called-with-an-at.patch
-# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/32353
-Patch10:        0010-Prevent-metadata-download-when-getting-installed-pro.patch
-# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/32505
-Patch11:        0011-Check-if-EOL-is-available-in-a-particular-product-bs.patch
-# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/32436
-Patch12:        0012-Bugfix-salt-key-crashes-if-tries-to-generate-keys-to.patch
-# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/32652
-Patch13:        0013-Prevent-crash-if-pygit2-package-is-requesting-re-com.patch
-# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/32649
-Patch14:        0014-align-OS-grains-from-older-SLES-with-current-one-326.patch
-# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/32892
-Patch15:        0015-Unblock-Zypper.-Modify-environment.patch
-# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/33088
-Patch16:        0016-Bugfix-Restore-boolean-values-from-the-repo-configur.patch
+# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/31798
+Patch6:         0006-Create-salt-proxy-instantiated-service-file.patch
 # PATCH-FIX-OPENSUSE Generate events from the Salt minion,
 #                    if Zypper has been used outside the Salt infrastructure
-Patch17:        0017-Add-SUSE-Manager-plugin.patch
+Patch7:         0007-Add-SUSE-Manager-plugin.patch
+# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/33464
+Patch8:         0008-Prevent-several-minion-processes-on-the-same-machine.patch
+# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/33469
+Patch9:         0009-checksum-validation-when-zypper-pkg.download.patch
+# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/33501
+Patch10:        0010-unit-tests-for-rpm.checksum-and-zypper.download.patch
+# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/33465
+Patch11:        0011-jobs.exit_success-allow-to-check-if-a-job-has-execut.patch
+# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/33432
+#                    https://github.com/saltstack/salt/pull/33581
+Patch12:        0012-Fix-pkgrepo.managed-gpgkey-argument-bsc-979448.patch
 
 BuildRoot:      %{_tmppath}/%{name}-%{version}-build
 BuildRequires:  logrotate
@@ -432,21 +423,17 @@ cp %{S:1} .
 %patch4 -p1
 %patch5 -p1
 %patch6 -p1
+
+# This is SUSE-only patch
+%if 0%{?suse_version}
 %patch7 -p1
 %patch8 -p1
+%endif
+
 %patch9 -p1
 %patch10 -p1
 %patch11 -p1
 %patch12 -p1
-%patch13 -p1
-%patch14 -p1
-%patch15 -p1
-%patch16 -p1
-
-# This is SUSE-only patch
-%if 0%{?suse_version}
-%patch17 -p1
-%endif
 
 %build
 python setup.py --salt-transport=both build
@@ -507,6 +494,7 @@ install -Dpm 0644 pkg/salt-master.service %{buildroot}%{_unitdir}/salt-master.se
 install -Dpm 0644 pkg/salt-minion.service %{buildroot}%{_unitdir}/salt-minion.service
 install -Dpm 0644 pkg/salt-syndic.service %{buildroot}%{_unitdir}/salt-syndic.service
 install -Dpm 0644 pkg/salt-api.service    %{buildroot}%{_unitdir}/salt-api.service
+install -Dpm 0644 pkg/salt-proxy@.service  %{buildroot}%{_unitdir}/salt-proxy@.service
 ln -s service %{buildroot}%{_sbindir}/rcsalt-master
 ln -s service %{buildroot}%{_sbindir}/rcsalt-syndic
 ln -s service %{buildroot}%{_sbindir}/rcsalt-minion
@@ -631,6 +619,20 @@ systemd-tmpfiles --create /usr/lib/tmpfiles.d/salt.conf || true
 %endif
 
 %post master
+if [ $1 -eq 2 ] ; then
+  # Upgrading from an earlier version.  If this is from 2014, where daemons
+  # ran as root, we need to chown some stuff to salt in order for the new
+  # version to actually work.  It seems a manual restart of salt-master may
+  # still be required, but at least this will actually work given the file
+  # ownership is correct.
+  for file in master.{pem,pub} ; do
+    [ -f /etc/salt/pki/master/$file ] && chown salt /etc/salt/pki/master/$file
+  done
+  for dir in file_lists minions jobs ; do
+    [ -d /var/cache/salt/master/$dir ] && chown -R salt:salt /var/cache/salt/master/$dir
+  done
+  true
+fi
 %if %{with systemd}
 %service_add_post salt-master.service
 %fillup_only
@@ -796,6 +798,9 @@ systemd-tmpfiles --create /usr/lib/tmpfiles.d/salt.conf || true
 %defattr(-,root,root)
 %{_bindir}/salt-proxy
 %{_mandir}/man1/salt-proxy.1.gz
+%if %{with systemd}
+%{_unitdir}/salt-proxy@.service
+%endif
 
 %files master
 %defattr(-,root,root)