From 12d9036d77e09c367d53c30fb7a244d94bb4a110 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?=
 <psuarezhernandez@suse.com>
Date: Tue, 18 Oct 2016 14:14:42 +0100
Subject: [PATCH 23/38] Adding 'dist-upgrade' support to zypper module

* Unit tests for zypper upgrade and dist-upgrade
* Refactor: Cleanup and pylint fixes
* Fix in log message
* Adds multiple repositories support to 'fromrepo' parameter
* Improves 'dryrun' outputting. Setting 'novendorchange' as not supported for SLE11
* Unit tests fixes
* Minor pylint fixes
* Disables 'novendorchange' for old SLEs versions
---
 salt/modules/zypper.py            | 53 +++++++++++++++++++++++++++--
 tests/unit/modules/zypper_test.py | 71 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 121 insertions(+), 3 deletions(-)

diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py
index 9120ebd..78a6030 100644
--- a/salt/modules/zypper.py
+++ b/salt/modules/zypper.py
@@ -1049,7 +1049,12 @@ def install(name=None,
     return salt.utils.compare_dicts(old, new)
 
 
-def upgrade(refresh=True):
+def upgrade(refresh=True,
+            dryrun=False,
+            dist_upgrade=False,
+            fromrepo=None,
+            novendorchange=False,
+            **kwargs):  # pylint: disable=unused-argument
     '''
     .. versionchanged:: 2015.8.12,2016.3.3,Carbon
         On minions running systemd>=205, `systemd-run(1)`_ is now used to
@@ -1072,6 +1077,19 @@ def upgrade(refresh=True):
         If set to False it depends on zypper if a refresh is
         executed.
 
+    dryrun
+        If set to True, it creates a debug solver log file and then perform
+        a dry-run upgrade (no changes are made). Default: False
+
+    dist_upgrade
+        Perform a system dist-upgrade. Default: False
+
+    fromrepo
+        Specify a list of package repositories to upgrade from. Default: None
+
+    novendorchange
+        If set to True, no allow vendor changes. Default: False
+
     Return a dict containing the new package names and versions::
 
         {'<package>': {'old': '<old-version>',
@@ -1082,24 +1100,53 @@ def upgrade(refresh=True):
     .. code-block:: bash
 
         salt '*' pkg.upgrade
+        salt '*' pkg.upgrade dist-upgrade=True fromrepo='["MyRepoName"]' novendorchange=True
+        salt '*' pkg.upgrade dist-upgrade=True dryrun=True
     '''
     ret = {'changes': {},
            'result': True,
            'comment': '',
     }
 
+    cmd_update = (['dist-upgrade'] if dist_upgrade else ['update']) + ['--auto-agree-with-licenses']
+
     if refresh:
         refresh_db()
+
+    if dryrun:
+        cmd_update.append('--dry-run')
+
+    if dist_upgrade:
+        if dryrun:
+            # Creates a solver test case for debugging.
+            log.info('Executing debugsolver and performing a dry-run dist-upgrade')
+            __zypper__.noraise.call(*cmd_update + ['--debug-solver'])
+
+        if fromrepo:
+            for repo in fromrepo:
+                cmd_update.extend(['--from', repo])
+            log.info('Targeting repos: {0!r}'.format(fromrepo))
+
+        if novendorchange:
+            # TODO: Grains validation should be moved to Zypper class
+            if __grains__['osrelease_info'][0] > 11:
+                cmd_update.append('--no-allow-vendor-change')
+                log.info('Disabling vendor changes')
+            else:
+                log.warn('Disabling vendor changes is not supported on this Zypper version')
+
     old = list_pkgs()
-    __zypper__(systemd_scope=_systemd_scope()).noraise.call('update', '--auto-agree-with-licenses')
+    __zypper__.noraise.call(*cmd_update)
     if __zypper__.exit_code not in __zypper__.SUCCESS_EXIT_CODES:
         ret['result'] = False
-        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)
 
+    if dryrun or not ret['result']:
+        ret['comment'] = (__zypper__.stdout() + os.linesep + __zypper__.stderr()).strip()
+
     return ret
 
 
diff --git a/tests/unit/modules/zypper_test.py b/tests/unit/modules/zypper_test.py
index 3f6a39d..00e746e 100644
--- a/tests/unit/modules/zypper_test.py
+++ b/tests/unit/modules/zypper_test.py
@@ -50,6 +50,7 @@ from salt.modules import zypper
 
 # Globals
 zypper.__salt__ = dict()
