From ff0f962147fc3af6cfd78f6251ddaa8cf94705c6 Mon Sep 17 00:00:00 2001 From: Matej Matuska Date: Thu, 18 Sep 2025 19:44:57 +0200 Subject: [PATCH 57/69] commands: Get target version based on target distro Jira: RHEL-110563 --- commands/command_utils.py | 48 ++++++++++++++------- commands/tests/test_upgrade_paths.py | 63 +++++++++++++++++++++------- 2 files changed, 80 insertions(+), 31 deletions(-) diff --git a/commands/command_utils.py b/commands/command_utils.py index 647e7b44..31125da5 100644 --- a/commands/command_utils.py +++ b/commands/command_utils.py @@ -62,9 +62,9 @@ def assert_version_format(version_str, desired_format, version_kind): """ if not re.match(desired_format.regex, version_str): error_str = ( - 'Unexpected format of target version: {0}. The required format is \'{1}\'.' - ) - raise CommandError(error_str.format(version_str, desired_format.human_readable)) + "Unexpected format of {} version: {}. The required format is '{}'." + ).format(version_kind.value, version_str, desired_format.human_readable) + raise CommandError(error_str) def get_major_version_from_a_valid_version(version): @@ -165,7 +165,17 @@ def get_target_versions_from_config(src_version_id, distro, flavor): return upgrade_paths_map.get(distro, {}).get(flavor, {}).get(src_version_id, []) -def get_supported_target_versions(flavour=get_upgrade_flavour()): +def get_virtual_version_from_config(src_version_id, distro): + """ + Retrieve the virtual version for the given version from upgrade_paths_map. + + :return: The virtual version or None if no match. + """ + upgrade_paths_map = get_upgrade_paths_config() + return upgrade_paths_map.get(distro, {}).get('_virtual_versions').get(src_version_id) + + +def get_supported_target_versions(target_distro, flavour=get_upgrade_flavour()): """ Return a list of supported target versions for the given `flavour` of upgrade. The default value for `flavour` is `default`. @@ -173,26 +183,30 @@ def get_supported_target_versions(flavour=get_upgrade_flavour()): os_release_contents = _retrieve_os_release_contents() current_version_id = os_release_contents.get('VERSION_ID', '') - distro_id = os_release_contents.get('ID', '') + source_distro = os_release_contents.get('ID', '') # We want to guarantee our actors that if they see 'centos'/'rhel'/... # then they will always see expected version format - expected_version_format = _DISTRO_VERSION_FORMATS.get(distro_id, VersionFormats.MAJOR_MINOR).value - assert_version_format(current_version_id, expected_version_format, _VersionKind.SOURCE) + expected_version_format = _DISTRO_VERSION_FORMATS.get(source_distro, VersionFormats.MAJOR_MINOR) + assert_version_format(current_version_id, expected_version_format.value, _VersionKind.SOURCE) + if source_distro == 'centos' and target_distro != 'centos': + # when upconverting from centos, we need to lookup by virtual version + current_version_id = get_virtual_version_from_config(current_version_id, source_distro) - target_versions = get_target_versions_from_config(current_version_id, distro_id, flavour) + target_versions = get_target_versions_from_config(current_version_id, target_distro, flavour) if not target_versions: # If we cannot find a particular major.minor version in the map, # we fallback to pick a target version just based on a major version. - # This can happen for example when testing not yet released versions + # This can happen for example when testing not yet released versions. + # But also removes the need to handle virtual versions on X->centos upgrades. major_version = get_major_version_from_a_valid_version(current_version_id) - target_versions = get_target_versions_from_config(major_version, distro_id, flavour) + target_versions = get_target_versions_from_config(major_version, target_distro, flavour) return target_versions -def get_target_version(flavour): - target_versions = get_supported_target_versions(flavour) +def get_target_version(flavour, target_distro): + target_versions = get_supported_target_versions(target_distro, flavour) return target_versions[-1] if target_versions else None @@ -214,13 +228,15 @@ def get_target_release(args): env_version_override = os.getenv('LEAPP_DEVEL_TARGET_RELEASE') target_ver = env_version_override or args.target + target_distro_id = os.getenv('LEAPP_TARGET_OS') if target_ver: - distro_id = get_distro_id() - expected_version_format = _DISTRO_VERSION_FORMATS.get(distro_id, VersionFormats.MAJOR_MINOR).value - assert_version_format(target_ver, expected_version_format, _VersionKind.TARGET) + expected_version_format = _DISTRO_VERSION_FORMATS.get( + target_distro_id, VersionFormats.MAJOR_MINOR + ) + assert_version_format(target_ver, expected_version_format.value, _VersionKind.TARGET) return (target_ver, flavor) - return (get_target_version(flavor), flavor) + return (get_target_version(flavor, target_distro_id), flavor) def set_resource_limits(): diff --git a/commands/tests/test_upgrade_paths.py b/commands/tests/test_upgrade_paths.py index 9bdf5792..95e6519a 100644 --- a/commands/tests/test_upgrade_paths.py +++ b/commands/tests/test_upgrade_paths.py @@ -8,26 +8,54 @@ from leapp.cli.commands import command_utils from leapp.exceptions import CommandError -@mock.patch("leapp.cli.commands.command_utils.get_upgrade_paths_config", - return_value={'rhel': {"default": {"7.9": ["8.4"], "8.6": ["9.0"], "7": ["8.4"], "8": ["9.0"]}}}) +@mock.patch( + "leapp.cli.commands.command_utils.get_upgrade_paths_config", + return_value={ + "rhel": { + "default": {"7.9": ["8.4"], "8.6": ["9.0"], "8.7": ["9.1"], "7": ["8.4"], "8": ["9.0"]} + }, + "centos": { + "default": {"8": ["9"], "9": ["10"]}, + "_virtual_versions": {"8": "8.7", "9": "9.8", "10": "10.2"}, + }, + "alma": { + "default": {"7.9": ["8.4"], "8.6": ["9.0"], "8.7": ["9.1"]} + }, + }, +) def test_get_target_version(mock_open, monkeypatch): - etc_os_release_contents = {'ID': 'rhel', 'VERSION_ID': '8.6'} - monkeypatch.setattr(command_utils, '_retrieve_os_release_contents', - lambda *args, **kwargs: etc_os_release_contents) - assert command_utils.get_target_version('default') == '9.0' + def set_etc_osrelease(distro_id, version_id): + etc_os_release_contents = {"ID": distro_id, "VERSION_ID": version_id} + monkeypatch.setattr( + command_utils, + "_retrieve_os_release_contents", + lambda *args, **kwargs: etc_os_release_contents, + ) + + set_etc_osrelease('rhel', '8.6') + assert command_utils.get_target_version('default', 'rhel') == '9.0' + + # the envar should not affect this function monkeypatch.setenv('LEAPP_DEVEL_TARGET_RELEASE', '') - etc_os_release_contents = {'ID': 'rhel', 'VERSION_ID': '8.6'} - monkeypatch.setattr(command_utils, '_retrieve_os_release_contents', - lambda *args, **kwargs: etc_os_release_contents) - assert command_utils.get_target_version('default') == '9.0' + assert command_utils.get_target_version('default', 'rhel') == '9.0' + # unsupported path, matches because of the major version fallback monkeypatch.delenv('LEAPP_DEVEL_TARGET_RELEASE', raising=True) - # unsupported path - etc_os_release_contents = {'ID': 'rhel', 'VERSION_ID': '8.5'} - monkeypatch.setattr(command_utils, '_retrieve_os_release_contents', - lambda *args, **kwargs: etc_os_release_contents) - assert command_utils.get_target_version('default') == '9.0' + set_etc_osrelease('rhel', '8.5') + assert command_utils.get_target_version('default', 'rhel') == '9.0' + + # centos->centos + set_etc_osrelease('centos', '9') + assert command_utils.get_target_version('default', 'centos') == '10' + + # centos->rhel, lookup based on virtual versions + set_etc_osrelease('centos', '8') + assert command_utils.get_target_version('default', 'rhel') == '9.1' + + # rhel->centos, reverse virtual versions lookup + set_etc_osrelease('rhel', '8.6') + assert command_utils.get_target_version('default', 'centos') == '9' @mock.patch( @@ -42,6 +70,11 @@ def test_get_target_version(mock_open, monkeypatch): }, ) def test_get_target_release(mock_open, monkeypatch): # do not remove mock_open + # NOTE Not testing with other distros, the tested function is mainly about + # handling of the CLI option, envar and format checking, the real target + # release retrieval is handled in get_target_version which is tested with + # different source/target distro combinanations elsewhere. + # Make it look like it's RHEL even on centos, because that's what the test # assumes. # Otherwise the test, when ran on Centos, fails because it works -- 2.51.1