We have some news to share for the request index beta feature. We’ve added more options to sort your requests, counters to the individual filters and documentation for the search functionality. Checkout the blog post for more details.

File 3005.1-implement-zypper-removeptf-573.patch of Package venv-salt-minion

From 327a5e5b24c4fa047df44b245abd672e02999cca Mon Sep 17 00:00:00 2001
From: Michael Calmer <Michael.Calmer@suse.de>
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 <mwilhite@vmware.com>
---
 changelog/63442.added                        |   1 +
 salt/modules/zypperpkg.py                    |  38 +-
 tests/pytests/unit/modules/test_zypperpkg.py | 356 ++++++++++++++++++-
 tests/unit/modules/test_zypperpkg.py         |   1 +
 4 files changed, 394 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 051f8f72c7..44f2cdbd3a 100644
--- a/salt/modules/zypperpkg.py
+++ b/salt/modules/zypperpkg.py
@@ -2073,17 +2073,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()
@@ -2095,6 +2099,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)
@@ -2183,6 +2194,11 @@ def remove(
         salt '*' pkg.remove <package name>
         salt '*' pkg.remove <package1>,<package2>,<package3>
         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)
 
@@ -2658,6 +2674,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 91132b7277..c996662e1c 100644
--- a/tests/pytests/unit/modules/test_zypperpkg.py
+++ b/tests/pytests/unit/modules/test_zypperpkg.py
@@ -11,7 +11,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
@@ -27,6 +27,11 @@ def configure_loader_modules():
     }
 
 
