From 118133a734987e4d2c01ab9775525b0152adc780 Mon Sep 17 00:00:00 2001 From: Inessa Vasilevskaya Date: Thu, 14 Dec 2023 14:03:25 +0100 Subject: [PATCH 51/60] Introduce two functions for listing which packages are leapp packages. * rpms.get_leapp_packages() library function returns the leapp packages which ship the leapp components. * rpms.get_leapp_deps_packages() library function returns the leapp deps meta packages which list the requirements of the associated leapp packages. This function can be used as leapp-installation-packages-getter. Refactoring of other actors using it will be done later. --- repos/system_upgrade/common/libraries/rpms.py | 139 ++++++++++++++++++ .../common/libraries/tests/test_rpms.py | 67 ++++++++- 2 files changed, 205 insertions(+), 1 deletion(-) diff --git a/repos/system_upgrade/common/libraries/rpms.py b/repos/system_upgrade/common/libraries/rpms.py index 6a5e8637..2890240f 100644 --- a/repos/system_upgrade/common/libraries/rpms.py +++ b/repos/system_upgrade/common/libraries/rpms.py @@ -1,7 +1,47 @@ from leapp.libraries import stdlib +from leapp.libraries.common.config.version import get_source_major_version from leapp.models import InstalledRPM +class LeappComponents(object): + """ + Supported component values to be used with get_packages_function: + * FRAMEWORK - the core of the leapp project: the leapp executable and + associated leapp libraries + * REPOSITORY - the leapp-repository project + * COCKPIT - the cockpit-leapp project + * TOOLS - miscellaneous tooling like snactor + """ + FRAMEWORK = 'framework' + REPOSITORY = 'repository' + COCKPIT = 'cockpit' + TOOLS = 'tools' + + +_LEAPP_PACKAGES_MAP = { + LeappComponents.FRAMEWORK: {'7': {'pkgs': ['leapp', 'python2-leapp'], + 'deps': ['leapp-deps']}, + '8': {'pkgs': ['leapp', 'python3-leapp'], + 'deps': ['leapp-deps']} + }, + LeappComponents.REPOSITORY: {'7': {'pkgs': ['leapp-upgrade-el7toel8'], + 'deps': ['leapp-upgrade-el7toel8-deps']}, + '8': {'pkgs': ['leapp-upgrade-el8toel9'], + 'deps': ['leapp-upgrade-el8toel9-deps']} + }, + LeappComponents.COCKPIT: {'7': {'pkgs': ['cockpit-leapp']}, + '8': {'pkgs': ['cockpit-leapp']} + }, + LeappComponents.TOOLS: {'7': {'pkgs': ['snactor']}, + '8': {'pkgs': ['snactor']} + } + } + +GET_LEAPP_PACKAGES_DEFAULT_COMPONENTS = frozenset((LeappComponents.FRAMEWORK, + LeappComponents.REPOSITORY, + LeappComponents.TOOLS)) + + def get_installed_rpms(): rpm_cmd = [ '/bin/rpm', @@ -114,3 +154,102 @@ def check_file_modification(config): """ output = _read_rpm_modifications(config) return _parse_config_modification(output, config) + + +def _get_leapp_packages_of_type(major_version, component, type_='pkgs'): + """ + Private implementation of get_leapp_packages() and get_leapp_deps_packages(). + + :param major_version: Same as for :func:`get_leapp_packages` and + :func:`get_leapp_deps_packages` + :param component: Same as for :func:`get_leapp_packages` and :func:`get_leapp_deps_packages` + :param type_: Either "pkgs" or "deps". Determines which set of packages we're looking for. + Corresponds to the keys in the `_LEAPP_PACKAGES_MAP`. + + Retrieving the set of leapp and leapp-deps packages only differs in which key is used to + retrieve the packages from _LEAPP_PACKAGES_MAP. This function abstracts that difference. + """ + res = set() + + major_versions = [major_version] if isinstance(major_version, str) else major_version + if not major_versions: + # No major_version of interest specified -> treat as if only current source system version + # requested + major_versions = [get_source_major_version()] + + components = [component] if isinstance(component, str) else component + if not components: + error_msg = ("At least one component must be specified when calling this" + " function, available choices are {choices}".format( + choices=sorted(_LEAPP_PACKAGES_MAP.keys())) + ) + raise ValueError(error_msg) + + for comp in components: + for a_major_version in major_versions: + if comp not in _LEAPP_PACKAGES_MAP: + error_msg = "The requested component {comp} is unknown, available choices are {choices}".format( + comp=component, choices=sorted(_LEAPP_PACKAGES_MAP.keys())) + raise ValueError(error_msg) + + if a_major_version not in _LEAPP_PACKAGES_MAP[comp]: + error_msg = "The requested major_version {ver} is unknown, available choices are {choices}".format( + ver=a_major_version, choices=sorted(_LEAPP_PACKAGES_MAP[comp].keys())) + raise ValueError(error_msg) + + # All went well otherwise, get the data + res.update(_LEAPP_PACKAGES_MAP[comp][a_major_version].get(type_, [])) + + return sorted(res) + + +def get_leapp_packages(major_version=None, component=GET_LEAPP_PACKAGES_DEFAULT_COMPONENTS): + """ + Get list of leapp packages. + + :param major_version: a list or string specifying major_versions. If not defined then current + system_version will be used. + :param component: a list or a single enum value specifying leapp components + (use enum :class: LeappComponents) If defined then only packages related to the specific + component(s) will be returned. + The default set of components is in `GET_LEAPP_PACKAGES_DEFAULT_COMPONENTS` and + simple modifications of the default can be achieved with code like: + + .. code-block:: python + get_leapp_packages( + component=GET_LEAPP_PACKAGES_DEFAULT_COMPONENTS.difference( + [LeappComponents.TOOLS] + )) + + :raises ValueError: if a requested component or major_version doesn't exist. + + .. note:: + Call :func:`get_leapp_dep_packages` as well if you also need the deps metapackages. + Those packages determine which RPMs need to be installed for leapp to function. + They aren't just Requires on the base leapp and leapp-repository RPMs because they + need to be switched from the old system_version's to the new ones at a different + point in the upgrade than the base RPMs. + """ + return _get_leapp_packages_of_type(major_version, component, type_="pkgs") + + +def get_leapp_dep_packages(major_version=None, component=GET_LEAPP_PACKAGES_DEFAULT_COMPONENTS): + """ + Get list of leapp dep metapackages. + + :param major_version: a list or string specifying major_versions. If not defined then current + system_version will be used. + :param component: a list or a single enum value specifying leapp components + (use enum :class: LeappComponents) If defined then only packages related to the specific + component(s) will be returned. + The default set of components is in `GET_LEAPP_PACKAGES_DEFAULT_COMPONENTS` and + simple modifications of the default can be achieved with code like: + + .. code-block:: python + get_leapp_packages( + component=GET_LEAPP_PACKAGES_DEFAULT_COMPONENTS.difference( + [LeappComponents.TOOLS] + )) + :raises ValueError: if a requested component or major_version doesn't exist. + """ + return _get_leapp_packages_of_type(major_version, component, type_="deps") diff --git a/repos/system_upgrade/common/libraries/tests/test_rpms.py b/repos/system_upgrade/common/libraries/tests/test_rpms.py index 39a32dcb..955ab05c 100644 --- a/repos/system_upgrade/common/libraries/tests/test_rpms.py +++ b/repos/system_upgrade/common/libraries/tests/test_rpms.py @@ -1,4 +1,8 @@ -from leapp.libraries.common.rpms import _parse_config_modification +import pytest + +from leapp.libraries.common.rpms import _parse_config_modification, get_leapp_dep_packages, get_leapp_packages +from leapp.libraries.common.testutils import CurrentActorMocked +from leapp.libraries.stdlib import api def test_parse_config_modification(): @@ -30,3 +34,64 @@ def test_parse_config_modification(): "S.5....T. c /etc/ssh/sshd_config", ] assert _parse_config_modification(data, "/etc/ssh/sshd_config") + + +@pytest.mark.parametrize('major_version,component,result', [ + (None, None, ['leapp', 'python3-leapp', 'leapp-upgrade-el8toel9', 'snactor']), + ('7', None, ['leapp', 'python2-leapp', 'leapp-upgrade-el7toel8', 'snactor']), + (['7', '8'], None, ['leapp', 'python2-leapp', 'leapp-upgrade-el7toel8', + 'python3-leapp', 'leapp-upgrade-el8toel9', 'snactor']), + ('8', 'framework', ['leapp', 'python3-leapp']), + ]) +def test_get_leapp_packages(major_version, component, result, monkeypatch): + monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(arch='x86_64', src_ver='8.9', dst_ver='9.3')) + + kwargs = {} + if major_version: + kwargs["major_version"] = major_version + if component: + kwargs["component"] = component + + assert set(get_leapp_packages(** kwargs)) == set(result) + + +@pytest.mark.parametrize('major_version,component,result', [ + ('8', 'nosuchcomponent', + (ValueError, + r"component nosuchcomponent is unknown, available choices are \['cockpit', 'framework', 'repository', 'tools']") + ), + ('nosuchversion', "framework", + (ValueError, r"major_version nosuchversion is unknown, available choices are \['7', '8']")), + ('nosuchversion', False, + (ValueError, r"At least one component must be specified when calling this function," + r" available choices are \['cockpit', 'framework', 'repository', 'tools']")), +]) +def test_get_leapp_packages_errors(major_version, component, result, monkeypatch): + monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(arch='x86_64', src_ver='8.9', dst_ver='9.3')) + + kwargs = {} + if major_version: + kwargs["major_version"] = major_version + if component is not None: + kwargs["component"] = component + + exc_type, exc_msg = result + with pytest.raises(exc_type, match=exc_msg): + get_leapp_packages(**kwargs) + + +@pytest.mark.parametrize('major_version,component,result', [ + (None, None, ['leapp-deps', 'leapp-upgrade-el8toel9-deps']), + ('8', 'framework', ['leapp-deps']), + ('7', 'tools', []), +]) +def test_get_leapp_dep_packages(major_version, component, result, monkeypatch): + monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(arch='x86_64', src_ver='8.9', dst_ver='9.3')) + + kwargs = {} + if major_version: + kwargs["major_version"] = major_version + if component: + kwargs["component"] = component + + assert frozenset(get_leapp_dep_packages(**kwargs)) == frozenset(result) -- 2.43.0