From a5027f623a0a2e7f49109af9e2fa3ce409a31a64 Mon Sep 17 00:00:00 2001 From: Michael Calmer Date: Mon, 23 Jan 2023 14:33:26 +0100 Subject: [PATCH] 3005.1 implement zypper removeptf (#573) * handle ptf packages inside of normal pkg.remove function * add testcase for remove and removeptf * add changelog * adapt old tests to changed function * Update Docs Co-authored-by: Megan Wilhite Co-authored-by: Megan Wilhite --- changelog/63442.added | 1 + salt/modules/zypperpkg.py | 38 +++++++- tests/pytests/unit/modules/test_zypperpkg.py | 92 +++++++++++++++++++- tests/unit/modules/test_zypperpkg.py | 1 + 4 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 changelog/63442.added diff --git a/changelog/63442.added b/changelog/63442.added new file mode 100644 index 0000000000..ad81b2f9d5 --- /dev/null +++ b/changelog/63442.added @@ -0,0 +1 @@ +implement removal of ptf packages in zypper pkg module diff --git a/salt/modules/zypperpkg.py b/salt/modules/zypperpkg.py index 5d745c432d..2888bb219f 100644 --- a/salt/modules/zypperpkg.py +++ b/salt/modules/zypperpkg.py @@ -2058,17 +2058,21 @@ def _uninstall(inclusion_detection, name=None, pkgs=None, root=None): except MinionError as exc: raise CommandExecutionError(exc) + ptfpackages = _find_ptf_packages(pkg_params.keys(), root=root) includes = _detect_includes(pkg_params.keys(), inclusion_detection) old = list_pkgs(root=root, includes=includes) targets = [] for target in pkg_params: + if target in ptfpackages: + # ptfpackages needs special handling + continue # 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 pkg_params[target] in old[target].split(","): targets.append(target + "-" + pkg_params[target]) elif target in old and not pkg_params[target]: targets.append(target) - if not targets: + if not targets and not ptfpackages: return {} systemd_scope = _systemd_scope() @@ -2080,6 +2084,13 @@ def _uninstall(inclusion_detection, name=None, pkgs=None, root=None): ) targets = targets[500:] + # handle ptf packages + while ptfpackages: + __zypper__(systemd_scope=systemd_scope, root=root).call( + "removeptf", "--allow-downgrade", *ptfpackages[:500] + ) + ptfpackages = ptfpackages[500:] + _clean_cache() new = list_pkgs(root=root, includes=includes) ret = salt.utils.data.compare_dicts(old, new) @@ -2168,6 +2179,11 @@ def remove( salt '*' pkg.remove salt '*' pkg.remove ,, salt '*' pkg.remove pkgs='["foo", "bar"]' + + .. versionchanged:: 3007 + Can now remove also PTF packages which require a different handling in the backend. + + Can now remove also PTF packages which require a different handling in the backend. """ return _uninstall(inclusion_detection, name=name, pkgs=pkgs, root=root) @@ -2643,6 +2659,26 @@ def _get_visible_patterns(root=None): return patterns +def _find_ptf_packages(pkgs, root=None): + """ + Find ptf packages in "pkgs" and return them as list + """ + ptfs = [] + cmd = ["rpm"] + if root: + cmd.extend(["--root", root]) + cmd.extend(["-q", "--qf", "%{NAME}: [%{PROVIDES} ]\n"]) + cmd.extend(pkgs) + output = __salt__["cmd.run"](cmd) + for line in output.splitlines(): + if not line.strip(): + continue + pkg, provides = line.split(":", 1) + if "ptf()" in provides: + ptfs.append(pkg) + return ptfs + + def _get_installed_patterns(root=None): """ List all installed patterns. diff --git a/tests/pytests/unit/modules/test_zypperpkg.py b/tests/pytests/unit/modules/test_zypperpkg.py index 84dc7a10b4..a0d8e0084e 100644 --- a/tests/pytests/unit/modules/test_zypperpkg.py +++ b/tests/pytests/unit/modules/test_zypperpkg.py @@ -10,7 +10,7 @@ import pytest import salt.modules.pkg_resource as pkg_resource import salt.modules.zypperpkg as zypper from salt.exceptions import CommandExecutionError, SaltInvocationError -from tests.support.mock import MagicMock, mock_open, patch +from tests.support.mock import MagicMock, mock_open, call, patch @pytest.fixture @@ -527,3 +527,93 @@ def test_dist_upgrade_failure(): assert exc.exception.info["changes"] == {} assert exc.exception.info["result"]["stdout"] == zypper_output + + +def test_remove_multiple_pkgs_with_ptf(): + call_spy = MagicMock() + zypper_mock = MagicMock() + zypper_mock.stdout = "" + zypper_mock.stderr = "" + zypper_mock.exit_code = 0 + zypper_mock.call = call_spy + + rpm_output = textwrap.dedent( + """ + vim: vi vim vim(x86-64) vim-base vim-enhanced vim-python vim_client + ptf-12345: ptf() ptf-12345 + """ + ) + rpm_mock = MagicMock(side_effect=[rpm_output]) + + with patch( + "salt.modules.zypperpkg.__zypper__", MagicMock(return_value=zypper_mock) + ), patch.object( + zypper, + "list_pkgs", + MagicMock(side_effect=[{"vim": "0.18.0", "ptf-12345": "1"}, {}]), + ), patch.dict( + zypper.__salt__, {"cmd.run": rpm_mock} + ): + expected_calls = [ + call( + "remove", + "vim", + ), + call( + "removeptf", + "--allow-downgrade", + "ptf-12345", + ), + ] + + result = zypper.remove(name="vim,ptf-12345") + call_spy.assert_has_calls(expected_calls, any_order=False) + assert result["vim"]["new"] == "", result + assert result["vim"]["old"] == "0.18.0", result + assert result["ptf-12345"]["new"] == "", result + assert result["ptf-12345"]["old"] == "1", result + + +def test_remove_ptf(): + call_spy = MagicMock() + zypper_mock = MagicMock() + zypper_mock.stdout = "" + zypper_mock.stderr = "" + zypper_mock.exit_code = 0 + zypper_mock.call = call_spy + + rpm_mock = MagicMock( + side_effect=[ + "vim: vi vim vim(x86-64) vim-base vim-enhanced vim-python vim_client", + "ptf-12345: ptf() ptf-12345", + ] + ) + + with patch( + "salt.modules.zypperpkg.__zypper__", MagicMock(return_value=zypper_mock) + ), patch.object( + zypper, + "list_pkgs", + MagicMock(side_effect=[{"vim": "0.18.0"}, {}, {"ptf-12345": "1"}, {}]), + ), patch.dict( + zypper.__salt__, {"cmd.run": rpm_mock} + ): + expected_call_vim = [ + "remove", + "vim", + ] + expected_call_ptf = [ + "removeptf", + "--allow-downgrade", + "ptf-12345", + ] + + result = zypper.remove(name="vim") + call_spy.assert_called_with(*expected_call_vim) + assert result["vim"]["new"] == "", result + assert result["vim"]["old"] == "0.18.0", result + + result = zypper.remove(name="ptf-12345") + call_spy.assert_called_with(*expected_call_ptf) + assert result["ptf-12345"]["new"] == "", result + assert result["ptf-12345"]["old"] == "1", result diff --git a/tests/unit/modules/test_zypperpkg.py b/tests/unit/modules/test_zypperpkg.py index bcd001cd85..db766363ec 100644 --- a/tests/unit/modules/test_zypperpkg.py +++ b/tests/unit/modules/test_zypperpkg.py @@ -1470,6 +1470,7 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): # If config.get starts being used elsewhere, we'll need to write a # side_effect function. patches = { + "cmd.run": MagicMock(return_value="vim: vi vim\npico: pico"), "cmd.run_all": MagicMock(return_value=cmd_out), "pkg_resource.parse_targets": MagicMock(return_value=parsed_targets), "pkg_resource.stringify": MagicMock(), -- 2.37.3