+zypper.__grains__ = dict()
 zypper.__context__ = dict()
 zypper.rpm = None
 
@@ -330,6 +331,76 @@ class ZypperTestCase(TestCase):
             self.assertEqual(zypper.latest_version('vim'), '7.4.326-2.62')
 
     @patch('salt.modules.zypper.refresh_db', MagicMock(return_value=True))
+    @patch.dict('salt.modules.zypper.__grains__', {'osrelease_info': [12, 1]})
+    def test_upgrade_success(self):
+        '''
+        Test system upgrade and dist-upgrade success.
+
+        :return:
+        '''
+        with patch('salt.modules.zypper.__zypper__.noraise.call', MagicMock()) as zypper_mock:
+            with patch('salt.modules.zypper.list_pkgs', MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.2"}])):
+                ret = zypper.upgrade()
+                self.assertTrue(ret['result'])
+                self.assertDictEqual(ret['changes'], {"vim": {"old": "1.1", "new": "1.2"}})
+                zypper_mock.assert_any_call('update', '--auto-agree-with-licenses')
+
+            with patch('salt.modules.zypper.list_pkgs', MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.2"}])):
+                ret = zypper.upgrade(dist_upgrade=True)
+                self.assertTrue(ret['result'])
+                self.assertDictEqual(ret['changes'], {"vim": {"old": "1.1", "new": "1.2"}})
+                zypper_mock.assert_any_call('dist-upgrade', '--auto-agree-with-licenses')
+
+            with patch('salt.modules.zypper.list_pkgs', MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.1"}])):
+                ret = zypper.upgrade(dist_upgrade=True, dryrun=True)
+                self.assertTrue(ret['result'])
+                self.assertDictEqual(ret['changes'], {})
+                zypper_mock.assert_any_call('dist-upgrade', '--auto-agree-with-licenses', '--dry-run')
+                zypper_mock.assert_any_call('dist-upgrade', '--auto-agree-with-licenses', '--dry-run', '--debug-solver')
+
+            with patch('salt.modules.zypper.list_pkgs', MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.2"}])):
+                ret = zypper.upgrade(dist_upgrade=True, fromrepo=["Dummy", "Dummy2"], novendorchange=True)
+                self.assertTrue(ret['result'])
+                self.assertDictEqual(ret['changes'], {"vim": {"old": "1.1", "new": "1.2"}})
+                zypper_mock.assert_any_call('dist-upgrade', '--auto-agree-with-licenses', '--from', "Dummy", '--from', 'Dummy2', '--no-allow-vendor-change')
+
+    @patch('salt.modules.zypper.refresh_db', MagicMock(return_value=True))
+    @patch.dict('salt.modules.zypper.__grains__', {'osrelease_info': [12, 1]})
+    def test_upgrade_failure(self):
+        '''
+        Test system upgrade failure.
+
+        :return:
+        '''
+        zypper_out = '''
+Loading repository data...
+Reading installed packages...
+Computing distribution upgrade...
+Use 'zypper repos' to get the list of defined repositories.
+Repository 'DUMMY' not found by its alias, number, or URI.
+'''
+
+        class FailingZypperDummy(object):
+            def __init__(self):
+                self.stdout = MagicMock(return_value=zypper_out)
+                self.stderr = MagicMock(return_value="")
+                self.exit_code = MagicMock(return_value=555)
+                self.noraise = MagicMock()
+                self.SUCCESS_EXIT_CODES = [0]
+
+            def __call__(self, *args, **kwargs):
+                return self
+
+        with patch('salt.modules.zypper.__zypper__', FailingZypperDummy()) as zypper_mock:
+            zypper_mock.noraise.call = MagicMock()
+            with patch('salt.modules.zypper.list_pkgs', MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.1"}])):
+                ret = zypper.upgrade(dist_upgrade=True, fromrepo=["DUMMY"])
+                self.assertFalse(ret['result'])
+                self.assertEqual(ret['comment'], zypper_out.strip())
+                self.assertDictEqual(ret['changes'], {})
+                zypper_mock.noraise.call.assert_called_with('dist-upgrade', '--auto-agree-with-licenses', '--from', 'DUMMY')
+
+    @patch('salt.modules.zypper.refresh_db', MagicMock(return_value=True))
     def test_upgrade_available(self):
         '''
         Test whether or not an upgrade is available for a given package.
-- 
2.10.2

