From 436a98f4768a49f0df4456f33d2941c9ec8a7489 Mon Sep 17 00:00:00 2001
From: Victor Zhestkov <vzhestkov@suse.com>
Date: Mon, 27 Jun 2022 17:54:30 +0300
Subject: [PATCH] Normalize package names once with
 pkg.installed/removed using yum (bsc#1195895)

* Normalize the package name only once on install/remove

* Add tests for single normalize on install/remove

* Fix split_arch conditions

* Fix test_pkg
---
 salt/modules/yumpkg.py        |  15 ++-
 salt/states/pkg.py            |   5 +-
 tests/unit/states/test_pkg.py | 229 +++++++++++++++++++++++++++++++++-
 3 files changed, 240 insertions(+), 9 deletions(-)

diff --git a/salt/modules/yumpkg.py b/salt/modules/yumpkg.py
index 9ea09a4bab..5abd3a04cd 100644
--- a/salt/modules/yumpkg.py
+++ b/salt/modules/yumpkg.py
@@ -1390,7 +1390,9 @@ def install(name=None,
 
     try:
         pkg_params, pkg_type = __salt__['pkg_resource.parse_targets'](
-            name, pkgs, sources, saltenv=saltenv, normalize=normalize, **kwargs
+            name, pkgs, sources, saltenv=saltenv,
+            normalize=normalize and kwargs.get("split_arch", True),
+            **kwargs
         )
     except MinionError as exc:
         raise CommandExecutionError(exc)
@@ -1538,7 +1540,10 @@ def install(name=None,
                 except ValueError:
                     pass
                 else:
-                    if archpart in salt.utils.pkg.rpm.ARCHES:
+                    if archpart in salt.utils.pkg.rpm.ARCHES and (
+                        archpart != __grains__["osarch"]
+                        or kwargs.get("split_arch", True)
+                    ):
                         arch = '.' + archpart
                         pkgname = namepart
 
@@ -2031,11 +2036,13 @@ def remove(name=None, pkgs=None, **kwargs):  # pylint: disable=W0613
             arch = ''
             pkgname = target
             try:
-                namepart, archpart = target.rsplit('.', 1)
+                namepart, archpart = pkgname.rsplit('.', 1)
             except ValueError:
                 pass
             else:
-                if archpart in salt.utils.pkg.rpm.ARCHES:
+                if archpart in salt.utils.pkg.rpm.ARCHES and (
+                    archpart != __grains__["osarch"] or kwargs.get("split_arch", True)
+                ):
                     arch = '.' + archpart
                     pkgname = namepart
             # Since we don't always have the arch info, epoch information has to parsed out. But
diff --git a/salt/states/pkg.py b/salt/states/pkg.py
index 25b51aaf14..cefae11a2f 100644
--- a/salt/states/pkg.py
+++ b/salt/states/pkg.py
@@ -1785,6 +1785,7 @@ def installed(
                                               normalize=normalize,
                                               update_holds=update_holds,
                                               ignore_epoch=ignore_epoch,
+                                              split_arch=False,
                                               **kwargs)
         except CommandExecutionError as exc:
             ret = {'name': name, 'result': False}
@@ -2743,7 +2744,9 @@ def _uninstall(
                 'comment': 'The following packages will be {0}d: '
                            '{1}.'.format(action, ', '.join(targets))}
 
-    changes = __salt__['pkg.{0}'.format(action)](name, pkgs=pkgs, version=version, **kwargs)
+    changes = __salt__['pkg.{0}'.format(action)](
+        name, pkgs=pkgs, version=version, split_arch=False, **kwargs
+    )
     new = __salt__['pkg.list_pkgs'](versions_as_list=True, **kwargs)
     failed = []
     for param in pkg_params:
diff --git a/tests/unit/states/test_pkg.py b/tests/unit/states/test_pkg.py
index 41d74a4706..de2162709a 100644
--- a/tests/unit/states/test_pkg.py
+++ b/tests/unit/states/test_pkg.py
@@ -12,6 +12,8 @@ from tests.support.mock import (
 
 # Import Salt Libs
 from salt.ext import six
+import salt.modules.pkg_resource as pkg_resource
+import salt.modules.yumpkg as yumpkg
 import salt.states.pkg as pkg
 from salt.ext.six.moves import zip
 
@@ -29,10 +31,33 @@ class PkgTestCase(TestCase, LoaderModuleMockMixin):
     def setup_loader_modules(self):
         return {
             pkg: {
-                '__grains__': {
-                    'os': 'CentOS'
-                }
-            }
+                "__env__": "base",
+                "__grains__": {
+                    "os": "CentOS",
+                    "os_family": "RedHat",
+                },
+                "__opts__": {
+                    "test": False,
+                    "cachedir": "",
+                },
+                "__salt__": {},
+            },
+            pkg_resource: {
+                "__salt__": {},
+                "__grains__": {
+                    "os": "CentOS",
+                    "os_family": "RedHat",
+                },
+            },
+            yumpkg: {
+                "__salt__": {},
+                "__grains__": {
+                    "os": "CentOS",
+                    "osarch": "x86_64",
+                    "osmajorrelease": 7,
+                },
+                "__opts__": {},
+            },
         }
 
     def test_uptodate_with_changes(self):
@@ -377,3 +402,199 @@ class PkgTestCase(TestCase, LoaderModuleMockMixin):
             hold_mock.assert_not_called()
             unhold_mock.assert_any_call(name="held-test", pkgs=["baz"])
             unhold_mock.assert_any_call(name="held-test", pkgs=["bar"])
+
+    def test_installed_with_single_normalize(self):
+        """
+        Test pkg.installed with preventing multiple package name normalisation
+        """
+
+        list_no_weird_installed = {
+            "pkga": "1.0.1",
+            "pkgb": "1.0.2",
+            "pkgc": "1.0.3",
+        }
+        list_no_weird_installed_ver_list = {
+            "pkga": ["1.0.1"],
+            "pkgb": ["1.0.2"],
+            "pkgc": ["1.0.3"],
+        }
+        list_with_weird_installed = {
+            "pkga": "1.0.1",
+            "pkgb": "1.0.2",
+            "pkgc": "1.0.3",
+            "weird-name-1.2.3-1234.5.6.test7tst.x86_64": "20220214-2.1",
+        }
+        list_with_weird_installed_ver_list = {
+            "pkga": ["1.0.1"],
+            "pkgb": ["1.0.2"],
+            "pkgc": ["1.0.3"],
+            "weird-name-1.2.3-1234.5.6.test7tst.x86_64": ["20220214-2.1"],
+        }
+        list_pkgs = MagicMock(
+            side_effect=[
+                # For the package with version specified
+                list_no_weird_installed_ver_list,
+                {},
+                list_no_weird_installed,
+                list_no_weird_installed_ver_list,
+                list_with_weird_installed,
+                list_with_weird_installed_ver_list,
+                # For the package with no version specified
+                list_no_weird_installed_ver_list,
+                {},
+                list_no_weird_installed,
+                list_no_weird_installed_ver_list,
+                list_with_weird_installed,
+                list_with_weird_installed_ver_list,
+            ]
+        )
+
+        salt_dict = {
+            "pkg.install": yumpkg.install,
+            "pkg.list_pkgs": list_pkgs,
+            "pkg.normalize_name": yumpkg.normalize_name,
+            "pkg_resource.version_clean": pkg_resource.version_clean,
+            "pkg_resource.parse_targets": pkg_resource.parse_targets,
+        }
+
+        with patch("salt.modules.yumpkg.list_pkgs", list_pkgs), patch(
+            "salt.modules.yumpkg.version_cmp", MagicMock(return_value=0)
+        ), patch(
+            "salt.modules.yumpkg._call_yum", MagicMock(return_value={"retcode": 0})
+        ) as call_yum_mock, patch.dict(
+            pkg.__salt__, salt_dict
+        ), patch.dict(
+            pkg_resource.__salt__, salt_dict
+        ), patch.dict(
+            yumpkg.__salt__, salt_dict
+        ), patch.dict(
+            yumpkg.__grains__, {"os": "CentOS", "osarch": "x86_64", "osmajorrelease": 7}
+        ), patch.object(
+            yumpkg, "list_holds", MagicMock()
+        ):
+
+            expected = {
+                "weird-name-1.2.3-1234.5.6.test7tst.x86_64": {
+                    "old": "",
+                    "new": "20220214-2.1",
+                }
+            }
+            ret = pkg.installed(
+                "test_install",
+                pkgs=[{"weird-name-1.2.3-1234.5.6.test7tst.x86_64.noarch": "20220214-2.1"}],
+            )
+            call_yum_mock.assert_called_once()
+            self.assertTrue(
+                "weird-name-1.2.3-1234.5.6.test7tst.x86_64-20220214-2.1"
+                in call_yum_mock.mock_calls[0].args[0]
+            )
+            self.assertTrue(ret["result"])
+            self.assertDictEqual(ret["changes"], expected)
+
+            call_yum_mock.reset_mock()
+
+            ret = pkg.installed(
+                "test_install",
+                pkgs=["weird-name-1.2.3-1234.5.6.test7tst.x86_64.noarch"],
+            )
+            call_yum_mock.assert_called_once()
+            self.assertTrue(
+                "weird-name-1.2.3-1234.5.6.test7tst.x86_64"
+                in call_yum_mock.mock_calls[0].args[0]
+            )
+            self.assertTrue(ret["result"])
+            self.assertDictEqual(ret["changes"], expected)
+
+    def test_removed_with_single_normalize(self):
+        """
+        Test pkg.removed with preventing multiple package name normalisation
+        """
+
+        list_no_weird_installed = {
+            "pkga": "1.0.1",
+            "pkgb": "1.0.2",
+            "pkgc": "1.0.3",
+        }
+        list_no_weird_installed_ver_list = {
+            "pkga": ["1.0.1"],
+            "pkgb": ["1.0.2"],
+            "pkgc": ["1.0.3"],
+        }
+        list_with_weird_installed = {
+            "pkga": "1.0.1",
+            "pkgb": "1.0.2",
+            "pkgc": "1.0.3",
+            "weird-name-1.2.3-1234.5.6.test7tst.x86_64": "20220214-2.1",
+        }
+        list_with_weird_installed_ver_list = {
+            "pkga": ["1.0.1"],
+            "pkgb": ["1.0.2"],
+            "pkgc": ["1.0.3"],
+            "weird-name-1.2.3-1234.5.6.test7tst.x86_64": ["20220214-2.1"],
+        }
+        list_pkgs = MagicMock(
+            side_effect=[
+                # For the package with version specified
+                list_with_weird_installed_ver_list,
+                list_with_weird_installed,
+                list_no_weird_installed,
+                list_no_weird_installed_ver_list,
+                # For the package with no version specified
+                list_with_weird_installed_ver_list,
+                list_with_weird_installed,
+                list_no_weird_installed,
+                list_no_weird_installed_ver_list,
+            ]
+        )
+
+        salt_dict = {
+            "pkg.remove": yumpkg.remove,
+            "pkg.list_pkgs": list_pkgs,
+            "pkg.normalize_name": yumpkg.normalize_name,
+            "pkg_resource.parse_targets": pkg_resource.parse_targets,
+            "pkg_resource.version_clean": pkg_resource.version_clean,
+        }
+
+        with patch("salt.modules.yumpkg.list_pkgs", list_pkgs), patch(
+            "salt.modules.yumpkg.version_cmp", MagicMock(return_value=0)
+        ), patch(
+            "salt.modules.yumpkg._call_yum", MagicMock(return_value={"retcode": 0})
+        ) as call_yum_mock, patch.dict(
+            pkg.__salt__, salt_dict
+        ), patch.dict(
+            pkg_resource.__salt__, salt_dict
+        ), patch.dict(
+            yumpkg.__salt__, salt_dict
+        ):
+
+            expected = {
+                "weird-name-1.2.3-1234.5.6.test7tst.x86_64": {
+                    "old": "20220214-2.1",
+                    "new": "",
+                }
+            }
+            ret = pkg.removed(
+                "test_remove",
+                pkgs=[{"weird-name-1.2.3-1234.5.6.test7tst.x86_64.noarch": "20220214-2.1"}],
+            )
+            call_yum_mock.assert_called_once()
+            self.assertTrue(
+                "weird-name-1.2.3-1234.5.6.test7tst.x86_64-20220214-2.1"
+                in call_yum_mock.mock_calls[0].args[0]
+            )
+            self.assertTrue(ret["result"])
+            self.assertDictEqual(ret["changes"], expected)
+
+            call_yum_mock.reset_mock()
+
+            ret = pkg.removed(
+                "test_remove",
+                pkgs=["weird-name-1.2.3-1234.5.6.test7tst.x86_64.noarch"],
+            )
+            call_yum_mock.assert_called_once()
+            self.assertTrue(
+                "weird-name-1.2.3-1234.5.6.test7tst.x86_64"
+                in call_yum_mock.mock_calls[0].args[0]
+            )
+            self.assertTrue(ret["result"])
+            self.assertDictEqual(ret["changes"], expected)
-- 
2.36.1


