From fe8996b982eb829c4774f68eb7fac0448b13e590 Mon Sep 17 00:00:00 2001
From: Jochen Breuer <jbreuer@suse.de>
Date: Tue, 7 Jul 2020 16:54:06 +0200
Subject: [PATCH] Parsing Epoch out of version provided during pkg
 remove (bsc#1173692)

yum doesn't seem to like the epoch information provided within the
version. Therefore it's removed before passing it to yum.

* Introducing `ignore_epoch` to pkg.remove
  Just like pkg.install pkg.remove now also has ignore_epoch. With
  this it is possible to ignore the epoch information completely
  during version comparison.
* No epoch regardless of arch
* Added tests for cases with and without arch.
* Epoch information is now skipped in all cases.
* Removes ignore_epoch from pkg state
---
 changelog/57881.changed           |  1 +
 salt/modules/yumpkg.py            | 14 +++--
 tests/unit/modules/test_yumpkg.py | 85 +++++++++++++++++++++++++++++++
 3 files changed, 96 insertions(+), 4 deletions(-)
 create mode 100644 changelog/57881.changed

diff --git a/changelog/57881.changed b/changelog/57881.changed
new file mode 100644
index 0000000000..e2ae2f4653
--- /dev/null
+++ b/changelog/57881.changed
@@ -0,0 +1 @@
+Parsing Epoch out of version during pkg remove, since yum can't handle that in all of the cases.
diff --git a/salt/modules/yumpkg.py b/salt/modules/yumpkg.py
index 722bbc0350..63969b82d4 100644
--- a/salt/modules/yumpkg.py
+++ b/salt/modules/yumpkg.py
@@ -2019,11 +2019,13 @@ def remove(name=None, pkgs=None, **kwargs):  # pylint: disable=W0613
     old = list_pkgs()
     targets = []
     for target in pkg_params:
+        version_to_remove = pkg_params[target]
+        installed_versions = old[target].split(",")
+
         # Check if package version set to be removed is actually installed:
-        # old[target] contains a comma-separated list of installed versions
-        if target in old and not pkg_params[target]:
+        if target in old and not version_to_remove:
             targets.append(target)
-        elif target in old and pkg_params[target] in old[target].split(','):
+        elif target in old and version_to_remove in installed_versions:
             arch = ''
             pkgname = target
             try:
@@ -2034,7 +2036,11 @@ def remove(name=None, pkgs=None, **kwargs):  # pylint: disable=W0613
                 if archpart in salt.utils.pkg.rpm.ARCHES:
                     arch = '.' + archpart
                     pkgname = namepart
-            targets.append('{0}-{1}{2}'.format(pkgname, pkg_params[target], arch))
+            # Since we don't always have the arch info, epoch information has to parsed out. But
+            # a version check was already performed, so we are removing the right version.
+            targets.append(
+                '{0}-{1}{2}'.format(pkgname, version_to_remove.split(':', 1)[-1], arch)
+            )
     if not targets:
         return {}
 
diff --git a/tests/unit/modules/test_yumpkg.py b/tests/unit/modules/test_yumpkg.py
index f4192f5fbf..237835f444 100644
--- a/tests/unit/modules/test_yumpkg.py
+++ b/tests/unit/modules/test_yumpkg.py
@@ -661,6 +661,91 @@ class YumTestCase(TestCase, LoaderModuleMockMixin):
                     redirect_stderr=True,
                 )
 
+    def test_remove_with_epoch(self):
+        """
+        Tests that we properly identify a version containing an epoch for
+        deinstallation.
+
+        You can deinstall pkgs only without the epoch if no arch is provided:
+
+        .. code-block:: bash
+
+            yum remove PackageKit-yum-1.1.10-2.el7.centos
+        """
+        name = "foo"
+        installed = "8:3.8.12-4.n.el7"
+        list_pkgs_mock = MagicMock(
+            side_effect=lambda **kwargs: {
+                name: [installed]
+                if kwargs.get("versions_as_list", False)
+                else installed
+            }
+        )
+        cmd_mock = MagicMock(
+            return_value={"pid": 12345, "retcode": 0, "stdout": "", "stderr": ""}
+        )
+        salt_mock = {
+            "cmd.run_all": cmd_mock,
+            "lowpkg.version_cmp": rpm.version_cmp,
+            "pkg_resource.parse_targets": MagicMock(
+                return_value=({name: installed}, "repository")
+            ),
+        }
+        full_pkg_string = "-".join((name, installed[2:]))
+        with patch.object(yumpkg, "list_pkgs", list_pkgs_mock), patch(
+            "salt.utils.systemd.has_scope", MagicMock(return_value=False)
+        ), patch.dict(yumpkg.__salt__, salt_mock):
+
+            with patch.dict(yumpkg.__grains__, {"os": "CentOS", "osrelease": 7}):
+                expected = ["yum", "-y", "remove", full_pkg_string]
+                yumpkg.remove(name)
+                call = cmd_mock.mock_calls[0][1][0]
+                assert call == expected, call
+
+    def test_remove_with_epoch_and_arch_info(self):
+        """
+        Tests that we properly identify a version containing an epoch and arch
+        deinstallation.
+
+        You can deinstall pkgs with or without epoch in combination with the arch.
+        Here we test for the absence of the epoch, but the presence for the arch:
+
+        .. code-block:: bash
+
+            yum remove PackageKit-yum-1.1.10-2.el7.centos.x86_64
+        """
+        arch = "x86_64"
+        name = "foo"
+        name_and_arch = name + "." + arch
+        installed = "8:3.8.12-4.n.el7"
+        list_pkgs_mock = MagicMock(
+            side_effect=lambda **kwargs: {
+                name_and_arch: [installed]
+                if kwargs.get("versions_as_list", False)
+                else installed
+            }
+        )
+        cmd_mock = MagicMock(
+            return_value={"pid": 12345, "retcode": 0, "stdout": "", "stderr": ""}
+        )
+        salt_mock = {
+            "cmd.run_all": cmd_mock,
+            "lowpkg.version_cmp": rpm.version_cmp,
+            "pkg_resource.parse_targets": MagicMock(
+                return_value=({name_and_arch: installed}, "repository")
+            ),
+        }
+        full_pkg_string = "-".join((name, installed[2:]))
+        with patch.object(yumpkg, "list_pkgs", list_pkgs_mock), patch(
+            "salt.utils.systemd.has_scope", MagicMock(return_value=False)
+        ), patch.dict(yumpkg.__salt__, salt_mock):
+
+            with patch.dict(yumpkg.__grains__, {"os": "CentOS", "osrelease": 7}):
+                expected = ["yum", "-y", "remove", full_pkg_string + "." + arch]
+                yumpkg.remove(name)
+                call = cmd_mock.mock_calls[0][1][0]
+                assert call == expected, call
+
     def test_install_with_epoch(self):
         '''
         Tests that we properly identify a version containing an epoch as an
-- 
2.31.1


