From acd8fbfd7b2c1fdf84b0250e245418d8c6e387ec Mon Sep 17 00:00:00 2001 From: Alberto Planas Date: Tue, 20 Oct 2020 11:43:09 +0200 Subject: [PATCH] Add "migrated" state and GPG key management functions (#290) * rpm_lowpkg: add API for GPG keys * zypperpkg: do not quote the repo name * pkgrepo: add migrated function * pkg: unify apt and rpm API for key repo aptpkg is the virtual package "pkg" for Debian, and contains some API for key management. This patch add a similar API for zypperpkg and yumpkg, also part of the same virtual package, based on the counterpart from rpm_lowpkg API. Convert test to pytests --- changelog/58782.added | 1 + salt/modules/aptpkg.py | 7 +- salt/modules/rpm_lowpkg.py | 151 ++ salt/modules/yumpkg.py | 89 +- salt/modules/zypperpkg.py | 88 + salt/states/pkgrepo.py | 207 +++ tests/pytests/unit/modules/test_yumpkg.py | 87 +- tests/pytests/unit/modules/test_zypperpkg.py | 66 +- tests/pytests/unit/states/test_pkgrepo.py | 448 +++++ tests/unit/modules/test_rpm_lowpkg.py | 232 ++- tests/unit/modules/test_yumpkg.py | 1754 ------------------ tests/unit/modules/test_zypperpkg.py | 44 +- 12 files changed, 1399 insertions(+), 1775 deletions(-) create mode 100644 changelog/58782.added delete mode 100644 tests/unit/modules/test_yumpkg.py diff --git a/changelog/58782.added b/changelog/58782.added new file mode 100644 index 0000000000..f9e69f64f2 --- /dev/null +++ b/changelog/58782.added @@ -0,0 +1 @@ +Add GPG key functions in "lowpkg" and a "migrated" function in the "pkgrepo" state for repository and GPG key migration. \ No newline at end of file diff --git a/salt/modules/aptpkg.py b/salt/modules/aptpkg.py index 692d99f97e..1d9557b497 100644 --- a/salt/modules/aptpkg.py +++ b/salt/modules/aptpkg.py @@ -1918,7 +1918,7 @@ def _convert_if_int(value): return value -def get_repo_keys(): +def get_repo_keys(**kwargs): """ .. versionadded:: 2017.7.0 @@ -2000,7 +2000,9 @@ def get_repo_keys(): return ret -def add_repo_key(path=None, text=None, keyserver=None, keyid=None, saltenv="base"): +def add_repo_key( + path=None, text=None, keyserver=None, keyid=None, saltenv="base", **kwargs +): """ .. versionadded:: 2017.7.0 @@ -2026,7 +2028,6 @@ def add_repo_key(path=None, text=None, keyserver=None, keyid=None, saltenv="base salt '*' pkg.add_repo_key keyserver='keyserver.example' keyid='0000AAAA' """ cmd = ["apt-key"] - kwargs = {} current_repo_keys = get_repo_keys() diff --git a/salt/modules/rpm_lowpkg.py b/salt/modules/rpm_lowpkg.py index f610ca412c..370bd5b728 100644 --- a/salt/modules/rpm_lowpkg.py +++ b/salt/modules/rpm_lowpkg.py @@ -828,3 +828,154 @@ def checksum(*paths, **kwargs): ) return ret + + +def list_gpg_keys(info=False, root=None): + """Return the list of all the GPG keys stored in the RPM database + + .. versionadded:: TBD + + info + get the key information, returing a dictionary instead of a + list + + root + use root as top level directory (default: "/") + + CLI Example: + + .. code-block:: bash + + salt '*' lowpkg.list_gpg_keys + salt '*' lowpkg.list_gpg_keys info=True + + """ + cmd = ["rpm"] + if root: + cmd.extend(["--root", root]) + cmd.extend(["-qa", "gpg-pubkey*"]) + keys = __salt__["cmd.run_stdout"](cmd, python_shell=False).splitlines() + if info: + return {key: info_gpg_key(key, root=root) for key in keys} + else: + return keys + + +def info_gpg_key(key, root=None): + """Return a dictionary with the information of a GPG key parsed + + .. versionadded:: TBD + + key + key identificatior + + root + use root as top level directory (default: "/") + + CLI Example: + + .. code-block:: bash + + salt '*' lowpkg.info_gpg_key gpg-pubkey-3dbdc284-53674dd4 + + """ + cmd = ["rpm"] + if root: + cmd.extend(["--root", root]) + cmd.extend(["-qi", key]) + info = __salt__["cmd.run_stdout"](cmd, python_shell=False) + + res = {} + # The parser algorithm is very ad-hoc. Works under the + # expectation that all the fields are of the type "key: value" in + # a single line, except "Description", that will be composed of + # multiple lines. Note that even if the official `rpm` makes this + # field the last one, other (like openSUSE) exted it with more + # fields. + in_description = False + description = [] + for line in info.splitlines(): + if line.startswith("Description"): + in_description = True + elif in_description: + description.append(line) + if line.startswith("-----END"): + res["Description"] = "\n".join(description) + in_description = False + elif line: + key, _, value = line.partition(":") + value = value.strip() + if "Date" in key: + try: + value = datetime.datetime.strptime( + value, "%a %d %b %Y %H:%M:%S %p %Z" + ) + except ValueError: + pass + elif "Size" in key: + try: + value = int(value) + except TypeError: + pass + elif "(none)" in value: + value = None + res[key.strip()] = value + return res + + +def import_gpg_key(key, root=None): + """Import a new key into the key storage + + .. versionadded:: TBD + + key + public key block content + + root + use root as top level directory (default: "/") + + CLI Example: + + .. code-block:: bash + + salt '*' lowpkg.import_gpg_key "-----BEGIN ..." + + """ + key_file = salt.utils.files.mkstemp() + with salt.utils.files.fopen(key_file, "w") as f: + f.write(key) + + cmd = ["rpm"] + if root: + cmd.extend(["--root", root]) + cmd.extend(["--import", key_file]) + ret = __salt__["cmd.retcode"](cmd) + + os.remove(key_file) + + return ret == 0 + + +def remove_gpg_key(key, root=None): + """Remove a key from the key storage + + .. versionadded:: TBD + + key + key identificatior + + root + use root as top level directory (default: "/") + + CLI Example: + + .. code-block:: bash + + salt '*' lowpkg.remove_gpg_key gpg-pubkey-3dbdc284-53674dd4 + + """ + cmd = ["rpm"] + if root: + cmd.extend(["--root", root]) + cmd.extend(["-e", key]) + return __salt__["cmd.retcode"](cmd) == 0 diff --git a/salt/modules/yumpkg.py b/salt/modules/yumpkg.py index 77cd14aaf2..fd79109e40 100644 --- a/salt/modules/yumpkg.py +++ b/salt/modules/yumpkg.py @@ -3391,7 +3391,6 @@ def services_need_restart(**kwargs): Requires systemd. - CLI Examples: .. code-block:: bash @@ -3417,3 +3416,91 @@ def services_need_restart(**kwargs): services.add(service) return list(services) + + +def get_repo_keys(info=False, root=None, **kwargs): + """Return the list of all the GPG keys stored in the RPM database + + .. versionadded:: TBD + + info + get the key information, returing a dictionary instead of a + list + + root + use root as top level directory (default: "/") + + CLI Example: + + .. code-block:: bash + + salt '*' pkg.get_repo_keys + salt '*' pkg.get_repo_keys info=True + + """ + return __salt__["lowpkg.list_gpg_keys"](info, root) + + +def add_repo_key(path=None, text=None, root=None, saltenv="base", **kwargs): + """Import a new key into the key storage + + .. versionadded:: TBD + + path + the path of the key file to import + + text + the key data to import, in string form + + root + use root as top level directory (default: "/") + + saltenv + the environment the key file resides in + + CLI Examples: + + .. code-block:: bash + + salt '*' pkg.add_repo_key 'salt://apt/sources/test.key' + salt '*' pkg.add_repo_key text="'$KEY1'" + + """ + if not path and not text: + raise SaltInvocationError("Provide a key to add") + + if path and text: + raise SaltInvocationError("Add a key via path or key") + + if path: + cache_path = __salt__["cp.cache_file"](path, saltenv) + + if not cache_path: + log.error("Unable to get cached copy of file: %s", path) + return False + + with salt.utils.files.fopen(cache_path, "r") as f: + text = f.read() + + return __salt__["lowpkg.import_gpg_key"](text, root) + + +def del_repo_key(keyid, root=None, **kwargs): + """Remove a key from the key storage + + .. versionadded:: TBD + + keyid + key identificatior + + root + use root as top level directory (default: "/") + + CLI Examples: + + .. code-block:: bash + + salt '*' pkg.del_repo_key keyid=gpg-pubkey-3dbdc284-53674dd4 + + """ + return __salt__["lowpkg.remove_gpg_key"](keyid, root) diff --git a/salt/modules/zypperpkg.py b/salt/modules/zypperpkg.py index 7216e25b86..863be3c894 100644 --- a/salt/modules/zypperpkg.py +++ b/salt/modules/zypperpkg.py @@ -3125,3 +3125,91 @@ def services_need_restart(root=None, **kwargs): services = zypper_output.split() return services + + +def get_repo_keys(info=False, root=None, **kwargs): + """Return the list of all the GPG keys stored in the RPM database + + .. versionadded:: TBD + + info + get the key information, returing a dictionary instead of a + list + + root + use root as top level directory (default: "/") + + CLI Example: + + .. code-block:: bash + + salt '*' pkg.get_repo_keys + salt '*' pkg.get_repo_keys info=True + + """ + return __salt__["lowpkg.list_gpg_keys"](info, root) + + +def add_repo_key(path=None, text=None, root=None, saltenv="base", **kwargs): + """Import a new key into the key storage + + .. versionadded:: TBD + + path + the path of the key file to import + + text + the key data to import, in string form + + root + use root as top level directory (default: "/") + + saltenv + the environment the key file resides in + + CLI Examples: + + .. code-block:: bash + + salt '*' pkg.add_repo_key 'salt://apt/sources/test.key' + salt '*' pkg.add_repo_key text="'$KEY1'" + + """ + if not path and not text: + raise SaltInvocationError("Provide a key to add") + + if path and text: + raise SaltInvocationError("Add a key via path or key") + + if path: + cache_path = __salt__["cp.cache_file"](path, saltenv) + + if not cache_path: + log.error("Unable to get cached copy of file: %s", path) + return False + + with salt.utils.files.fopen(cache_path, "r") as f: + text = f.read() + + return __salt__["lowpkg.import_gpg_key"](text, root) + + +def del_repo_key(keyid, root=None, **kwargs): + """Remove a key from the key storage + + .. versionadded:: TBD + + keyid + key identificatior + + root + use root as top level directory (default: "/") + + CLI Examples: + + .. code-block:: bash + + salt '*' pkg.del_repo_key keyid=gpg-pubkey-3dbdc284-53674dd4 + + """ + return __salt__["lowpkg.remove_gpg_key"](keyid, root) diff --git a/salt/states/pkgrepo.py b/salt/states/pkgrepo.py index c8c75e3244..e3d7f7084e 100644 --- a/salt/states/pkgrepo.py +++ b/salt/states/pkgrepo.py @@ -85,6 +85,7 @@ package managers are APT, DNF, YUM and Zypper. Here is some example SLS: """ +import os import sys import salt.utils.data @@ -672,3 +673,209 @@ def absent(name, **kwargs): ret["comment"] = "Failed to remove repo {}".format(name) return ret + + +def _normalize_repo(repo): + """Normalize the get_repo information""" + # `pkg.get_repo()` specific virtual module implementation is + # parsing the information directly from the repository + # configuration file, and can be different from the ones that + # `pkg.mod_repo()` accepts + + # If the field is not present will be dropped + suse = { + # "alias": "repo", + "name": "humanname", + "priority": "priority", + "enabled": "enabled", + "autorefresh": "refresh", + "gpgcheck": "gpgcheck", + "keepackages": "cache", + "baseurl": "url", + } + translator = { + "Suse": suse, + } + table = translator.get(__grains__["os_family"], {}) + return {table[k]: v for k, v in repo.items() if k in table} + + +def _normalize_key(key): + """Normalize the info_gpg_key information""" + + # If the field is not present will be dropped + rpm = { + "Description": "key", + } + translator = { + "Suse": rpm, + "RedHat": rpm, + } + table = translator.get(__grains__["os_family"], {}) + return {table[k]: v for k, v in key.items() if k in table} + + +def _repos_keys_migrate_drop(root, keys, drop): + """Helper function to calculate repost and key migrations""" + + def _d2s(d): + """Serialize a dict and store in a set""" + return { + (k, tuple((_k, _v) for _k, _v in sorted(v.items()))) + for k, v in sorted(d.items()) + } + + src_repos = _d2s( + {k: _normalize_repo(v) for k, v in __salt__["pkg.list_repos"]().items()} + ) + # There is no guarantee that the target repository is even initialized + try: + tgt_repos = _d2s( + { + k: _normalize_repo(v) + for k, v in __salt__["pkg.list_repos"](root=root).items() + } + ) + except Exception: # pylint: disable=broad-except + tgt_repos = set() + + src_keys = set() + tgt_keys = set() + if keys: + src_keys = _d2s( + { + k: _normalize_key(v) + for k, v in __salt__["lowpkg.list_gpg_keys"](info=True).items() + } + ) + try: + tgt_keys = _d2s( + { + k: _normalize_key(v) + for k, v in __salt__["lowpkg.list_gpg_keys"]( + info=True, root=root + ).items() + } + ) + except Exception: # pylint: disable=broad-except + pass + + repos_to_migrate = src_repos - tgt_repos + repos_to_drop = tgt_repos - src_repos if drop else set() + + keys_to_migrate = src_keys - tgt_keys + keys_to_drop = tgt_keys - src_keys if drop else set() + + return (repos_to_migrate, repos_to_drop, keys_to_migrate, keys_to_drop) + + +def _copy_repository_to(root): + repo = { + "Suse": ["/etc/zypp/repos.d"], + "RedHat": ["/etc/yum.conf", "/etc/yum.repos.d"], + } + for src in repo.get(__grains__["os_family"], []): + dst = os.path.join(root, os.path.relpath(src, os.path.sep)) + __salt__["file.copy"](src=src, dst=dst, recurse=True) + + +def migrated(name, keys=True, drop=False, method=None, **kwargs): + """Migrate a repository from one directory to another, including the + GPG keys if requested + + .. versionadded:: TBD + + name + Directory were to migrate the repositories. For example, if we + are booting from a USB key and we mounted the rootfs in + "/mnt", the repositories will live in "/mnt/etc/yum.repos.d" + or in "/etc/zypp/repos.d", depending on the system. For both + cases the expected value for "name" would be "/mnt" + + keys + If is is True, will migrate all the keys + + drop + If True, the target repositories that do not exist in the + source will be dropped + + method + If None or "salt", it will use the Salt API to migrate the + repositories, if "copy", it will copy the repository files + directly + + """ + ret = {"name": name, "result": False, "changes": {}, "comment": ""} + + if __grains__["os_family"] not in ("Suse",): + ret["comment"] = "Migration not supported for this platform" + return ret + + if keys and "lowpkg.import_gpg_key" not in __salt__: + ret["comment"] = "Keys cannot be migrated for this platform" + return ret + + if method not in (None, "salt", "copy"): + ret["comment"] = "Migration method not supported" + return ret + + ( + repos_to_migrate, + repos_to_drop, + keys_to_migrate, + keys_to_drop, + ) = _repos_keys_migrate_drop(name, keys, drop) + + if not any((repos_to_migrate, repos_to_drop, keys_to_migrate, keys_to_drop)): + ret["result"] = True + ret["comment"] = "Repositories are already migrated" + return ret + + if __opts__["test"]: + ret["result"] = None + ret["comment"] = "There are keys or repositories to migrate or drop" + ret["changes"] = { + "repos to migrate": [repo for repo, _ in repos_to_migrate], + "repos to drop": [repo for repo, _ in repos_to_drop], + "keys to migrate": [key for key, _ in keys_to_migrate], + "keys to drop": [key for key, _ in keys_to_drop], + } + return ret + + for repo, repo_info in repos_to_migrate: + if method == "copy": + _copy_repository_to(name) + else: + __salt__["pkg.mod_repo"](repo, **dict(repo_info), root=name) + for repo, _ in repos_to_drop: + __salt__["pkg.del_repo"](repo, root=name) + + for _, key_info in keys_to_migrate: + __salt__["lowpkg.import_gpg_key"](dict(key_info)["key"], root=name) + for key, _ in keys_to_drop: + __salt__["lowpkg.remove_gpg_key"](key, root=name) + + ( + rem_repos_to_migrate, + rem_repos_to_drop, + rem_keys_to_migrate, + rem_keys_to_drop, + ) = _repos_keys_migrate_drop(name, keys, drop) + + if any( + (rem_repos_to_migrate, rem_repos_to_drop, rem_keys_to_migrate, rem_keys_to_drop) + ): + ret["result"] = False + ret["comment"] = "Migration of repositories failed" + return ret + + ret["result"] = True + ret["comment"] = "Repositories synchronized" + ret["changes"] = { + "repos migrated": [repo for repo, _ in repos_to_migrate], + "repos dropped": [repo for repo, _ in repos_to_drop], + "keys migrated": [key for key, _ in keys_to_migrate], + "keys dropped": [key for key, _ in keys_to_drop], + } + + return ret diff --git a/tests/pytests/unit/modules/test_yumpkg.py b/tests/pytests/unit/modules/test_yumpkg.py index 66c86972a0..ef7100fe9d 100644 --- a/tests/pytests/unit/modules/test_yumpkg.py +++ b/tests/pytests/unit/modules/test_yumpkg.py @@ -7,7 +7,7 @@ import salt.modules.rpm_lowpkg as rpm import salt.modules.yumpkg as yumpkg import salt.utils.platform from salt.exceptions import CommandExecutionError, SaltInvocationError -from tests.support.mock import MagicMock, Mock, patch +from tests.support.mock import MagicMock, Mock, call, mock_open, patch try: import pytest @@ -1683,6 +1683,91 @@ def test_get_repo_with_non_existent_repo(list_repos_var): assert ret == expected, ret +def test_get_repo_keys(): + salt_mock = {"lowpkg.list_gpg_keys": MagicMock(return_value=True)} + with patch.dict(yumpkg.__salt__, salt_mock): + assert yumpkg.get_repo_keys(info=True, root="/mnt") + salt_mock["lowpkg.list_gpg_keys"].assert_called_once_with(True, "/mnt") + + +def test_add_repo_key_fail(): + with pytest.raises(SaltInvocationError): + yumpkg.add_repo_key() + + with pytest.raises(SaltInvocationError): + yumpkg.add_repo_key(path="path", text="text") + + +def test_add_repo_key_path(): + salt_mock = { + "cp.cache_file": MagicMock(return_value="path"), + "lowpkg.import_gpg_key": MagicMock(return_value=True), + } + with patch("salt.utils.files.fopen", mock_open(read_data="text")), patch.dict( + yumpkg.__salt__, salt_mock + ): + assert yumpkg.add_repo_key(path="path", root="/mnt") + salt_mock["cp.cache_file"].assert_called_once_with("path", "base") + salt_mock["lowpkg.import_gpg_key"].assert_called_once_with("text", "/mnt") + + +def test_add_repo_key_text(): + salt_mock = {"lowpkg.import_gpg_key": MagicMock(return_value=True)} + with patch.dict(yumpkg.__salt__, salt_mock): + assert yumpkg.add_repo_key(text="text", root="/mnt") + salt_mock["lowpkg.import_gpg_key"].assert_called_once_with("text", "/mnt") + + +def test_del_repo_key(): + salt_mock = {"lowpkg.remove_gpg_key": MagicMock(return_value=True)} + with patch.dict(yumpkg.__salt__, salt_mock): + assert yumpkg.del_repo_key(keyid="keyid", root="/mnt") + salt_mock["lowpkg.remove_gpg_key"].assert_called_once_with("keyid", "/mnt") + + +def test_pkg_update_dnf(): + """ + Tests that the proper CLI options are added when obsoletes=False + """ + name = "foo" + old = "1.2.2-1.fc31" + new = "1.2.3-1.fc31" + cmd_mock = MagicMock(return_value={"retcode": 0}) + list_pkgs_mock = MagicMock(side_effect=[{name: old}, {name: new}]) + parse_targets_mock = MagicMock(return_value=({"foo": None}, "repository")) + with patch.dict( + yumpkg.__salt__, + {"cmd.run_all": cmd_mock, "pkg_resource.parse_targets": parse_targets_mock}, + ), patch.object(yumpkg, "refresh_db", MagicMock()), patch.object( + yumpkg, "list_pkgs", list_pkgs_mock + ), patch.object( + yumpkg, "_yum", MagicMock(return_value="dnf") + ), patch( + "salt.utils.systemd.has_scope", MagicMock(return_value=False) + ): + ret = yumpkg.update(name, setopt="obsoletes=0,plugins=0") + expected = {name: {"old": old, "new": new}} + assert ret == expected, ret + + cmd_mock.assert_called_once_with( + [ + "dnf", + "--quiet", + "-y", + "--setopt", + "obsoletes=0", + "--setopt", + "plugins=0", + "--obsoletes=False", + "upgrade", + "foo", + ], + env={}, + output_loglevel="trace", + python_shell=False, + ) + + def test_call_yum_default(): """ Call default Yum/Dnf. diff --git a/tests/pytests/unit/modules/test_zypperpkg.py b/tests/pytests/unit/modules/test_zypperpkg.py index aece43ea29..37bbef87b7 100644 --- a/tests/pytests/unit/modules/test_zypperpkg.py +++ b/tests/pytests/unit/modules/test_zypperpkg.py @@ -8,7 +8,8 @@ import os import pytest import salt.modules.pkg_resource as pkg_resource import salt.modules.zypperpkg as zypper -from tests.support.mock import MagicMock, patch +from salt.exceptions import SaltInvocationError +from tests.support.mock import MagicMock, mock_open, patch @pytest.fixture @@ -55,3 +56,66 @@ def test_list_pkgs_no_context(): pkgs = zypper.list_pkgs(versions_as_list=True, use_context=False) list_pkgs_context_mock.assert_not_called() list_pkgs_context_mock.reset_mock() + + +def test_normalize_name(): + """ + Test that package is normalized only when it should be + """ + with patch.dict(zypper.__grains__, {"osarch": "x86_64"}): + result = zypper.normalize_name("foo") + assert result == "foo", result + result = zypper.normalize_name("foo.x86_64") + assert result == "foo", result + result = zypper.normalize_name("foo.noarch") + assert result == "foo", result + + with patch.dict(zypper.__grains__, {"osarch": "aarch64"}): + result = zypper.normalize_name("foo") + assert result == "foo", result + result = zypper.normalize_name("foo.aarch64") + assert result == "foo", result + result = zypper.normalize_name("foo.noarch") + assert result == "foo", result + + +def test_get_repo_keys(): + salt_mock = {"lowpkg.list_gpg_keys": MagicMock(return_value=True)} + with patch.dict(zypper.__salt__, salt_mock): + assert zypper.get_repo_keys(info=True, root="/mnt") + salt_mock["lowpkg.list_gpg_keys"].assert_called_once_with(True, "/mnt") + + +def test_add_repo_key_fail(): + with pytest.raises(SaltInvocationError): + zypper.add_repo_key() + + with pytest.raises(SaltInvocationError): + zypper.add_repo_key(path="path", text="text") + + +def test_add_repo_key_path(): + salt_mock = { + "cp.cache_file": MagicMock(return_value="path"), + "lowpkg.import_gpg_key": MagicMock(return_value=True), + } + with patch("salt.utils.files.fopen", mock_open(read_data="text")), patch.dict( + zypper.__salt__, salt_mock + ): + assert zypper.add_repo_key(path="path", root="/mnt") + salt_mock["cp.cache_file"].assert_called_once_with("path", "base") + salt_mock["lowpkg.import_gpg_key"].assert_called_once_with("text", "/mnt") + + +def test_add_repo_key_text(): + salt_mock = {"lowpkg.import_gpg_key": MagicMock(return_value=True)} + with patch.dict(zypper.__salt__, salt_mock): + assert zypper.add_repo_key(text="text", root="/mnt") + salt_mock["lowpkg.import_gpg_key"].assert_called_once_with("text", "/mnt") + + +def test_del_repo_key(): + salt_mock = {"lowpkg.remove_gpg_key": MagicMock(return_value=True)} + 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") diff --git a/tests/pytests/unit/states/test_pkgrepo.py b/tests/pytests/unit/states/test_pkgrepo.py index daa913bcc2..cbb12cfb9b 100644 --- a/tests/pytests/unit/states/test_pkgrepo.py +++ b/tests/pytests/unit/states/test_pkgrepo.py @@ -51,3 +51,451 @@ def test_update_key_url(): assert ret["changes"] == { "key_url": {"old": kwargs["key_url"], "new": changed_kwargs["key_url"]} } + + +def test__normalize_repo_suse(): + repo = { + "name": "repo name", + "autorefresh": True, + "priority": 0, + "pkg_gpgcheck": True, + } + grains = {"os_family": "Suse"} + with patch.dict(pkgrepo.__grains__, grains): + assert pkgrepo._normalize_repo(repo) == { + "humanname": "repo name", + "refresh": True, + "priority": 0, + } + + +def test__normalize_key_rpm(): + key = {"Description": "key", "Date": "Date", "Other": "Other"} + for os_family in ("Suse", "RedHat"): + grains = {"os_family": os_family} + with patch.dict(pkgrepo.__grains__, grains): + assert pkgrepo._normalize_key(key) == {"key": "key"} + + +def test__repos_keys_migrate_drop_migrate_to_empty(): + src_repos = { + "repo-1": { + "name": "repo name 1", + "autorefresh": True, + "priority": 0, + "pkg_gpgcheck": True, + }, + "repo-2": { + "name": "repo name 2", + "autorefresh": True, + "priority": 0, + "pkg_gpgcheck": False, + }, + } + tgt_repos = {} + + src_keys = { + "key1": {"Description": "key1", "Other": "Other1"}, + "key2": {"Description": "key2", "Other": "Other2"}, + } + tgt_keys = {} + + grains = {"os_family": "Suse"} + salt_mock = { + "pkg.list_repos": MagicMock(side_effect=[src_repos, tgt_repos]), + "lowpkg.list_gpg_keys": MagicMock(side_effect=[src_keys, tgt_keys]), + } + with patch.dict(pkgrepo.__grains__, grains), patch.dict( + pkgrepo.__salt__, salt_mock + ): + assert pkgrepo._repos_keys_migrate_drop("/mnt", False, False) == ( + { + ( + "repo-1", + ( + ("humanname", "repo name 1"), + ("priority", 0), + ("refresh", True), + ), + ), + ( + "repo-2", + ( + ("humanname", "repo name 2"), + ("priority", 0), + ("refresh", True), + ), + ), + }, + set(), + set(), + set(), + ) + + +def test__repos_keys_migrate_drop_migrate_to_empty_keys(): + src_repos = { + "repo-1": { + "name": "repo name 1", + "autorefresh": True, + "priority": 0, + "pkg_gpgcheck": True, + }, + "repo-2": { + "name": "repo name 2", + "autorefresh": True, + "priority": 0, + "pkg_gpgcheck": False, + }, + } + tgt_repos = {} + + src_keys = { + "key1": {"Description": "key1", "Other": "Other1"}, + "key2": {"Description": "key2", "Other": "Other2"}, + } + tgt_keys = {} + + grains = {"os_family": "Suse"} + salt_mock = { + "pkg.list_repos": MagicMock(side_effect=[src_repos, tgt_repos]), + "lowpkg.list_gpg_keys": MagicMock(side_effect=[src_keys, tgt_keys]), + } + with patch.dict(pkgrepo.__grains__, grains), patch.dict( + pkgrepo.__salt__, salt_mock + ): + assert pkgrepo._repos_keys_migrate_drop("/mnt", True, False) == ( + { + ( + "repo-1", + ( + ("humanname", "repo name 1"), + ("priority", 0), + ("refresh", True), + ), + ), + ( + "repo-2", + ( + ("humanname", "repo name 2"), + ("priority", 0), + ("refresh", True), + ), + ), + }, + set(), + {("key1", (("key", "key1"),)), ("key2", (("key", "key2"),))}, + set(), + ) + + +def test__repos_keys_migrate_drop_migrate_to_populated_no_drop(): + src_repos = { + "repo-1": { + "name": "repo name 1", + "autorefresh": True, + "priority": 0, + "pkg_gpgcheck": True, + }, + "repo-2": { + "name": "repo name 2", + "autorefresh": True, + "priority": 0, + "pkg_gpgcheck": False, + }, + } + tgt_repos = { + "repo-1": { + "name": "repo name 1", + "autorefresh": True, + "priority": 0, + "pkg_gpgcheck": True, + }, + "repo-3": { + "name": "repo name 3", + "autorefresh": True, + "priority": 0, + "pkg_gpgcheck": False, + }, + } + + src_keys = { + "key1": {"Description": "key1", "Other": "Other1"}, + "key2": {"Description": "key2", "Other": "Other2"}, + } + tgt_keys = { + "key1": {"Description": "key1", "Other": "Other1"}, + "key3": {"Description": "key3", "Other": "Other2"}, + } + + grains = {"os_family": "Suse"} + salt_mock = { + "pkg.list_repos": MagicMock(side_effect=[src_repos, tgt_repos]), + "lowpkg.list_gpg_keys": MagicMock(side_effect=[src_keys, tgt_keys]), + } + with patch.dict(pkgrepo.__grains__, grains), patch.dict( + pkgrepo.__salt__, salt_mock + ): + assert pkgrepo._repos_keys_migrate_drop("/mnt", True, False) == ( + { + ( + "repo-2", + ( + ("humanname", "repo name 2"), + ("priority", 0), + ("refresh", True), + ), + ), + }, + set(), + {("key2", (("key", "key2"),))}, + set(), + ) + + +def test__repos_keys_migrate_drop_migrate_to_populated_drop(): + src_repos = { + "repo-1": { + "name": "repo name 1", + "autorefresh": True, + "priority": 0, + "pkg_gpgcheck": True, + }, + "repo-2": { + "name": "repo name 2", + "autorefresh": True, + "priority": 0, + "pkg_gpgcheck": False, + }, + } + tgt_repos = { + "repo-1": { + "name": "repo name 1", + "autorefresh": True, + "priority": 0, + "pkg_gpgcheck": True, + }, + "repo-3": { + "name": "repo name 3", + "autorefresh": True, + "priority": 0, + "pkg_gpgcheck": False, + }, + } + + src_keys = { + "key1": {"Description": "key1", "Other": "Other1"}, + "key2": {"Description": "key2", "Other": "Other2"}, + } + tgt_keys = { + "key1": {"Description": "key1", "Other": "Other1"}, + "key3": {"Description": "key3", "Other": "Other2"}, + } + + grains = {"os_family": "Suse"} + salt_mock = { + "pkg.list_repos": MagicMock(side_effect=[src_repos, tgt_repos]), + "lowpkg.list_gpg_keys": MagicMock(side_effect=[src_keys, tgt_keys]), + } + with patch.dict(pkgrepo.__grains__, grains), patch.dict( + pkgrepo.__salt__, salt_mock + ): + assert pkgrepo._repos_keys_migrate_drop("/mnt", True, True) == ( + { + ( + "repo-2", + ( + ("humanname", "repo name 2"), + ("priority", 0), + ("refresh", True), + ), + ), + }, + { + ( + "repo-3", + ( + ("humanname", "repo name 3"), + ("priority", 0), + ("refresh", True), + ), + ), + }, + {("key2", (("key", "key2"),))}, + {("key3", (("key", "key3"),))}, + ) + + +@pytest.mark.skip_on_windows(reason="Not a Windows test") +def test__copy_repository_to_suse(): + grains = {"os_family": "Suse"} + salt_mock = {"file.copy": MagicMock()} + with patch.dict(pkgrepo.__grains__, grains), patch.dict( + pkgrepo.__salt__, salt_mock + ): + pkgrepo._copy_repository_to("/mnt") + salt_mock["file.copy"].assert_called_with( + src="/etc/zypp/repos.d", dst="/mnt/etc/zypp/repos.d", recurse=True + ) + + +def test_migrated_non_supported_platform(): + grains = {"os_family": "Debian"} + with patch.dict(pkgrepo.__grains__, grains): + assert pkgrepo.migrated("/mnt") == { + "name": "/mnt", + "result": False, + "changes": {}, + "comment": "Migration not supported for this platform", + } + + +def test_migrated_missing_keys_api(): + grains = {"os_family": "Suse"} + with patch.dict(pkgrepo.__grains__, grains): + assert pkgrepo.migrated("/mnt") == { + "name": "/mnt", + "result": False, + "changes": {}, + "comment": "Keys cannot be migrated for this platform", + } + + +def test_migrated_wrong_method(): + grains = {"os_family": "Suse"} + salt_mock = { + "lowpkg.import_gpg_key": True, + } + with patch.dict(pkgrepo.__grains__, grains), patch.dict( + pkgrepo.__salt__, salt_mock + ): + assert pkgrepo.migrated("/mnt", method_="magic") == { + "name": "/mnt", + "result": False, + "changes": {}, + "comment": "Migration method not supported", + } + + +@patch( + "salt.states.pkgrepo._repos_keys_migrate_drop", + MagicMock(return_value=(set(), set(), set(), set())), +) +def test_migrated_empty(): + grains = {"os_family": "Suse"} + salt_mock = { + "lowpkg.import_gpg_key": True, + } + with patch.dict(pkgrepo.__grains__, grains), patch.dict( + pkgrepo.__salt__, salt_mock + ): + assert pkgrepo.migrated("/mnt") == { + "name": "/mnt", + "result": True, + "changes": {}, + "comment": "Repositories are already migrated", + } + + +def test_migrated(): + _repos_keys_migrate_drop = MagicMock() + _repos_keys_migrate_drop.side_effect = [ + ( + { + ( + "repo-1", + ( + ("humanname", "repo name 1"), + ("priority", 0), + ("refresh", True), + ), + ), + }, + { + ( + "repo-2", + ( + ("humanname", "repo name 2"), + ("priority", 0), + ("refresh", True), + ), + ), + }, + {("key1", (("key", "key1"),))}, + {("key2", (("key", "key2"),))}, + ), + (set(), set(), set(), set()), + ] + + grains = {"os_family": "Suse"} + opts = {"test": False} + salt_mock = { + "pkg.mod_repo": MagicMock(), + "pkg.del_repo": MagicMock(), + "lowpkg.import_gpg_key": MagicMock(), + "lowpkg.remove_gpg_key": MagicMock(), + } + with patch.dict(pkgrepo.__grains__, grains), patch.dict( + pkgrepo.__opts__, opts + ), patch.dict(pkgrepo.__salt__, salt_mock), patch( + "salt.states.pkgrepo._repos_keys_migrate_drop", _repos_keys_migrate_drop + ): + assert pkgrepo.migrated("/mnt", True, True) == { + "name": "/mnt", + "result": True, + "changes": { + "repos migrated": ["repo-1"], + "repos dropped": ["repo-2"], + "keys migrated": ["key1"], + "keys dropped": ["key2"], + }, + "comment": "Repositories synchronized", + } + salt_mock["pkg.mod_repo"].assert_called_with( + "repo-1", humanname="repo name 1", priority=0, refresh=True, root="/mnt" + ) + salt_mock["pkg.del_repo"].assert_called_with("repo-2", root="/mnt") + salt_mock["lowpkg.import_gpg_key"].assert_called_with("key1", root="/mnt") + salt_mock["lowpkg.remove_gpg_key"].assert_called_with("key2", root="/mnt") + + +def test_migrated_test(): + _repos_keys_migrate_drop = MagicMock() + _repos_keys_migrate_drop.return_value = ( + { + ( + "repo-1", + (("humanname", "repo name 1"), ("priority", 0), ("refresh", True)), + ), + }, + { + ( + "repo-2", + (("humanname", "repo name 2"), ("priority", 0), ("refresh", True)), + ), + }, + {("key1", (("key", "key1"),))}, + {("key2", (("key", "key2"),))}, + ) + + grains = {"os_family": "Suse"} + opts = {"test": True} + salt_mock = { + "lowpkg.import_gpg_key": True, + } + with patch.dict(pkgrepo.__grains__, grains), patch.dict( + pkgrepo.__opts__, opts + ), patch.dict(pkgrepo.__salt__, salt_mock), patch( + "salt.states.pkgrepo._repos_keys_migrate_drop", _repos_keys_migrate_drop + ): + assert pkgrepo.migrated("/mnt", True, True) == { + "name": "/mnt", + "result": None, + "changes": { + "repos to migrate": ["repo-1"], + "repos to drop": ["repo-2"], + "keys to migrate": ["key1"], + "keys to drop": ["key2"], + }, + "comment": "There are keys or repositories to migrate or drop", + } diff --git a/tests/unit/modules/test_rpm_lowpkg.py b/tests/unit/modules/test_rpm_lowpkg.py index e7e8230510..280a19b911 100644 --- a/tests/unit/modules/test_rpm_lowpkg.py +++ b/tests/unit/modules/test_rpm_lowpkg.py @@ -5,6 +5,7 @@ # Import Python Libs from __future__ import absolute_import +import datetime # Import Salt Libs import salt.modules.rpm_lowpkg as rpm @@ -255,14 +256,223 @@ class RpmTestCase(TestCase, LoaderModuleMockMixin): :return: """ - self.assertEqual(-1, rpm.version_cmp("1", "2")) - self.assertEqual(mock_version_cmp.called, True) - self.assertEqual(mock_log.warning.called, True) - self.assertEqual( - mock_log.warning.mock_calls[0][1][0], - "Please install a package that provides rpm.labelCompare for more accurate version comparisons.", - ) - self.assertEqual( - mock_log.warning.mock_calls[1][1][0], - "Falling back on salt.utils.versions.version_cmp() for version comparisons", - ) + with patch( + "salt.modules.rpm_lowpkg.rpm.labelCompare", MagicMock(return_value=0) + ), patch("salt.modules.rpm_lowpkg.HAS_RPM", False): + self.assertEqual( + -1, rpm.version_cmp("1", "2") + ) # mock returns -1, a python implementation was called + + def test_list_gpg_keys_no_info(self): + """ + Test list_gpg_keys with no extra information + """ + mock = MagicMock(return_value="\n".join(["gpg-pubkey-1", "gpg-pubkey-2"])) + with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}): + self.assertEqual(rpm.list_gpg_keys(), ["gpg-pubkey-1", "gpg-pubkey-2"]) + self.assertFalse(_called_with_root(mock)) + + def test_list_gpg_keys_no_info_root(self): + """ + Test list_gpg_keys with no extra information and root + """ + mock = MagicMock(return_value="\n".join(["gpg-pubkey-1", "gpg-pubkey-2"])) + with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}): + self.assertEqual( + rpm.list_gpg_keys(root="/mnt"), ["gpg-pubkey-1", "gpg-pubkey-2"] + ) + self.assertTrue(_called_with_root(mock)) + + @patch("salt.modules.rpm_lowpkg.info_gpg_key") + def test_list_gpg_keys_info(self, info_gpg_key): + """ + Test list_gpg_keys with extra information + """ + info_gpg_key.side_effect = lambda x, root: { + "Description": "key for {}".format(x) + } + mock = MagicMock(return_value="\n".join(["gpg-pubkey-1", "gpg-pubkey-2"])) + with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}): + self.assertEqual( + rpm.list_gpg_keys(info=True), + { + "gpg-pubkey-1": {"Description": "key for gpg-pubkey-1"}, + "gpg-pubkey-2": {"Description": "key for gpg-pubkey-2"}, + }, + ) + self.assertFalse(_called_with_root(mock)) + + def test_info_gpg_key(self): + """ + Test info_gpg_keys from a normal output + """ + info = """Name : gpg-pubkey +Version : 3dbdc284 +Release : 53674dd4 +Architecture: (none) +Install Date: Fri 08 Mar 2019 11:57:44 AM UTC +Group : Public Keys +Size : 0 +License : pubkey +Signature : (none) +Source RPM : (none) +Build Date : Mon 05 May 2014 10:37:40 AM UTC +Build Host : localhost +Packager : openSUSE Project Signing Key +Summary : gpg(openSUSE Project Signing Key ) +Description : +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: rpm-4.14.2.1 (NSS-3) + +mQENBEkUTD8BCADWLy5d5IpJedHQQSXkC1VK/oAZlJEeBVpSZjMCn8LiHaI9Wq3G +3Vp6wvsP1b3kssJGzVFNctdXt5tjvOLxvrEfRJuGfqHTKILByqLzkeyWawbFNfSQ +93/8OunfSTXC1Sx3hgsNXQuOrNVKrDAQUqT620/jj94xNIg09bLSxsjN6EeTvyiO +mtE9H1J03o9tY6meNL/gcQhxBvwuo205np0JojYBP0pOfN8l9hnIOLkA0yu4ZXig +oKOVmf4iTjX4NImIWldT+UaWTO18NWcCrujtgHueytwYLBNV5N0oJIP2VYuLZfSD +VYuPllv7c6O2UEOXJsdbQaVuzU1HLocDyipnABEBAAG0NG9wZW5TVVNFIFByb2pl +Y3QgU2lnbmluZyBLZXkgPG9wZW5zdXNlQG9wZW5zdXNlLm9yZz6JATwEEwECACYC +GwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAUCU2dN1AUJHR8ElQAKCRC4iy/UPb3C +hGQrB/9teCZ3Nt8vHE0SC5NmYMAE1Spcjkzx6M4r4C70AVTMEQh/8BvgmwkKP/qI +CWo2vC1hMXRgLg/TnTtFDq7kW+mHsCXmf5OLh2qOWCKi55Vitlf6bmH7n+h34Sha +Ei8gAObSpZSF8BzPGl6v0QmEaGKM3O1oUbbB3Z8i6w21CTg7dbU5vGR8Yhi9rNtr +hqrPS+q2yftjNbsODagaOUb85ESfQGx/LqoMePD+7MqGpAXjKMZqsEDP0TbxTwSk +4UKnF4zFCYHPLK3y/hSH5SEJwwPY11l6JGdC1Ue8Zzaj7f//axUs/hTC0UZaEE+a +5v4gbqOcigKaFs9Lc3Bj8b/lE10Y +=i2TA +-----END PGP PUBLIC KEY BLOCK----- + +""" + mock = MagicMock(return_value=info) + with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}): + self.assertEqual( + rpm.info_gpg_key("key"), + { + "Name": "gpg-pubkey", + "Version": "3dbdc284", + "Release": "53674dd4", + "Architecture": None, + "Install Date": datetime.datetime(2019, 3, 8, 11, 57, 44), + "Group": "Public Keys", + "Size": 0, + "License": "pubkey", + "Signature": None, + "Source RPM": None, + "Build Date": datetime.datetime(2014, 5, 5, 10, 37, 40), + "Build Host": "localhost", + "Packager": "openSUSE Project Signing Key ", + "Summary": "gpg(openSUSE Project Signing Key )", + "Description": """-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: rpm-4.14.2.1 (NSS-3) + +mQENBEkUTD8BCADWLy5d5IpJedHQQSXkC1VK/oAZlJEeBVpSZjMCn8LiHaI9Wq3G +3Vp6wvsP1b3kssJGzVFNctdXt5tjvOLxvrEfRJuGfqHTKILByqLzkeyWawbFNfSQ +93/8OunfSTXC1Sx3hgsNXQuOrNVKrDAQUqT620/jj94xNIg09bLSxsjN6EeTvyiO +mtE9H1J03o9tY6meNL/gcQhxBvwuo205np0JojYBP0pOfN8l9hnIOLkA0yu4ZXig +oKOVmf4iTjX4NImIWldT+UaWTO18NWcCrujtgHueytwYLBNV5N0oJIP2VYuLZfSD +VYuPllv7c6O2UEOXJsdbQaVuzU1HLocDyipnABEBAAG0NG9wZW5TVVNFIFByb2pl +Y3QgU2lnbmluZyBLZXkgPG9wZW5zdXNlQG9wZW5zdXNlLm9yZz6JATwEEwECACYC +GwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAUCU2dN1AUJHR8ElQAKCRC4iy/UPb3C +hGQrB/9teCZ3Nt8vHE0SC5NmYMAE1Spcjkzx6M4r4C70AVTMEQh/8BvgmwkKP/qI +CWo2vC1hMXRgLg/TnTtFDq7kW+mHsCXmf5OLh2qOWCKi55Vitlf6bmH7n+h34Sha +Ei8gAObSpZSF8BzPGl6v0QmEaGKM3O1oUbbB3Z8i6w21CTg7dbU5vGR8Yhi9rNtr +hqrPS+q2yftjNbsODagaOUb85ESfQGx/LqoMePD+7MqGpAXjKMZqsEDP0TbxTwSk +4UKnF4zFCYHPLK3y/hSH5SEJwwPY11l6JGdC1Ue8Zzaj7f//axUs/hTC0UZaEE+a +5v4gbqOcigKaFs9Lc3Bj8b/lE10Y +=i2TA +-----END PGP PUBLIC KEY BLOCK-----""", + }, + ) + self.assertFalse(_called_with_root(mock)) + + def test_info_gpg_key_extended(self): + """ + Test info_gpg_keys from an extended output + """ + info = """Name : gpg-pubkey +Version : 3dbdc284 +Release : 53674dd4 +Architecture: (none) +Install Date: Fri 08 Mar 2019 11:57:44 AM UTC +Group : Public Keys +Size : 0 +License : pubkey +Signature : (none) +Source RPM : (none) +Build Date : Mon 05 May 2014 10:37:40 AM UTC +Build Host : localhost +Packager : openSUSE Project Signing Key +Summary : gpg(openSUSE Project Signing Key ) +Description : +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: rpm-4.14.2.1 (NSS-3) + +mQENBEkUTD8BCADWLy5d5IpJedHQQSXkC1VK/oAZlJEeBVpSZjMCn8LiHaI9Wq3G +3Vp6wvsP1b3kssJGzVFNctdXt5tjvOLxvrEfRJuGfqHTKILByqLzkeyWawbFNfSQ +93/8OunfSTXC1Sx3hgsNXQuOrNVKrDAQUqT620/jj94xNIg09bLSxsjN6EeTvyiO +mtE9H1J03o9tY6meNL/gcQhxBvwuo205np0JojYBP0pOfN8l9hnIOLkA0yu4ZXig +oKOVmf4iTjX4NImIWldT+UaWTO18NWcCrujtgHueytwYLBNV5N0oJIP2VYuLZfSD +VYuPllv7c6O2UEOXJsdbQaVuzU1HLocDyipnABEBAAG0NG9wZW5TVVNFIFByb2pl +Y3QgU2lnbmluZyBLZXkgPG9wZW5zdXNlQG9wZW5zdXNlLm9yZz6JATwEEwECACYC +GwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAUCU2dN1AUJHR8ElQAKCRC4iy/UPb3C +hGQrB/9teCZ3Nt8vHE0SC5NmYMAE1Spcjkzx6M4r4C70AVTMEQh/8BvgmwkKP/qI +CWo2vC1hMXRgLg/TnTtFDq7kW+mHsCXmf5OLh2qOWCKi55Vitlf6bmH7n+h34Sha +Ei8gAObSpZSF8BzPGl6v0QmEaGKM3O1oUbbB3Z8i6w21CTg7dbU5vGR8Yhi9rNtr +hqrPS+q2yftjNbsODagaOUb85ESfQGx/LqoMePD+7MqGpAXjKMZqsEDP0TbxTwSk +4UKnF4zFCYHPLK3y/hSH5SEJwwPY11l6JGdC1Ue8Zzaj7f//axUs/hTC0UZaEE+a +5v4gbqOcigKaFs9Lc3Bj8b/lE10Y +=i2TA +-----END PGP PUBLIC KEY BLOCK----- + +Distribution: (none) +""" + mock = MagicMock(return_value=info) + with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}): + self.assertEqual( + rpm.info_gpg_key("key"), + { + "Name": "gpg-pubkey", + "Version": "3dbdc284", + "Release": "53674dd4", + "Architecture": None, + "Install Date": datetime.datetime(2019, 3, 8, 11, 57, 44), + "Group": "Public Keys", + "Size": 0, + "License": "pubkey", + "Signature": None, + "Source RPM": None, + "Build Date": datetime.datetime(2014, 5, 5, 10, 37, 40), + "Build Host": "localhost", + "Packager": "openSUSE Project Signing Key ", + "Summary": "gpg(openSUSE Project Signing Key )", + "Description": """-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: rpm-4.14.2.1 (NSS-3) + +mQENBEkUTD8BCADWLy5d5IpJedHQQSXkC1VK/oAZlJEeBVpSZjMCn8LiHaI9Wq3G +3Vp6wvsP1b3kssJGzVFNctdXt5tjvOLxvrEfRJuGfqHTKILByqLzkeyWawbFNfSQ +93/8OunfSTXC1Sx3hgsNXQuOrNVKrDAQUqT620/jj94xNIg09bLSxsjN6EeTvyiO +mtE9H1J03o9tY6meNL/gcQhxBvwuo205np0JojYBP0pOfN8l9hnIOLkA0yu4ZXig +oKOVmf4iTjX4NImIWldT+UaWTO18NWcCrujtgHueytwYLBNV5N0oJIP2VYuLZfSD +VYuPllv7c6O2UEOXJsdbQaVuzU1HLocDyipnABEBAAG0NG9wZW5TVVNFIFByb2pl +Y3QgU2lnbmluZyBLZXkgPG9wZW5zdXNlQG9wZW5zdXNlLm9yZz6JATwEEwECACYC +GwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAUCU2dN1AUJHR8ElQAKCRC4iy/UPb3C +hGQrB/9teCZ3Nt8vHE0SC5NmYMAE1Spcjkzx6M4r4C70AVTMEQh/8BvgmwkKP/qI +CWo2vC1hMXRgLg/TnTtFDq7kW+mHsCXmf5OLh2qOWCKi55Vitlf6bmH7n+h34Sha +Ei8gAObSpZSF8BzPGl6v0QmEaGKM3O1oUbbB3Z8i6w21CTg7dbU5vGR8Yhi9rNtr +hqrPS+q2yftjNbsODagaOUb85ESfQGx/LqoMePD+7MqGpAXjKMZqsEDP0TbxTwSk +4UKnF4zFCYHPLK3y/hSH5SEJwwPY11l6JGdC1Ue8Zzaj7f//axUs/hTC0UZaEE+a +5v4gbqOcigKaFs9Lc3Bj8b/lE10Y +=i2TA +-----END PGP PUBLIC KEY BLOCK-----""", + "Distribution": None, + }, + ) + self.assertFalse(_called_with_root(mock)) + + def test_remove_gpg_key(self): + """ + Test remove_gpg_key + """ + mock = MagicMock(return_value=0) + with patch.dict(rpm.__salt__, {"cmd.retcode": mock}): + self.assertTrue(rpm.remove_gpg_key("gpg-pubkey-1")) + self.assertFalse(_called_with_root(mock)) diff --git a/tests/unit/modules/test_yumpkg.py b/tests/unit/modules/test_yumpkg.py deleted file mode 100644 index fd57faad32..0000000000 --- a/tests/unit/modules/test_yumpkg.py +++ /dev/null @@ -1,1754 +0,0 @@ -import os - -import salt.modules.cmdmod as cmdmod -import salt.modules.pkg_resource as pkg_resource -import salt.modules.rpm_lowpkg as rpm -import salt.modules.yumpkg as yumpkg -import salt.utils.platform -from salt.exceptions import CommandExecutionError, SaltInvocationError -from tests.support.mixins import LoaderModuleMockMixin -from tests.support.mock import MagicMock, Mock, call, patch -from tests.support.unit import TestCase, skipIf - -try: - import pytest -except ImportError: - pytest = None - -LIST_REPOS = { - "base": { - "file": "/etc/yum.repos.d/CentOS-Base.repo", - "gpgcheck": "1", - "gpgkey": "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7", - "mirrorlist": "http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os&infra=$infra", - "name": "CentOS-$releasever - Base", - }, - "base-source": { - "baseurl": "http://vault.centos.org/centos/$releasever/os/Source/", - "enabled": "0", - "file": "/etc/yum.repos.d/CentOS-Sources.repo", - "gpgcheck": "1", - "gpgkey": "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7", - "name": "CentOS-$releasever - Base Sources", - }, - "updates": { - "file": "/etc/yum.repos.d/CentOS-Base.repo", - "gpgcheck": "1", - "gpgkey": "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7", - "mirrorlist": "http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=updates&infra=$infra", - "name": "CentOS-$releasever - Updates", - }, - "updates-source": { - "baseurl": "http://vault.centos.org/centos/$releasever/updates/Source/", - "enabled": "0", - "file": "/etc/yum.repos.d/CentOS-Sources.repo", - "gpgcheck": "1", - "gpgkey": "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7", - "name": "CentOS-$releasever - Updates Sources", - }, -} - - -class YumTestCase(TestCase, LoaderModuleMockMixin): - """ - Test cases for salt.modules.yumpkg - """ - - def setup_loader_modules(self): - return { - yumpkg: { - "__context__": {"yum_bin": "yum"}, - "__grains__": { - "osarch": "x86_64", - "os": "CentOS", - "os_family": "RedHat", - "osmajorrelease": 7, - }, - }, - pkg_resource: {}, - } - - def test_list_pkgs(self): - """ - Test packages listing. - - :return: - """ - - def _add_data(data, key, value): - data.setdefault(key, []).append(value) - - rpm_out = [ - "python-urlgrabber_|-(none)_|-3.10_|-8.el7_|-noarch_|-(none)_|-1487838471", - "alsa-lib_|-(none)_|-1.1.1_|-1.el7_|-x86_64_|-(none)_|-1487838475", - "gnupg2_|-(none)_|-2.0.22_|-4.el7_|-x86_64_|-(none)_|-1487838477", - "rpm-python_|-(none)_|-4.11.3_|-21.el7_|-x86_64_|-(none)_|-1487838477", - "pygpgme_|-(none)_|-0.3_|-9.el7_|-x86_64_|-(none)_|-1487838478", - "yum_|-(none)_|-3.4.3_|-150.el7.centos_|-noarch_|-(none)_|-1487838479", - "lzo_|-(none)_|-2.06_|-8.el7_|-x86_64_|-(none)_|-1487838479", - "qrencode-libs_|-(none)_|-3.4.1_|-3.el7_|-x86_64_|-(none)_|-1487838480", - "ustr_|-(none)_|-1.0.4_|-16.el7_|-x86_64_|-(none)_|-1487838480", - "shadow-utils_|-2_|-4.1.5.1_|-24.el7_|-x86_64_|-(none)_|-1487838481", - "util-linux_|-(none)_|-2.23.2_|-33.el7_|-x86_64_|-(none)_|-1487838484", - "openssh_|-(none)_|-6.6.1p1_|-33.el7_3_|-x86_64_|-(none)_|-1487838485", - "virt-what_|-(none)_|-1.13_|-8.el7_|-x86_64_|-(none)_|-1487838486", - ] - with patch.dict(yumpkg.__grains__, {"osarch": "x86_64"}), patch.dict( - yumpkg.__salt__, - {"cmd.run": MagicMock(return_value=os.linesep.join(rpm_out))}, - ), patch.dict(yumpkg.__salt__, {"pkg_resource.add_pkg": _add_data}), patch.dict( - yumpkg.__salt__, - {"pkg_resource.format_pkg_list": pkg_resource.format_pkg_list}, - ), patch.dict( - yumpkg.__salt__, {"pkg_resource.stringify": MagicMock()} - ), patch.dict( - pkg_resource.__salt__, {"pkg.parse_arch": yumpkg.parse_arch} - ): - pkgs = yumpkg.list_pkgs(versions_as_list=True) - for pkg_name, pkg_version in { - "python-urlgrabber": "3.10-8.el7", - "alsa-lib": "1.1.1-1.el7", - "gnupg2": "2.0.22-4.el7", - "rpm-python": "4.11.3-21.el7", - "pygpgme": "0.3-9.el7", - "yum": "3.4.3-150.el7.centos", - "lzo": "2.06-8.el7", - "qrencode-libs": "3.4.1-3.el7", - "ustr": "1.0.4-16.el7", - "shadow-utils": "2:4.1.5.1-24.el7", - "util-linux": "2.23.2-33.el7", - "openssh": "6.6.1p1-33.el7_3", - "virt-what": "1.13-8.el7", - }.items(): - self.assertTrue(pkgs.get(pkg_name)) - self.assertEqual(pkgs[pkg_name], [pkg_version]) - - def test_list_pkgs_with_attr(self): - """ - Test packages listing with the attr parameter - - :return: - """ - - def _add_data(data, key, value): - data.setdefault(key, []).append(value) - - rpm_out = [ - "python-urlgrabber_|-(none)_|-3.10_|-8.el7_|-noarch_|-(none)_|-1487838471", - "alsa-lib_|-(none)_|-1.1.1_|-1.el7_|-x86_64_|-(none)_|-1487838475", - "gnupg2_|-(none)_|-2.0.22_|-4.el7_|-x86_64_|-(none)_|-1487838477", - "rpm-python_|-(none)_|-4.11.3_|-21.el7_|-x86_64_|-(none)_|-1487838477", - "pygpgme_|-(none)_|-0.3_|-9.el7_|-x86_64_|-(none)_|-1487838478", - "yum_|-(none)_|-3.4.3_|-150.el7.centos_|-noarch_|-(none)_|-1487838479", - "lzo_|-(none)_|-2.06_|-8.el7_|-x86_64_|-(none)_|-1487838479", - "qrencode-libs_|-(none)_|-3.4.1_|-3.el7_|-x86_64_|-(none)_|-1487838480", - "ustr_|-(none)_|-1.0.4_|-16.el7_|-x86_64_|-(none)_|-1487838480", - "shadow-utils_|-2_|-4.1.5.1_|-24.el7_|-x86_64_|-(none)_|-1487838481", - "util-linux_|-(none)_|-2.23.2_|-33.el7_|-x86_64_|-(none)_|-1487838484", - "openssh_|-(none)_|-6.6.1p1_|-33.el7_3_|-x86_64_|-(none)_|-1487838485", - "virt-what_|-(none)_|-1.13_|-8.el7_|-x86_64_|-(none)_|-1487838486", - ] - with patch.dict(yumpkg.__grains__, {"osarch": "x86_64"}), patch.dict( - yumpkg.__salt__, - {"cmd.run": MagicMock(return_value=os.linesep.join(rpm_out))}, - ), patch.dict(yumpkg.__salt__, {"pkg_resource.add_pkg": _add_data}), patch.dict( - yumpkg.__salt__, - {"pkg_resource.format_pkg_list": pkg_resource.format_pkg_list}, - ), patch.dict( - yumpkg.__salt__, {"pkg_resource.stringify": MagicMock()} - ), patch.dict( - pkg_resource.__salt__, {"pkg.parse_arch": yumpkg.parse_arch} - ): - pkgs = yumpkg.list_pkgs( - attr=["epoch", "release", "arch", "install_date_time_t"] - ) - for pkg_name, pkg_attr in { - "python-urlgrabber": { - "version": "3.10", - "release": "8.el7", - "arch": "noarch", - "install_date_time_t": 1487838471, - "epoch": None, - }, - "alsa-lib": { - "version": "1.1.1", - "release": "1.el7", - "arch": "x86_64", - "install_date_time_t": 1487838475, - "epoch": None, - }, - "gnupg2": { - "version": "2.0.22", - "release": "4.el7", - "arch": "x86_64", - "install_date_time_t": 1487838477, - "epoch": None, - }, - "rpm-python": { - "version": "4.11.3", - "release": "21.el7", - "arch": "x86_64", - "install_date_time_t": 1487838477, - "epoch": None, - }, - "pygpgme": { - "version": "0.3", - "release": "9.el7", - "arch": "x86_64", - "install_date_time_t": 1487838478, - "epoch": None, - }, - "yum": { - "version": "3.4.3", - "release": "150.el7.centos", - "arch": "noarch", - "install_date_time_t": 1487838479, - "epoch": None, - }, - "lzo": { - "version": "2.06", - "release": "8.el7", - "arch": "x86_64", - "install_date_time_t": 1487838479, - "epoch": None, - }, - "qrencode-libs": { - "version": "3.4.1", - "release": "3.el7", - "arch": "x86_64", - "install_date_time_t": 1487838480, - "epoch": None, - }, - "ustr": { - "version": "1.0.4", - "release": "16.el7", - "arch": "x86_64", - "install_date_time_t": 1487838480, - "epoch": None, - }, - "shadow-utils": { - "epoch": "2", - "version": "4.1.5.1", - "release": "24.el7", - "arch": "x86_64", - "install_date_time_t": 1487838481, - }, - "util-linux": { - "version": "2.23.2", - "release": "33.el7", - "arch": "x86_64", - "install_date_time_t": 1487838484, - "epoch": None, - }, - "openssh": { - "version": "6.6.1p1", - "release": "33.el7_3", - "arch": "x86_64", - "install_date_time_t": 1487838485, - "epoch": None, - }, - "virt-what": { - "version": "1.13", - "release": "8.el7", - "install_date_time_t": 1487838486, - "arch": "x86_64", - "epoch": None, - }, - }.items(): - - self.assertTrue(pkgs.get(pkg_name)) - self.assertEqual(pkgs[pkg_name], [pkg_attr]) - - def test_list_pkgs_with_attr_multiple_versions(self): - """ - Test packages listing with the attr parameter reporting multiple version installed - - :return: - """ - - def _add_data(data, key, value): - data.setdefault(key, []).append(value) - - rpm_out = [ - "glibc_|-(none)_|-2.12_|-1.212.el6_|-i686_|-(none)_|-1542394210" - "glibc_|-(none)_|-2.12_|-1.212.el6_|-x86_64_|-(none)_|-1542394204", - "virt-what_|-(none)_|-1.13_|-8.el7_|-x86_64_|-(none)_|-1487838486", - "virt-what_|-(none)_|-1.10_|-2.el7_|-x86_64_|-(none)_|-1387838486", - ] - with patch.dict(yumpkg.__grains__, {"osarch": "x86_64"}), patch.dict( - yumpkg.__salt__, - {"cmd.run": MagicMock(return_value=os.linesep.join(rpm_out))}, - ), patch.dict(yumpkg.__salt__, {"pkg_resource.add_pkg": _add_data}), patch.dict( - yumpkg.__salt__, - {"pkg_resource.format_pkg_list": pkg_resource.format_pkg_list}, - ), patch.dict( - yumpkg.__salt__, {"pkg_resource.stringify": MagicMock()} - ), patch.dict( - pkg_resource.__salt__, {"pkg.parse_arch": yumpkg.parse_arch} - ): - pkgs = yumpkg.list_pkgs( - attr=["epoch", "release", "arch", "install_date_time_t"] - ) - expected_pkg_list = { - "glibc": [ - { - "version": "2.12", - "release": "1.212.el6", - "install_date_time_t": 1542394210, - "arch": "i686", - "epoch": None, - }, - { - "version": "2.12", - "release": "1.212.el6", - "install_date_time_t": 1542394204, - "arch": "x86_64", - "epoch": None, - }, - ], - "virt-what": [ - { - "version": "1.10", - "release": "2.el7", - "install_date_time_t": 1387838486, - "arch": "x86_64", - "epoch": None, - }, - { - "version": "1.13", - "release": "8.el7", - "install_date_time_t": 1487838486, - "arch": "x86_64", - "epoch": None, - }, - ], - } - for pkgname, pkginfo in pkgs.items(): - self.assertCountEqual(pkginfo, expected_pkg_list[pkgname]) - - def test_list_patches(self): - """ - Test patches listing. - - :return: - """ - yum_out = [ - "i my-fake-patch-not-installed-1234 recommended spacewalk-usix-2.7.5.2-2.2.noarch", - " my-fake-patch-not-installed-1234 recommended spacewalksd-5.0.26.2-21.2.x86_64", - "i my-fake-patch-not-installed-1234 recommended suseRegisterInfo-3.1.1-18.2.x86_64", - "i my-fake-patch-installed-1234 recommended my-package-one-1.1-0.1.x86_64", - "i my-fake-patch-installed-1234 recommended my-package-two-1.1-0.1.x86_64", - ] - - expected_patches = { - "my-fake-patch-not-installed-1234": { - "installed": False, - "summary": [ - "spacewalk-usix-2.7.5.2-2.2.noarch", - "spacewalksd-5.0.26.2-21.2.x86_64", - "suseRegisterInfo-3.1.1-18.2.x86_64", - ], - }, - "my-fake-patch-installed-1234": { - "installed": True, - "summary": [ - "my-package-one-1.1-0.1.x86_64", - "my-package-two-1.1-0.1.x86_64", - ], - }, - } - - with patch.dict(yumpkg.__grains__, {"osarch": "x86_64"}), patch.dict( - yumpkg.__salt__, - {"cmd.run_stdout": MagicMock(return_value=os.linesep.join(yum_out))}, - ): - patches = yumpkg.list_patches() - self.assertFalse(patches["my-fake-patch-not-installed-1234"]["installed"]) - self.assertTrue( - len(patches["my-fake-patch-not-installed-1234"]["summary"]) == 3 - ) - for _patch in expected_patches["my-fake-patch-not-installed-1234"][ - "summary" - ]: - self.assertTrue( - _patch in patches["my-fake-patch-not-installed-1234"]["summary"] - ) - - self.assertTrue(patches["my-fake-patch-installed-1234"]["installed"]) - self.assertTrue( - len(patches["my-fake-patch-installed-1234"]["summary"]) == 2 - ) - for _patch in expected_patches["my-fake-patch-installed-1234"]["summary"]: - self.assertTrue( - _patch in patches["my-fake-patch-installed-1234"]["summary"] - ) - - def test_latest_version_with_options(self): - with patch.object(yumpkg, "list_pkgs", MagicMock(return_value={})): - - # with fromrepo - cmd = MagicMock(return_value={"retcode": 0, "stdout": ""}) - with patch.dict( - yumpkg.__salt__, - {"cmd.run_all": cmd, "config.get": MagicMock(return_value=False)}, - ): - yumpkg.latest_version( - "foo", refresh=False, fromrepo="good", branch="foo" - ) - cmd.assert_called_once_with( - [ - "yum", - "--quiet", - "--disablerepo=*", - "--enablerepo=good", - "--branch=foo", - "list", - "available", - "foo", - ], - env={}, - ignore_retcode=True, - output_loglevel="trace", - python_shell=False, - ) - - # without fromrepo - cmd = MagicMock(return_value={"retcode": 0, "stdout": ""}) - with patch.dict( - yumpkg.__salt__, - {"cmd.run_all": cmd, "config.get": MagicMock(return_value=False)}, - ): - yumpkg.latest_version( - "foo", - refresh=False, - enablerepo="good", - disablerepo="bad", - branch="foo", - ) - cmd.assert_called_once_with( - [ - "yum", - "--quiet", - "--disablerepo=bad", - "--enablerepo=good", - "--branch=foo", - "list", - "available", - "foo", - ], - env={}, - ignore_retcode=True, - output_loglevel="trace", - python_shell=False, - ) - - # without fromrepo, but within the scope - cmd = MagicMock(return_value={"retcode": 0, "stdout": ""}) - with patch("salt.utils.systemd.has_scope", MagicMock(return_value=True)): - with patch.dict( - yumpkg.__salt__, - {"cmd.run_all": cmd, "config.get": MagicMock(return_value=True)}, - ): - yumpkg.latest_version( - "foo", - refresh=False, - enablerepo="good", - disablerepo="bad", - branch="foo", - ) - cmd.assert_called_once_with( - [ - "systemd-run", - "--scope", - "yum", - "--quiet", - "--disablerepo=bad", - "--enablerepo=good", - "--branch=foo", - "list", - "available", - "foo", - ], - env={}, - ignore_retcode=True, - output_loglevel="trace", - python_shell=False, - ) - - def test_list_repo_pkgs_with_options(self): - """ - Test list_repo_pkgs with and without fromrepo - - NOTE: mock_calls is a stack. The most recent call is indexed - with 0, while the first call would have the highest index. - """ - really_old_yum = MagicMock(return_value="3.2.0") - older_yum = MagicMock(return_value="3.4.0") - newer_yum = MagicMock(return_value="3.4.5") - list_repos_mock = MagicMock(return_value=LIST_REPOS) - kwargs = { - "output_loglevel": "trace", - "ignore_retcode": True, - "python_shell": False, - "env": {}, - } - - with patch.object(yumpkg, "list_repos", list_repos_mock): - - # Test with really old yum. The fromrepo argument has no effect on - # the yum commands we'd run. - with patch.dict(yumpkg.__salt__, {"cmd.run": really_old_yum}): - - cmd = MagicMock(return_value={"retcode": 0, "stdout": ""}) - with patch.dict( - yumpkg.__salt__, - {"cmd.run_all": cmd, "config.get": MagicMock(return_value=False)}, - ): - yumpkg.list_repo_pkgs("foo") - # We should have called cmd.run_all twice - assert len(cmd.mock_calls) == 2 - - # Check args from first call - assert cmd.mock_calls[1][1] == ( - ["yum", "--quiet", "list", "available"], - ) - - # Check kwargs from first call - assert cmd.mock_calls[1][2] == kwargs - - # Check args from second call - assert cmd.mock_calls[0][1] == ( - ["yum", "--quiet", "list", "installed"], - ) - - # Check kwargs from second call - assert cmd.mock_calls[0][2] == kwargs - - # Test with really old yum. The fromrepo argument has no effect on - # the yum commands we'd run. - with patch.dict(yumpkg.__salt__, {"cmd.run": older_yum}): - - cmd = MagicMock(return_value={"retcode": 0, "stdout": ""}) - with patch.dict( - yumpkg.__salt__, - {"cmd.run_all": cmd, "config.get": MagicMock(return_value=False)}, - ): - yumpkg.list_repo_pkgs("foo") - # We should have called cmd.run_all twice - assert len(cmd.mock_calls) == 2 - - # Check args from first call - assert cmd.mock_calls[1][1] == ( - ["yum", "--quiet", "--showduplicates", "list", "available"], - ) - - # Check kwargs from first call - assert cmd.mock_calls[1][2] == kwargs - - # Check args from second call - assert cmd.mock_calls[0][1] == ( - ["yum", "--quiet", "--showduplicates", "list", "installed"], - ) - - # Check kwargs from second call - assert cmd.mock_calls[0][2] == kwargs - - # Test with newer yum. We should run one yum command per repo, so - # fromrepo would limit how many calls we make. - with patch.dict(yumpkg.__salt__, {"cmd.run": newer_yum}): - - # When fromrepo is used, we would only run one yum command, for - # that specific repo. - cmd = MagicMock(return_value={"retcode": 0, "stdout": ""}) - with patch.dict( - yumpkg.__salt__, - {"cmd.run_all": cmd, "config.get": MagicMock(return_value=False)}, - ): - yumpkg.list_repo_pkgs("foo", fromrepo="base") - # We should have called cmd.run_all once - assert len(cmd.mock_calls) == 1 - - # Check args - assert cmd.mock_calls[0][1] == ( - [ - "yum", - "--quiet", - "--showduplicates", - "repository-packages", - "base", - "list", - "foo", - ], - ) - # Check kwargs - assert cmd.mock_calls[0][2] == kwargs - - # Test enabling base-source and disabling updates. We should - # get two calls, one for each enabled repo. Because dict - # iteration order will vary, different Python versions will be - # do them in different orders, which is OK, but it will just - # mean that we will have to check both the first and second - # mock call both times. - cmd = MagicMock(return_value={"retcode": 0, "stdout": ""}) - with patch.dict( - yumpkg.__salt__, - {"cmd.run_all": cmd, "config.get": MagicMock(return_value=False)}, - ): - yumpkg.list_repo_pkgs( - "foo", enablerepo="base-source", disablerepo="updates" - ) - # We should have called cmd.run_all twice - assert len(cmd.mock_calls) == 2 - - for repo in ("base", "base-source"): - for index in (0, 1): - try: - # Check args - assert cmd.mock_calls[index][1] == ( - [ - "yum", - "--quiet", - "--showduplicates", - "repository-packages", - repo, - "list", - "foo", - ], - ) - # Check kwargs - assert cmd.mock_calls[index][2] == kwargs - break - except AssertionError: - continue - else: - self.fail("repo '{}' not checked".format(repo)) - - def test_list_upgrades_dnf(self): - """ - The subcommand should be "upgrades" with dnf - """ - with patch.dict(yumpkg.__context__, {"yum_bin": "dnf"}): - # with fromrepo - cmd = MagicMock(return_value={"retcode": 0, "stdout": ""}) - with patch.dict( - yumpkg.__salt__, - {"cmd.run_all": cmd, "config.get": MagicMock(return_value=False)}, - ): - yumpkg.list_upgrades(refresh=False, fromrepo="good", branch="foo") - cmd.assert_called_once_with( - [ - "dnf", - "--quiet", - "--disablerepo=*", - "--enablerepo=good", - "--branch=foo", - "list", - "upgrades", - ], - env={}, - output_loglevel="trace", - ignore_retcode=True, - python_shell=False, - ) - - # without fromrepo - cmd = MagicMock(return_value={"retcode": 0, "stdout": ""}) - with patch.dict( - yumpkg.__salt__, - {"cmd.run_all": cmd, "config.get": MagicMock(return_value=False)}, - ): - yumpkg.list_upgrades( - refresh=False, enablerepo="good", disablerepo="bad", branch="foo" - ) - cmd.assert_called_once_with( - [ - "dnf", - "--quiet", - "--disablerepo=bad", - "--enablerepo=good", - "--branch=foo", - "list", - "upgrades", - ], - env={}, - output_loglevel="trace", - ignore_retcode=True, - python_shell=False, - ) - - def test_list_upgrades_yum(self): - """ - The subcommand should be "updates" with yum - """ - # with fromrepo - cmd = MagicMock(return_value={"retcode": 0, "stdout": ""}) - with patch.dict( - yumpkg.__salt__, - {"cmd.run_all": cmd, "config.get": MagicMock(return_value=False)}, - ): - yumpkg.list_upgrades(refresh=False, fromrepo="good", branch="foo") - cmd.assert_called_once_with( - [ - "yum", - "--quiet", - "--disablerepo=*", - "--enablerepo=good", - "--branch=foo", - "list", - "updates", - ], - env={}, - output_loglevel="trace", - ignore_retcode=True, - python_shell=False, - ) - - # without fromrepo - cmd = MagicMock(return_value={"retcode": 0, "stdout": ""}) - with patch.dict( - yumpkg.__salt__, - {"cmd.run_all": cmd, "config.get": MagicMock(return_value=False)}, - ): - yumpkg.list_upgrades( - refresh=False, enablerepo="good", disablerepo="bad", branch="foo" - ) - cmd.assert_called_once_with( - [ - "yum", - "--quiet", - "--disablerepo=bad", - "--enablerepo=good", - "--branch=foo", - "list", - "updates", - ], - env={}, - output_loglevel="trace", - ignore_retcode=True, - python_shell=False, - ) - - def test_refresh_db_with_options(self): - - with patch("salt.utils.pkg.clear_rtag", Mock()): - - # With check_update=True we will do a cmd.run to run the clean_cmd, and - # then a separate cmd.retcode to check for updates. - - # with fromrepo - yum_call = MagicMock() - with patch.dict( - yumpkg.__salt__, - {"cmd.run_all": yum_call, "config.get": MagicMock(return_value=False)}, - ): - yumpkg.refresh_db(check_update=True, fromrepo="good", branch="foo") - - assert yum_call.call_count == 2 - yum_call.assert_any_call( - [ - "yum", - "--quiet", - "--assumeyes", - "clean", - "expire-cache", - "--disablerepo=*", - "--enablerepo=good", - "--branch=foo", - ], - env={}, - ignore_retcode=True, - output_loglevel="trace", - python_shell=False, - ) - yum_call.assert_any_call( - [ - "yum", - "--quiet", - "--assumeyes", - "check-update", - "--setopt=autocheck_running_kernel=false", - "--disablerepo=*", - "--enablerepo=good", - "--branch=foo", - ], - output_loglevel="trace", - env={}, - ignore_retcode=True, - python_shell=False, - ) - - # without fromrepo - yum_call = MagicMock() - with patch.dict( - yumpkg.__salt__, - {"cmd.run_all": yum_call, "config.get": MagicMock(return_value=False)}, - ): - yumpkg.refresh_db( - check_update=True, - enablerepo="good", - disablerepo="bad", - branch="foo", - ) - assert yum_call.call_count == 2 - yum_call.assert_any_call( - [ - "yum", - "--quiet", - "--assumeyes", - "clean", - "expire-cache", - "--disablerepo=bad", - "--enablerepo=good", - "--branch=foo", - ], - env={}, - ignore_retcode=True, - output_loglevel="trace", - python_shell=False, - ) - yum_call.assert_any_call( - [ - "yum", - "--quiet", - "--assumeyes", - "check-update", - "--setopt=autocheck_running_kernel=false", - "--disablerepo=bad", - "--enablerepo=good", - "--branch=foo", - ], - output_loglevel="trace", - env={}, - ignore_retcode=True, - python_shell=False, - ) - - # With check_update=False we will just do a cmd.run for the clean_cmd - - # with fromrepo - yum_call = MagicMock() - with patch.dict( - yumpkg.__salt__, - {"cmd.run_all": yum_call, "config.get": MagicMock(return_value=False)}, - ): - yumpkg.refresh_db(check_update=False, fromrepo="good", branch="foo") - assert yum_call.call_count == 1 - yum_call.assert_called_once_with( - [ - "yum", - "--quiet", - "--assumeyes", - "clean", - "expire-cache", - "--disablerepo=*", - "--enablerepo=good", - "--branch=foo", - ], - env={}, - output_loglevel="trace", - ignore_retcode=True, - python_shell=False, - ) - - # without fromrepo - yum_call = MagicMock() - with patch.dict( - yumpkg.__salt__, - {"cmd.run_all": yum_call, "config.get": MagicMock(return_value=False)}, - ): - yumpkg.refresh_db( - check_update=False, - enablerepo="good", - disablerepo="bad", - branch="foo", - ) - assert yum_call.call_count == 1 - yum_call.assert_called_once_with( - [ - "yum", - "--quiet", - "--assumeyes", - "clean", - "expire-cache", - "--disablerepo=bad", - "--enablerepo=good", - "--branch=foo", - ], - env={}, - output_loglevel="trace", - ignore_retcode=True, - python_shell=False, - ) - - def test_install_with_options(self): - parse_targets = MagicMock(return_value=({"foo": None}, "repository")) - with patch.object( - yumpkg, "list_pkgs", MagicMock(return_value={}) - ), patch.object(yumpkg, "list_holds", MagicMock(return_value=[])), patch.dict( - yumpkg.__salt__, {"pkg_resource.parse_targets": parse_targets} - ), patch( - "salt.utils.systemd.has_scope", MagicMock(return_value=False) - ): - - # with fromrepo - cmd = MagicMock(return_value={"retcode": 0}) - with patch.dict(yumpkg.__salt__, {"cmd.run_all": cmd}): - yumpkg.install( - refresh=False, - fromrepo="good", - branch="foo", - setopt="obsoletes=0,plugins=0", - ) - cmd.assert_called_once_with( - [ - "yum", - "-y", - "--disablerepo=*", - "--enablerepo=good", - "--branch=foo", - "--setopt", - "obsoletes=0", - "--setopt", - "plugins=0", - "install", - "foo", - ], - env={}, - output_loglevel="trace", - python_shell=False, - ignore_retcode=False, - redirect_stderr=True, - ) - - # without fromrepo - cmd = MagicMock(return_value={"retcode": 0}) - with patch.dict(yumpkg.__salt__, {"cmd.run_all": cmd}): - yumpkg.install( - refresh=False, - enablerepo="good", - disablerepo="bad", - branch="foo", - setopt="obsoletes=0,plugins=0", - ) - cmd.assert_called_once_with( - [ - "yum", - "-y", - "--disablerepo=bad", - "--enablerepo=good", - "--branch=foo", - "--setopt", - "obsoletes=0", - "--setopt", - "plugins=0", - "install", - "foo", - ], - env={}, - output_loglevel="trace", - python_shell=False, - ignore_retcode=False, - redirect_stderr=True, - ) - - def test_install_with_epoch(self): - """ - Tests that we properly identify a version containing an epoch as an - upgrade instead of a downgrade. - """ - name = "foo" - old = "8:3.8.12-6.n.el7" - new = "9:3.8.12-4.n.el7" - list_pkgs_mock = MagicMock( - side_effect=lambda **kwargs: { - name: [old] if kwargs.get("versions_as_list", False) else old - } - ) - 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: new}, "repository") - ), - } - full_pkg_string = "-".join((name, new[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): - - # Test yum - expected = ["yum", "-y", "install", full_pkg_string] - with patch.dict(yumpkg.__context__, {"yum_bin": "yum"}), patch.dict( - yumpkg.__grains__, {"os": "CentOS", "osrelease": 7} - ): - yumpkg.install("foo", version=new) - call = cmd_mock.mock_calls[0][1][0] - assert call == expected, call - - # Test dnf - expected = [ - "dnf", - "-y", - "--best", - "--allowerasing", - "install", - full_pkg_string, - ] - yumpkg.__context__.pop("yum_bin") - cmd_mock.reset_mock() - with patch.dict(yumpkg.__context__, {"yum_bin": "dnf"}), patch.dict( - yumpkg.__grains__, {"os": "Fedora", "osrelease": 27} - ): - yumpkg.install("foo", version=new) - call = cmd_mock.mock_calls[0][1][0] - assert call == expected, call - - @skipIf(not salt.utils.platform.is_linux(), "Only run on Linux") - def test_install_error_reporting(self): - """ - Tests that we properly report yum/dnf errors. - """ - name = "foo" - old = "8:3.8.12-6.n.el7" - new = "9:3.8.12-4.n.el7" - list_pkgs_mock = MagicMock( - side_effect=lambda **kwargs: { - name: [old] if kwargs.get("versions_as_list", False) else old - } - ) - salt_mock = { - "cmd.run_all": cmdmod.run_all, - "lowpkg.version_cmp": rpm.version_cmp, - "pkg_resource.parse_targets": MagicMock( - return_value=({name: new}, "repository") - ), - } - full_pkg_string = "-".join((name, new[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), patch.object( - yumpkg, "_yum", MagicMock(return_value="cat") - ): - - expected = { - "changes": {}, - "errors": [ - "cat: invalid option -- 'y'\n" - "Try 'cat --help' for more information." - ], - } - with pytest.raises(CommandExecutionError) as exc_info: - yumpkg.install("foo", version=new) - assert exc_info.value.info == expected, exc_info.value.info - - def test_upgrade_with_options(self): - with patch.object(yumpkg, "list_pkgs", MagicMock(return_value={})), patch( - "salt.utils.systemd.has_scope", MagicMock(return_value=False) - ): - - # with fromrepo - cmd = MagicMock(return_value={"retcode": 0}) - with patch.dict(yumpkg.__salt__, {"cmd.run_all": cmd}): - yumpkg.upgrade( - refresh=False, - fromrepo="good", - exclude="kernel*", - branch="foo", - setopt="obsoletes=0,plugins=0", - ) - cmd.assert_called_once_with( - [ - "yum", - "--quiet", - "-y", - "--disablerepo=*", - "--enablerepo=good", - "--branch=foo", - "--setopt", - "obsoletes=0", - "--setopt", - "plugins=0", - "--exclude=kernel*", - "upgrade", - ], - env={}, - output_loglevel="trace", - python_shell=False, - ) - - # without fromrepo - cmd = MagicMock(return_value={"retcode": 0}) - with patch.dict(yumpkg.__salt__, {"cmd.run_all": cmd}): - yumpkg.upgrade( - refresh=False, - enablerepo="good", - disablerepo="bad", - exclude="kernel*", - branch="foo", - setopt="obsoletes=0,plugins=0", - ) - cmd.assert_called_once_with( - [ - "yum", - "--quiet", - "-y", - "--disablerepo=bad", - "--enablerepo=good", - "--branch=foo", - "--setopt", - "obsoletes=0", - "--setopt", - "plugins=0", - "--exclude=kernel*", - "upgrade", - ], - env={}, - output_loglevel="trace", - python_shell=False, - ) - - def test_info_installed_with_all_versions(self): - """ - Test the return information of all versions for the named package(s), installed on the system. - - :return: - """ - run_out = { - "virgo-dummy": [ - { - "build_date": "2015-07-09T10:55:19Z", - "vendor": "openSUSE Build Service", - "description": "This is the Virgo dummy package used for testing SUSE Manager", - "license": "GPL-2.0", - "build_host": "sheep05", - "url": "http://www.suse.com", - "build_date_time_t": 1436432119, - "relocations": "(not relocatable)", - "source_rpm": "virgo-dummy-1.0-1.1.src.rpm", - "install_date": "2016-02-23T16:31:57Z", - "install_date_time_t": 1456241517, - "summary": "Virgo dummy package", - "version": "1.0", - "signature": "DSA/SHA1, Thu Jul 9 08:55:33 2015, Key ID 27fa41bd8a7c64f9", - "release": "1.1", - "group": "Applications/System", - "arch": "i686", - "size": "17992", - }, - { - "build_date": "2015-07-09T10:15:19Z", - "vendor": "openSUSE Build Service", - "description": "This is the Virgo dummy package used for testing SUSE Manager", - "license": "GPL-2.0", - "build_host": "sheep05", - "url": "http://www.suse.com", - "build_date_time_t": 1436432119, - "relocations": "(not relocatable)", - "source_rpm": "virgo-dummy-1.0-1.1.src.rpm", - "install_date": "2016-02-23T16:31:57Z", - "install_date_time_t": 14562415127, - "summary": "Virgo dummy package", - "version": "1.0", - "signature": "DSA/SHA1, Thu Jul 9 08:55:33 2015, Key ID 27fa41bd8a7c64f9", - "release": "1.1", - "group": "Applications/System", - "arch": "x86_64", - "size": "13124", - }, - ], - "libopenssl1_0_0": [ - { - "build_date": "2015-11-04T23:20:34Z", - "vendor": "SUSE LLC ", - "description": "The OpenSSL Project is a collaborative effort.", - "license": "OpenSSL", - "build_host": "sheep11", - "url": "https://www.openssl.org/", - "build_date_time_t": 1446675634, - "relocations": "(not relocatable)", - "source_rpm": "openssl-1.0.1i-34.1.src.rpm", - "install_date": "2016-02-23T16:31:35Z", - "install_date_time_t": 1456241495, - "summary": "Secure Sockets and Transport Layer Security", - "version": "1.0.1i", - "signature": "RSA/SHA256, Wed Nov 4 22:21:34 2015, Key ID 70af9e8139db7c82", - "release": "34.1", - "group": "Productivity/Networking/Security", - "packager": "https://www.suse.com/", - "arch": "x86_64", - "size": "2576912", - } - ], - } - with patch.dict( - yumpkg.__salt__, {"lowpkg.info": MagicMock(return_value=run_out)} - ): - installed = yumpkg.info_installed(all_versions=True) - # Test overall products length - self.assertEqual(len(installed), 2) - - # Test multiple versions for the same package - for pkg_name, pkg_info_list in installed.items(): - self.assertEqual( - len(pkg_info_list), 2 if pkg_name == "virgo-dummy" else 1 - ) - for info in pkg_info_list: - self.assertTrue(info["arch"] in ("x86_64", "i686")) - - def test_pkg_hold_yum(self): - """ - Tests that we properly identify versionlock plugin when using yum - for RHEL/CentOS 7 and Fedora < 22 - """ - - # Test RHEL/CentOS 7 - list_pkgs_mock = { - "yum-plugin-versionlock": "0:1.0.0-0.n.el7", - "yum-versionlock": "0:1.0.0-0.n.el7", - } - - cmd = MagicMock(return_value={"retcode": 0}) - with patch.object( - yumpkg, "list_pkgs", MagicMock(return_value=list_pkgs_mock) - ), patch.object(yumpkg, "list_holds", MagicMock(return_value=[])), patch.dict( - yumpkg.__salt__, {"cmd.run_all": cmd} - ), patch( - "salt.utils.systemd.has_scope", MagicMock(return_value=False) - ): - yumpkg.hold("foo") - cmd.assert_called_once_with( - ["yum", "versionlock", "foo"], - env={}, - output_loglevel="trace", - python_shell=False, - ) - - # Test Fedora 20 - cmd = MagicMock(return_value={"retcode": 0}) - with patch.dict(yumpkg.__context__, {"yum_bin": "yum"}), patch.dict( - yumpkg.__grains__, {"os": "Fedora", "osrelease": 20} - ), patch.object( - yumpkg, "list_pkgs", MagicMock(return_value=list_pkgs_mock) - ), patch.object( - yumpkg, "list_holds", MagicMock(return_value=[]) - ), patch.dict( - yumpkg.__salt__, {"cmd.run_all": cmd} - ), patch( - "salt.utils.systemd.has_scope", MagicMock(return_value=False) - ): - yumpkg.hold("foo") - cmd.assert_called_once_with( - ["yum", "versionlock", "foo"], - env={}, - output_loglevel="trace", - python_shell=False, - ) - - def test_pkg_hold_tdnf(self): - """ - Tests that we raise a SaltInvocationError if we try to use - hold-related functions on Photon OS. - """ - with patch.dict(yumpkg.__context__, {"yum_bin": "tdnf"}): - self.assertRaises(SaltInvocationError, yumpkg.hold, "foo") - - def test_pkg_hold_dnf(self): - """ - Tests that we properly identify versionlock plugin when using dnf - for RHEL/CentOS 8 and Fedora >= 22 - """ - - # Test RHEL/CentOS 8 - list_pkgs_mock = { - "python2-dnf-plugin-versionlock": "0:1.0.0-0.n.el8", - "python3-dnf-plugin-versionlock": "0:1.0.0-0.n.el8", - } - - yumpkg.__context__.pop("yum_bin") - cmd = MagicMock(return_value={"retcode": 0}) - with patch.dict(yumpkg.__context__, {"yum_bin": "dnf"}), patch.dict( - yumpkg.__grains__, {"osmajorrelease": 8} - ), patch.object( - yumpkg, "list_pkgs", MagicMock(return_value=list_pkgs_mock) - ), patch.object( - yumpkg, "list_holds", MagicMock(return_value=[]) - ), patch.dict( - yumpkg.__salt__, {"cmd.run_all": cmd} - ), patch( - "salt.utils.systemd.has_scope", MagicMock(return_value=False) - ): - yumpkg.hold("foo") - cmd.assert_called_once_with( - ["dnf", "versionlock", "foo"], - env={}, - output_loglevel="trace", - python_shell=False, - ) - - # Test Fedora 26+ - cmd = MagicMock(return_value={"retcode": 0}) - with patch.dict(yumpkg.__context__, {"yum_bin": "dnf"}), patch.dict( - yumpkg.__grains__, {"os": "Fedora", "osrelease": 26} - ), patch.object( - yumpkg, "list_pkgs", MagicMock(return_value=list_pkgs_mock) - ), patch.object( - yumpkg, "list_holds", MagicMock(return_value=[]) - ), patch.dict( - yumpkg.__salt__, {"cmd.run_all": cmd} - ), patch( - "salt.utils.systemd.has_scope", MagicMock(return_value=False) - ): - yumpkg.hold("foo") - cmd.assert_called_once_with( - ["dnf", "versionlock", "foo"], - env={}, - output_loglevel="trace", - python_shell=False, - ) - - # Test Fedora 22-25 - list_pkgs_mock = { - "python-dnf-plugins-extras-versionlock": "0:1.0.0-0.n.el8", - "python3-dnf-plugins-extras-versionlock": "0:1.0.0-0.n.el8", - } - - cmd = MagicMock(return_value={"retcode": 0}) - with patch.dict(yumpkg.__context__, {"yum_bin": "dnf"}), patch.dict( - yumpkg.__grains__, {"os": "Fedora", "osrelease": 25} - ), patch.object( - yumpkg, "list_pkgs", MagicMock(return_value=list_pkgs_mock) - ), patch.object( - yumpkg, "list_holds", MagicMock(return_value=[]) - ), patch.dict( - yumpkg.__salt__, {"cmd.run_all": cmd} - ), patch( - "salt.utils.systemd.has_scope", MagicMock(return_value=False) - ): - yumpkg.hold("foo") - cmd.assert_called_once_with( - ["dnf", "versionlock", "foo"], - env={}, - output_loglevel="trace", - python_shell=False, - ) - - @skipIf(not yumpkg.HAS_YUM, "Could not import yum") - def test_yum_base_error(self): - with patch("yum.YumBase") as mock_yum_yumbase: - mock_yum_yumbase.side_effect = CommandExecutionError - with pytest.raises(CommandExecutionError): - yumpkg._get_yum_config() - - def test_group_info(self): - """ - Test yumpkg.group_info parsing - """ - expected = { - "conditional": [], - "default": ["qgnomeplatform", "xdg-desktop-portal-gtk"], - "description": "GNOME is a highly intuitive and user friendly desktop environment.", - "group": "GNOME", - "id": "gnome-desktop", - "mandatory": [ - "NetworkManager-libreswan-gnome", - "PackageKit-command-not-found", - "PackageKit-gtk3-module", - "abrt-desktop", - "at-spi2-atk", - "at-spi2-core", - "avahi", - "baobab", - "caribou", - "caribou-gtk2-module", - "caribou-gtk3-module", - "cheese", - "chrome-gnome-shell", - "compat-cheese314", - "control-center", - "dconf", - "empathy", - "eog", - "evince", - "evince-nautilus", - "file-roller", - "file-roller-nautilus", - "firewall-config", - "firstboot", - "fprintd-pam", - "gdm", - "gedit", - "glib-networking", - "gnome-bluetooth", - "gnome-boxes", - "gnome-calculator", - "gnome-classic-session", - "gnome-clocks", - "gnome-color-manager", - "gnome-contacts", - "gnome-dictionary", - "gnome-disk-utility", - "gnome-font-viewer", - "gnome-getting-started-docs", - "gnome-icon-theme", - "gnome-icon-theme-extras", - "gnome-icon-theme-symbolic", - "gnome-initial-setup", - "gnome-packagekit", - "gnome-packagekit-updater", - "gnome-screenshot", - "gnome-session", - "gnome-session-xsession", - "gnome-settings-daemon", - "gnome-shell", - "gnome-software", - "gnome-system-log", - "gnome-system-monitor", - "gnome-terminal", - "gnome-terminal-nautilus", - "gnome-themes-standard", - "gnome-tweak-tool", - "gnome-user-docs", - "gnome-weather", - "gucharmap", - "gvfs-afc", - "gvfs-afp", - "gvfs-archive", - "gvfs-fuse", - "gvfs-goa", - "gvfs-gphoto2", - "gvfs-mtp", - "gvfs-smb", - "initial-setup-gui", - "libcanberra-gtk2", - "libcanberra-gtk3", - "libproxy-mozjs", - "librsvg2", - "libsane-hpaio", - "metacity", - "mousetweaks", - "nautilus", - "nautilus-sendto", - "nm-connection-editor", - "orca", - "redhat-access-gui", - "sane-backends-drivers-scanners", - "seahorse", - "setroubleshoot", - "sushi", - "totem", - "totem-nautilus", - "vinagre", - "vino", - "xdg-user-dirs-gtk", - "yelp", - ], - "optional": [ - "", - "alacarte", - "dconf-editor", - "dvgrab", - "fonts-tweak-tool", - "gconf-editor", - "gedit-plugins", - "gnote", - "libappindicator-gtk3", - "seahorse-nautilus", - "seahorse-sharing", - "vim-X11", - "xguest", - ], - "type": "package group", - } - cmd_out = """Group: GNOME - Group-Id: gnome-desktop - Description: GNOME is a highly intuitive and user friendly desktop environment. - Mandatory Packages: - =NetworkManager-libreswan-gnome - =PackageKit-command-not-found - =PackageKit-gtk3-module - abrt-desktop - =at-spi2-atk - =at-spi2-core - =avahi - =baobab - -caribou - -caribou-gtk2-module - -caribou-gtk3-module - =cheese - =chrome-gnome-shell - =compat-cheese314 - =control-center - =dconf - =empathy - =eog - =evince - =evince-nautilus - =file-roller - =file-roller-nautilus - =firewall-config - =firstboot - fprintd-pam - =gdm - =gedit - =glib-networking - =gnome-bluetooth - =gnome-boxes - =gnome-calculator - =gnome-classic-session - =gnome-clocks - =gnome-color-manager - =gnome-contacts - =gnome-dictionary - =gnome-disk-utility - =gnome-font-viewer - =gnome-getting-started-docs - =gnome-icon-theme - =gnome-icon-theme-extras - =gnome-icon-theme-symbolic - =gnome-initial-setup - =gnome-packagekit - =gnome-packagekit-updater - =gnome-screenshot - =gnome-session - =gnome-session-xsession - =gnome-settings-daemon - =gnome-shell - =gnome-software - =gnome-system-log - =gnome-system-monitor - =gnome-terminal - =gnome-terminal-nautilus - =gnome-themes-standard - =gnome-tweak-tool - =gnome-user-docs - =gnome-weather - =gucharmap - =gvfs-afc - =gvfs-afp - =gvfs-archive - =gvfs-fuse - =gvfs-goa - =gvfs-gphoto2 - =gvfs-mtp - =gvfs-smb - initial-setup-gui - =libcanberra-gtk2 - =libcanberra-gtk3 - =libproxy-mozjs - =librsvg2 - =libsane-hpaio - =metacity - =mousetweaks - =nautilus - =nautilus-sendto - =nm-connection-editor - =orca - -redhat-access-gui - =sane-backends-drivers-scanners - =seahorse - =setroubleshoot - =sushi - =totem - =totem-nautilus - =vinagre - =vino - =xdg-user-dirs-gtk - =yelp - Default Packages: - =qgnomeplatform - =xdg-desktop-portal-gtk - Optional Packages: - alacarte - dconf-editor - dvgrab - fonts-tweak-tool - gconf-editor - gedit-plugins - gnote - libappindicator-gtk3 - seahorse-nautilus - seahorse-sharing - vim-X11 - xguest - """ - with patch.dict( - yumpkg.__salt__, {"cmd.run_stdout": MagicMock(return_value=cmd_out)} - ): - info = yumpkg.group_info("@gnome-desktop") - self.assertDictEqual(info, expected) - - def test_get_repo_with_existent_repo(self): - """ - Test get_repo with an existent repository - Expected return is a populated dictionary - """ - repo = "base-source" - kwargs = { - "baseurl": "http://vault.centos.org/centos/$releasever/os/Source/", - "gpgkey": "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7", - "name": "CentOS-$releasever - Base Sources", - "enabled": True, - } - parse_repo_file_return = ( - "", - { - "base-source": { - "baseurl": "http://vault.centos.org/centos/$releasever/os/Source/", - "gpgkey": "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7", - "name": "CentOS-$releasever - Base Sources", - "enabled": "1", - } - }, - ) - expected = { - "baseurl": "http://vault.centos.org/centos/$releasever/os/Source/", - "gpgkey": "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7", - "name": "CentOS-$releasever - Base Sources", - "enabled": "1", - } - patch_list_repos = patch.object( - yumpkg, "list_repos", autospec=True, return_value=LIST_REPOS - ) - patch_parse_repo_file = patch.object( - yumpkg, - "_parse_repo_file", - autospec=True, - return_value=parse_repo_file_return, - ) - - with patch_list_repos, patch_parse_repo_file: - ret = yumpkg.get_repo(repo, **kwargs) - assert ret == expected, ret - - def test_get_repo_with_non_existent_repo(self): - """ - Test get_repo with an non existent repository - Expected return is an empty dictionary - """ - repo = "non-existent-repository" - kwargs = { - "baseurl": "http://fake.centos.org/centos/$releasever/os/Non-Existent/", - "gpgkey": "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7", - "name": "CentOS-$releasever - Non-Existent Repository", - "enabled": True, - } - expected = {} - patch_list_repos = patch.object( - yumpkg, "list_repos", autospec=True, return_value=LIST_REPOS - ) - - with patch_list_repos: - ret = yumpkg.get_repo(repo, **kwargs) - assert ret == expected, ret - - -@skipIf(pytest is None, "PyTest is missing") -class YumUtilsTestCase(TestCase, LoaderModuleMockMixin): - """ - Yum/Dnf utils tests. - """ - - def setup_loader_modules(self): - return { - yumpkg: { - "__context__": {"yum_bin": "fake-yum"}, - "__grains__": { - "osarch": "x86_64", - "os_family": "RedHat", - "osmajorrelease": 7, - }, - } - } - - def test_call_yum_default(self): - """ - Call default Yum/Dnf. - :return: - """ - with patch.dict( - yumpkg.__salt__, - {"cmd.run_all": MagicMock(), "config.get": MagicMock(return_value=False)}, - ): - yumpkg._call_yum(["-y", "--do-something"]) # pylint: disable=W0106 - yumpkg.__salt__["cmd.run_all"].assert_called_once_with( - ["fake-yum", "-y", "--do-something"], - env={}, - output_loglevel="trace", - python_shell=False, - ) - - @patch("salt.utils.systemd.has_scope", MagicMock(return_value=True)) - def test_call_yum_in_scope(self): - """ - Call Yum/Dnf within the scope. - :return: - """ - with patch.dict( - yumpkg.__salt__, - {"cmd.run_all": MagicMock(), "config.get": MagicMock(return_value=True)}, - ): - yumpkg._call_yum(["-y", "--do-something"]) # pylint: disable=W0106 - yumpkg.__salt__["cmd.run_all"].assert_called_once_with( - ["systemd-run", "--scope", "fake-yum", "-y", "--do-something"], - env={}, - output_loglevel="trace", - python_shell=False, - ) - - def test_call_yum_with_kwargs(self): - """ - Call Yum/Dnf with the optinal keyword arguments. - :return: - """ - with patch.dict( - yumpkg.__salt__, - {"cmd.run_all": MagicMock(), "config.get": MagicMock(return_value=False)}, - ): - yumpkg._call_yum( - ["-y", "--do-something"], - python_shell=True, - output_loglevel="quiet", - ignore_retcode=False, - username="Darth Vader", - ) # pylint: disable=W0106 - yumpkg.__salt__["cmd.run_all"].assert_called_once_with( - ["fake-yum", "-y", "--do-something"], - env={}, - ignore_retcode=False, - output_loglevel="quiet", - python_shell=True, - username="Darth Vader", - ) - - @skipIf(not salt.utils.systemd.booted(), "Requires systemd") - @patch("salt.modules.yumpkg._yum", Mock(return_value="dnf")) - def test_services_need_restart(self): - """ - Test that dnf needs-restarting output is parsed and - salt.utils.systemd.pid_to_service is called as expected. - """ - expected = ["firewalld", "salt-minion"] - - dnf_mock = Mock( - return_value="123 : /usr/bin/firewalld\n456 : /usr/bin/salt-minion\n" - ) - systemd_mock = Mock(side_effect=["firewalld", "salt-minion"]) - with patch.dict(yumpkg.__salt__, {"cmd.run_stdout": dnf_mock}), patch( - "salt.utils.systemd.pid_to_service", systemd_mock - ): - assert sorted(yumpkg.services_need_restart()) == expected - systemd_mock.assert_has_calls([call("123"), call("456")]) - - @patch("salt.modules.yumpkg._yum", Mock(return_value="dnf")) - def test_services_need_restart_requires_systemd(self): - """Test that yumpkg.services_need_restart raises an error if systemd is unavailable.""" - with patch("salt.utils.systemd.booted", Mock(return_value=False)): - pytest.raises(CommandExecutionError, yumpkg.services_need_restart) - - @patch("salt.modules.yumpkg._yum", Mock(return_value="yum")) - def test_services_need_restart_requires_dnf(self): - """Test that yumpkg.services_need_restart raises an error if DNF is unavailable.""" - pytest.raises(CommandExecutionError, yumpkg.services_need_restart) diff --git a/tests/unit/modules/test_zypperpkg.py b/tests/unit/modules/test_zypperpkg.py index 78fe226914..2440954d89 100644 --- a/tests/unit/modules/test_zypperpkg.py +++ b/tests/unit/modules/test_zypperpkg.py @@ -12,11 +12,9 @@ import salt.modules.pkg_resource as pkg_resource import salt.modules.zypperpkg as zypper import salt.utils.files import salt.utils.pkg -from salt.exceptions import CommandExecutionError -from salt.ext import six -from salt.ext.six.moves import configparser +from salt.exceptions import CommandExecutionError, SaltInvocationError from tests.support.mixins import LoaderModuleMockMixin -from tests.support.mock import MagicMock, Mock, call, mock_open, patch +from tests.support.mock import MagicMock, Mock, call, patch from tests.support.unit import TestCase @@ -2441,3 +2439,41 @@ pattern() = package-c""" python_shell=False, env={"ZYPP_READONLY_HACK": "1"}, ) + self.assertEqual(zypper.__context__, {"pkg.other_data": None}) + + def test_get_repo_keys(self): + salt_mock = {"lowpkg.list_gpg_keys": MagicMock(return_value=True)} + with patch.dict(zypper.__salt__, salt_mock): + self.assertTrue(zypper.get_repo_keys(info=True, root="/mnt")) + salt_mock["lowpkg.list_gpg_keys"].assert_called_once_with(True, "/mnt") + + def test_add_repo_key_fail(self): + with self.assertRaises(SaltInvocationError): + zypper.add_repo_key() + + with self.assertRaises(SaltInvocationError): + zypper.add_repo_key(path="path", text="text") + + def test_add_repo_key_path(self): + salt_mock = { + "cp.cache_file": MagicMock(return_value="path"), + "lowpkg.import_gpg_key": MagicMock(return_value=True), + } + with patch("salt.utils.files.fopen", mock_open(read_data="text")), patch.dict( + zypper.__salt__, salt_mock + ): + self.assertTrue(zypper.add_repo_key(path="path", root="/mnt")) + salt_mock["cp.cache_file"].assert_called_once_with("path", "base") + salt_mock["lowpkg.import_gpg_key"].assert_called_once_with("text", "/mnt") + + def test_add_repo_key_text(self): + salt_mock = {"lowpkg.import_gpg_key": MagicMock(return_value=True)} + with patch.dict(zypper.__salt__, salt_mock): + self.assertTrue(zypper.add_repo_key(text="text", root="/mnt")) + salt_mock["lowpkg.import_gpg_key"].assert_called_once_with("text", "/mnt") + + def test_del_repo_key(self): + salt_mock = {"lowpkg.remove_gpg_key": MagicMock(return_value=True)} + with patch.dict(zypper.__salt__, salt_mock): + self.assertTrue(zypper.del_repo_key(keyid="keyid", root="/mnt")) + salt_mock["lowpkg.remove_gpg_key"].assert_called_once_with("keyid", "/mnt") -- 2.33.0