+@pytest.fixture(autouse=True)
+def fresh_zypper_instance():
+    zypper.__zypper__ = zypper._Zypper()
+
+
 def test_list_pkgs_no_context():
     """
     Test packages listing.
@@ -395,3 +400,352 @@ def test_del_repo_key():
     with patch.dict(zypper.__salt__, salt_mock):
         assert zypper.del_repo_key(keyid="keyid", root="/mnt")
         salt_mock["lowpkg.remove_gpg_key"].assert_called_once_with("keyid", "/mnt")
+
+@pytest.mark.parametrize(
+    "zypper_version,lowpkg_version_cmp,expected_inst_avc,expected_dup_avc",
+    [
+        ("0.5", [-1, -1], False, False),
+        ("1.11.34", [0, -1], False, True),
+        ("1.14.8", [0, 0], True, True),
+    ],
+)
+def test_refresh_zypper_flags(
+    zypper_version, lowpkg_version_cmp, expected_inst_avc, expected_dup_avc
+):
+    with patch(
+        "salt.modules.zypperpkg.version", MagicMock(return_value=zypper_version)
+    ), patch.dict(
+        zypper.__salt__,
+        {"lowpkg.version_cmp": MagicMock(side_effect=lowpkg_version_cmp)},
+    ):
+        _zypper = zypper._Zypper()
+        _zypper.refresh_zypper_flags()
+        assert _zypper.inst_avc == expected_inst_avc
+        assert _zypper.dup_avc == expected_dup_avc
+
+
+@pytest.mark.parametrize(
+    "inst_avc,dup_avc,avc,allowvendorchange_param,novendorchange_param,expected",
+    [
+        # inst_avc = True, dup_avc = True
+        (True, True, False, False, False, True),
+        (True, True, False, True, False, True),
+        (True, True, False, False, True, False),
+        (True, True, False, True, True, True),
+        # inst_avc = False, dup_avc = True
+        (False, True, False, False, False, True),
+        (False, True, False, True, False, True),
+        (False, True, False, False, True, False),
+        (False, True, False, True, True, True),
+        # inst_avc = False, dup_avc = False
+        (False, False, False, False, False, False),
+        (False, False, False, True, False, False),
+        (False, False, False, False, True, False),
+        (False, False, False, True, True, False),
+    ],
+)
+@patch("salt.modules.zypperpkg._Zypper.refresh_zypper_flags", MagicMock())
+def test_allow_vendor_change(
+    inst_avc,
+    dup_avc,
+    avc,
+    allowvendorchange_param,
+    novendorchange_param,
+    expected,
+):
+    _zypper = zypper._Zypper()
+    _zypper.inst_avc = inst_avc
+    _zypper.dup_avc = dup_avc
+    _zypper.avc = avc
+    _zypper.allow_vendor_change(allowvendorchange_param, novendorchange_param)
+    assert _zypper.avc == expected
+
+
+@pytest.mark.parametrize(
+    "package,pre_version,post_version,fromrepo_param,name_param,pkgs_param,diff_attr_param",
+    [
+        ("vim", "1.1", "1.2", [], "", [], "all"),
+        ("kernel-default", "1.1", "1.1,1.2", ["dummy", "dummy2"], "", [], None),
+        ("vim", "1.1", "1.2", [], "vim", [], None),
+    ],
+)
+@patch.object(zypper, "refresh_db", MagicMock(return_value=True))
+def test_upgrade(
+    package,
+    pre_version,
+    post_version,
+    fromrepo_param,
+    name_param,
+    pkgs_param,
+    diff_attr_param,
+):
+    with patch(
+        "salt.modules.zypperpkg.__zypper__.noraise.call"
+    ) as zypper_mock, patch.object(
+        zypper,
+        "list_pkgs",
+        MagicMock(side_effect=[{package: pre_version}, {package: post_version}]),
+    ) as list_pkgs_mock:
+        expected_call = ["update", "--auto-agree-with-licenses"]
+        for repo in fromrepo_param:
+            expected_call.extend(["--repo", repo])
+
+        if pkgs_param:
+            expected_call.extend(pkgs_param)
+        elif name_param:
+            expected_call.append(name_param)
+
+        result = zypper.upgrade(
+            name=name_param,
+            pkgs=pkgs_param,
+            fromrepo=fromrepo_param,
+            diff_attr=diff_attr_param,
+        )
+        zypper_mock.assert_any_call(*expected_call)
+        assert result == {package: {"old": pre_version, "new": post_version}}
+        list_pkgs_mock.assert_any_call(root=None, attr=diff_attr_param)
+
+
+@pytest.mark.parametrize(
+    "package,pre_version,post_version,fromrepo_param",
+    [
+        ("vim", "1.1", "1.2", []),
+        ("emacs", "1.1", "1.2", ["Dummy", "Dummy2"]),
+    ],
+)
+@patch.object(zypper, "refresh_db", MagicMock(return_value=True))
+def test_dist_upgrade(package, pre_version, post_version, fromrepo_param):
+    with patch(
+        "salt.modules.zypperpkg.__zypper__.noraise.call"
+    ) as zypper_mock, patch.object(
+        zypper,
+        "list_pkgs",
+        MagicMock(side_effect=[{package: pre_version}, {package: post_version}]),
+    ):
+        expected_call = ["dist-upgrade", "--auto-agree-with-licenses"]
+
+        for repo in fromrepo_param:
+            expected_call.extend(["--from", repo])
+
+        result = zypper.upgrade(dist_upgrade=True, fromrepo=fromrepo_param)
+        zypper_mock.assert_any_call(*expected_call)
+        assert result == {package: {"old": pre_version, "new": post_version}}
+
+
+@pytest.mark.parametrize(
+    "package,pre_version,post_version,dup_avc,novendorchange_param,allowvendorchange_param,vendor_change",
+    [
+        # dup_avc = True, both params = default -> no vendor change
+        ("vim", "1.1", "1.2", True, True, False, False),
+        # dup_avc = True, allowvendorchange = True -> vendor change
+        (
+            "emacs",
+            "1.1",
+            "1.2",
+            True,
+            True,
+            True,
+            True,
+        ),
+        # dup_avc = True, novendorchange = False -> vendor change
+        ("joe", "1.1", "1.2", True, False, False, True),
+        # dup_avc = True, both params = toggled -> vendor change
+        ("kate", "1.1", "1.2", True, False, True, True),
+         # dup_avc = False -> no vendor change
+        (
+            "gedit",
+            "1.1",
+            "1.2",
+            False,
+            False,
+            True,
+            False
+        ),
+    ],
+)
+@patch.object(zypper, "refresh_db", MagicMock(return_value=True))
+def test_dist_upgrade_vendorchange(
+    package,
+    pre_version,
+    post_version,
+    dup_avc,
+    novendorchange_param,
+    allowvendorchange_param,
+    vendor_change
+):
+    cmd_run_mock = MagicMock(return_value={"retcode": 0, "stdout": None})
+    with patch.object(
+        zypper,
+        "list_pkgs",
+        MagicMock(side_effect=[{package: pre_version}, {package: post_version}]),
+    ), patch("salt.modules.zypperpkg.__zypper__.refresh_zypper_flags",), patch.dict(
+        zypper.__salt__, {"cmd.run_all": cmd_run_mock}
+    ):
+        expected_cmd = ["zypper", "--non-interactive", "--no-refresh", "dist-upgrade"]
+        # --allow-vendor-change is injected right after "dist-upgrade"
+        if vendor_change:
+            expected_cmd.append("--allow-vendor-change")
+        expected_cmd.append("--auto-agree-with-licenses")
+
+        zypper.__zypper__.dup_avc = dup_avc
+        zypper.upgrade(
+            dist_upgrade=True,
+            allowvendorchange=allowvendorchange_param,
+            novendorchange=novendorchange_param,
+        )
+        cmd_run_mock.assert_any_call(
+            expected_cmd, output_loglevel="trace", python_shell=False, env={}
+        )
+
+
+@pytest.mark.parametrize(
+    "package,pre_version,post_version,fromrepo_param",
+    [
+        ("vim", "1.1", "1.1", []),
+        ("emacs", "1.1", "1.1", ["Dummy", "Dummy2"]),
+    ],
+)
+@patch.object(zypper, "refresh_db", MagicMock(return_value=True))
+def test_dist_upgrade_dry_run(package, pre_version, post_version, fromrepo_param):
+    with patch(
+        "salt.modules.zypperpkg.__zypper__.noraise.call"
+    ) as zypper_mock, patch.object(
+        zypper,
+        "list_pkgs",
+        MagicMock(side_effect=[{package: pre_version}, {package: post_version}]),
+    ):
+        expected_call = ["dist-upgrade", "--auto-agree-with-licenses", "--dry-run"]
+
+        for repo in fromrepo_param:
+            expected_call.extend(["--from", repo])
+
+        zypper.upgrade(dist_upgrade=True, dryrun=True, fromrepo=fromrepo_param)
+        zypper_mock.assert_any_call(*expected_call)
+        # dryrun=True causes two calls, one with a trailing --debug-solver flag
+        expected_call.append("--debug-solver")
+        zypper_mock.assert_any_call(*expected_call)
+
+
+@patch.object(zypper, "refresh_db", MagicMock(return_value=True))
+def test_dist_upgrade_failure():
+    zypper_output = textwrap.dedent(
+        """\
+        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.
+        """
+    )
+    call_spy = MagicMock()
+    zypper_mock = MagicMock()
+    zypper_mock.stdout = zypper_output
+    zypper_mock.stderr = ""
+    zypper_mock.exit_code = 3
+    zypper_mock.noraise.call = call_spy
+    with patch("salt.modules.zypperpkg.__zypper__", zypper_mock), patch.object(
+        zypper, "list_pkgs", MagicMock(side_effect=[{"vim": 1.1}, {"vim": 1.1}])
+    ):
+        expected_call = [
+            "dist-upgrade",
+            "--auto-agree-with-licenses",
+            "--from",
+            "Dummy",
+        ]
+
+        with pytest.raises(CommandExecutionError) as exc:
+            zypper.upgrade(dist_upgrade=True, fromrepo=["Dummy"])
+            call_spy.assert_called_with(*expected_call)
+
+            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 f5b6d74b6f..6e5ca88895 100644
--- a/tests/unit/modules/test_zypperpkg.py
+++ b/tests/unit/modules/test_zypperpkg.py
@@ -1953,6 +1953,7 @@ Repository 'DUMMY' not found by its alias, number, or URI.
         # 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.39.2


openSUSE Build Service is sponsored by