From cdf761ad52a7b9a007ccebd5b9a94efef2e78ca2 Mon Sep 17 00:00:00 2001 From: Matej Matuska Date: Thu, 30 Jul 2026 12:10:50 +0200 Subject: [PATCH] IPU 9.9 -> 10.3: Final build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rebase on upstream version 0.25.0 - Inhibit the upgrade when machine ID is missing or invalid - Fix check of Ceph/LUKS volumes based on UUIDs - Do not crash when failing to mount a pseudo filesystem during upgrade - Prevent hanging system upgrade boot on systems with RAID and defined ‘resume’ on kernel command line - Introduce upgrades with conversions from Rocky Linux - Resolves: RHEL-188370, RHEL-151413, RHEL-155964, RHEL-155965, RHEL-155972, RHEL-180965, RHEL-187004, RHEL-193717, RHEL-133938 --- .gitignore | 1 + ...ve-legacy-overlay-solution-leftovers.patch | 112 - 0002-update-upgrade-paths-8.10-9.9-10.3.patch | 1053 ---- ...able-logrotate.timer-during-8to9-IPU.patch | 106 - ...scheme-enable-by-default-on-CS-9to10.patch | 63 - ...service-Silence-AVC-SELinux-warnings.patch | 46 - ...-Add-API-to-get-a-distro-report-name.patch | 138 - ...copy-upgrade-kernel-s-hmac-into-boot.patch | 190 - ...e-live.dir-is-relative-to-device-roo.patch | 131 - ...hange-location-of-source-system-tree.patch | 61 - 0010-Respect-distro-names-in-reports.patch | 1678 ------- ...istca-Respect-distro-name-in-reports.patch | 86 - ...ppc64-Respect-distro-name-in-reports.patch | 49 - ...elinux-Respect-distro-name-in-report.patch | 38 - ...elease-Respect-distro-name-in-report.patch | 92 - ...e-outadated-check-for-source-version.patch | 113 - ...echeck-Respect-distro-name-in-report.patch | 114 - ...tlogincheck-Remove-7to8-related-code.patch | 101 - ...tlogincheck-Respect-target-distro-na.patch | 60 - ..._ifcfg-Respect-distro-name-in-report.patch | 120 - ...-Use-distro-name-in-the-leapp-commen.patch | 155 - ...ck-Dont-make-the-recommendation-as-r.patch | 75 - ...ecture-Respect-distro-name-in-report.patch | 73 - ...emory-Respect-distro-name-in-reports.patch | 88 - ...reator-Respect-distro-name-in-report.patch | 126 - ...icesanddirvers-Respect-distro-name-i.patch | 241 - ...ckages-Respect-distro-name-in-report.patch | 140 - ...xclude-hugetlbfs-from-overlay-mounts.patch | 32 - ...Fix-check-of-naming-scheme-and-tests.patch | 121 - ...esdisable-refactoring-fix-ethX-detec.patch | 340 -- ...interfaces-non-PCI-conflicting-broke.patch | 518 -- ...esconfig-Deprecate-legacy-functional.patch | 398 -- ...esdisable-Disable-net.ifnames-correc.patch | 124 - ...esdisable-Mention-net.naming-scheme-.patch | 303 -- ...g-in-list-representation-of-commands.patch | 46 - ...g-in-list-representation-of-commands.patch | 116 - ...Add-PulseAudio-config-check-for-RHEL.patch | 533 -- ...insenabled-correctly-create-remediat.patch | 31 - ...onsider-source-clients-always-signed.patch | 201 - ...reenboot-rpms-are-removed-during-IPU.patch | 42 - ...e-grub-config-during-8-9-conversions.patch | 205 - ...s-update-version-requirements-per-py.patch | 75 - 0042-Drop-dependency-on-mock.patch | 149 - ...oting-from-remediation-commands-1520.patch | 194 - ...ate-leapp-data-data-stream-4.3-CTC-1.patch | 4401 ----------------- ...sable-ConfigParser-interpolation-whe.patch | 72 - ...ncy-and-simplify-config-parsing-to-P.patch | 157 - ...gnore-failures-when-callling-mount-a.patch | 102 - ...ator-do-not-make-nofail-mounts-requi.patch | 142 - ...vice-use-multi-user.target-instead-o.patch | 133 - ...do-upgrade.sh-to-reflect-upstream-ch.patch | 334 -- ...-do-upgrade.sh-output-when-.noreboot.patch | 43 - ...ndle-the-overrides-protocol-subsecti.patch | 47 - ...onfig_reader-Actor-and-8to9-Models-t.patch | 324 -- ...ltipathConfig9to10-and-MultipathConf.patch | 83 - ...ltipathConfRead9to10-config_reader-a.patch | 1897 ------- ...ltipathConfCheck9to10-mpath_conf_che.patch | 522 -- ...ltipathUpgradeConfUpdate9to10-mpath_.patch | 659 --- ...ndings-wwids-prkeys-file-copying-to-.patch | 567 --- ...athfiles-library-and-use-it-in-el9to.patch | 228 - ...-upgrade_cmdline-add-to_remove-field.patch | 96 - 0061-checkluks-emit-messages-only-once.patch | 81 - ...-rd.luks.uuid-from-the-upgrade-entry.patch | 256 - ...t-actor-context-when-switching-repos.patch | 114 - ...stutils-logger_mocked-accepts-kwargs.patch | 52 - ...pp_tmpdir-fixture-for-standardized-t.patch | 52 - ...e-DNF-libraries-into-dnflibs-package.patch | 1748 ------- ...e-imports-to-use-new-dnflibs-modules.patch | 256 - ...dnflibs-introduce-DNFError-exception.patch | 35 - ...n-Drop-dead-code-_handle_transaction.patch | 48 - ...ugin-Drop-deprecated-DNF_PLUGIN_PATH.patch | 107 - ...n-Introduce-new-DNF-exceptions-and-t.patch | 903 ---- ...e-Move-init-of-dnf.Base-to-dnflibs-a.patch | 637 --- ...g-Raise-proper-exceptions-and-add-te.patch | 363 -- ...ent-DNF-related-exception-propagatio.patch | 57 - ...fix-for-users-with-None-home-directo.patch | 87 - ...amfs-always-regenerate-initramfs-153.patch | 68 - ...-harness-with-AGENTS.md-and-task-ski.patch | 393 -- ...deprecations-warn-about-ifcfg-device.patch | 230 - ...-1-from-upgrade-and-target-kernel-bo.patch | 441 -- ..._path.py-crash-on-invalid-repo-paths.patch | 71 - ...use-distro-aware-OS-names-for-source.patch | 119 - ...vice-use-install_exec_t-SELinux-cont.patch | 58 - ...config-for-setting-rpm-gpg-keys-to-r.patch | 380 -- ...r-invalid-fstab-entries-on-pseudo-fi.patch | 274 - ...ve-deprecated-is_rhel_realtime-funct.patch | 81 - ...el-Fix-rt-kernel-detection-on-centos.patch | 142 - ...d-YAML-frontmatter-to-skills-for-Cla.patch | 78 - ...r-incorrect-var-run-symlink-configur.patch | 249 - ...nk-Log-warning-when-var-run-is-missi.patch | 34 - ...ps-actor-to-scan-installed-DNF-comps.patch | 419 -- ...-Fix-the-upgrade-of-Graphical-Server.patch | 165 - 0092-Bump-leapp-framework.patch | 26 - ...m-include-configuration-in-initramfs.patch | 491 -- ...tramfs_gen-write-rd.md.uuids-into-cm.patch | 235 - ...Update-actions-checkout-action-to-v7.patch | 53 - ...lues-for-rhui-jobs-follow-up-on-9to1.patch | 115 - ...-and-refactor-of-repo-related-actors.patch | 2493 ---------- ...tresolver-Init-message-consumption-c.patch | 752 --- ...tresolver-RepositoriesBlacklisted-us.patch | 309 -- ...tresolver-repositoriesblacklist-rede.patch | 786 --- ...mapping-scan_repositories-load_repos.patch | 288 -- ...tresolver-operate-with-RepoMapDataHa.patch | 615 --- ...tresolver-setuptargetrepos-minor-upd.patch | 262 - ...tresolver-pes_events_scanner-refacto.patch | 849 ---- ...argetcontentresolver-Make-TODO-notes.patch | 52 - leapp-repository.spec | 232 +- plans/tier0.fmf | 18 +- sources | 2 +- 109 files changed, 33 insertions(+), 34028 deletions(-) delete mode 100644 0001-Remove-legacy-overlay-solution-leftovers.patch delete mode 100644 0002-update-upgrade-paths-8.10-9.9-10.3.patch delete mode 100644 0003-Enable-logrotate.timer-during-8to9-IPU.patch delete mode 100644 0004-feat-net-naming-scheme-enable-by-default-on-CS-9to10.patch delete mode 100644 0005-Leapp_resume.service-Silence-AVC-SELinux-warnings.patch delete mode 100644 0006-Add-API-to-get-a-distro-report-name.patch delete mode 100644 0007-livemode-copy-upgrade-kernel-s-hmac-into-boot.patch delete mode 100644 0008-fix-livemode-make-live.dir-is-relative-to-device-roo.patch delete mode 100644 0009-fix-livemode-change-location-of-source-system-tree.patch delete mode 100644 0010-Respect-distro-names-in-reports.patch delete mode 100644 0011-checkblacklistca-Respect-distro-name-in-reports.patch delete mode 100644 0012-blsgrubcfgonppc64-Respect-distro-name-in-reports.patch delete mode 100644 0013-checkselinux-Respect-distro-name-in-report.patch delete mode 100644 0014-checkosrelease-Respect-distro-name-in-report.patch delete mode 100644 0015-rocecheck-Remove-outadated-check-for-source-version.patch delete mode 100644 0016-rocecheck-Respect-distro-name-in-report.patch delete mode 100644 0017-opensshpermitrootlogincheck-Remove-7to8-related-code.patch delete mode 100644 0018-opensshpermitrootlogincheck-Respect-target-distro-na.patch delete mode 100644 0019-checkifcfg_ifcfg-Respect-distro-name-in-report.patch delete mode 100644 0020-opensslproviders-Use-distro-name-in-the-leapp-commen.patch delete mode 100644 0021-opensslconfigcheck-Dont-make-the-recommendation-as-r.patch delete mode 100644 0022-checkmicroarchitecture-Respect-distro-name-in-report.patch delete mode 100644 0023-checkmemory-Respect-distro-name-in-reports.patch delete mode 100644 0024-targetuserspacecreator-Respect-distro-name-in-report.patch delete mode 100644 0025-checkdetecteddevicesanddirvers-Respect-distro-name-i.patch delete mode 100644 0026-reportleftoverpackages-Respect-distro-name-in-report.patch delete mode 100644 0027-fix-overlaygen-exclude-hugetlbfs-from-overlay-mounts.patch delete mode 100644 0028-biosdevname-Fix-check-of-naming-scheme-and-tests.patch delete mode 100644 0029-persistentnetnamesdisable-refactoring-fix-ethX-detec.patch delete mode 100644 0030-Fix-scan-of-net-interfaces-non-PCI-conflicting-broke.patch delete mode 100644 0031-persistentnetnamesconfig-Deprecate-legacy-functional.patch delete mode 100644 0032-persistentnetnamesdisable-Disable-net.ifnames-correc.patch delete mode 100644 0033-persistentnetnamesdisable-Mention-net.naming-scheme-.patch delete mode 100644 0034-fix-quoting-in-list-representation-of-commands.patch delete mode 100644 0035-fixup-fix-quoting-in-list-representation-of-commands.patch delete mode 100644 0036-pulseaudiocheck-Add-PulseAudio-config-check-for-RHEL.patch delete mode 100644 0037-fix-checkyumpluginsenabled-correctly-create-remediat.patch delete mode 100644 0038-fix-rhui-consider-source-clients-always-signed.patch delete mode 100644 0039-Ensure-that-greenboot-rpms-are-removed-during-IPU.patch delete mode 100644 0040-Regenerate-grub-config-during-8-9-conversions.patch delete mode 100644 0041-python-tests-deps-update-version-requirements-per-py.patch delete mode 100644 0042-Drop-dependency-on-mock.patch delete mode 100644 0043-remove-single-quoting-from-remediation-commands-1520.patch delete mode 100644 0044-Update-leapp-data-data-stream-4.3-CTC-1.patch delete mode 100644 0045-repofileutils-disable-ConfigParser-interpolation-whe.patch delete mode 100644 0046-Drop-six-dependency-and-simplify-config-parsing-to-P.patch delete mode 100644 0047-mounting-ignore-failures-when-callling-mount-a.patch delete mode 100644 0048-mount_unit_generator-do-not-make-nofail-mounts-requi.patch delete mode 100644 0049-leapp_resume.service-use-multi-user.target-instead-o.patch delete mode 100644 0050-LiveMode-update-do-upgrade.sh-to-reflect-upstream-ch.patch delete mode 100644 0051-LiveMode-improve-do-upgrade.sh-output-when-.noreboot.patch delete mode 100644 0052-multipathutil-handle-the-overrides-protocol-subsecti.patch delete mode 100644 0053-multipath-move-config_reader-Actor-and-8to9-Models-t.patch delete mode 100644 0054-multipath-Add-MultipathConfig9to10-and-MultipathConf.patch delete mode 100644 0055-multipath-Add-MultipathConfRead9to10-config_reader-a.patch delete mode 100644 0056-multipath-Add-MultipathConfCheck9to10-mpath_conf_che.patch delete mode 100644 0057-multipath-Add-MultipathUpgradeConfUpdate9to10-mpath_.patch delete mode 100644 0058-multipath-Add-bindings-wwids-prkeys-file-copying-to-.patch delete mode 100644 0059-multipath-Add-mpathfiles-library-and-use-it-in-el9to.patch delete mode 100644 0060-model-upgrade_cmdline-add-to_remove-field.patch delete mode 100644 0061-checkluks-emit-messages-only-once.patch delete mode 100644 0062-checkluks-remove-rd.luks.uuid-from-the-upgrade-entry.patch delete mode 100644 0063-conftest-exit-actor-context-when-switching-repos.patch delete mode 100644 0064-testutils-logger_mocked-accepts-kwargs.patch delete mode 100644 0065-conftest-Add-leapp_tmpdir-fixture-for-standardized-t.patch delete mode 100644 0066-Reorganize-DNF-libraries-into-dnflibs-package.patch delete mode 100644 0067-Update-imports-to-use-new-dnflibs-modules.patch delete mode 100644 0068-dnflibs-introduce-DNFError-exception.patch delete mode 100644 0069-dnflibs.dnfplugin-Drop-dead-code-_handle_transaction.patch delete mode 100644 0070-dnflibs.dnfplugin-Drop-deprecated-DNF_PLUGIN_PATH.patch delete mode 100644 0071-dnflibs.dnfplugin-Introduce-new-DNF-exceptions-and-t.patch delete mode 100644 0072-dnflibs-dnfmodule-Move-init-of-dnf.Base-to-dnflibs-a.patch delete mode 100644 0073-dnflibs.dnfconfig-Raise-proper-exceptions-and-add-te.patch delete mode 100644 0074-DOCSTRINGS-Document-DNF-related-exception-propagatio.patch delete mode 100644 0075-pulseaudiocheck-fix-for-users-with-None-home-directo.patch delete mode 100644 0076-fix-target_initramfs-always-regenerate-initramfs-153.patch delete mode 100644 0077-Add-AI-assistant-harness-with-AGENTS.md-and-task-ski.patch delete mode 100644 0078-el8toel9-networkdeprecations-warn-about-ifcfg-device.patch delete mode 100644 0079-Remove-enforcing-1-from-upgrade-and-target-kernel-bo.patch delete mode 100644 0080-Fix-utils-actor_path.py-crash-on-invalid-repo-paths.patch delete mode 100644 0081-fix-breadcrumbs-use-distro-aware-OS-names-for-source.patch delete mode 100644 0082-leapp_resume.service-use-install_exec_t-SELinux-cont.patch delete mode 100644 0083-config-rhui-Add-config-for-setting-rpm-gpg-keys-to-r.patch delete mode 100644 0084-Add-inhibitor-for-invalid-fstab-entries-on-pseudo-fi.patch delete mode 100644 0085-lib-version-Remove-deprecated-is_rhel_realtime-funct.patch delete mode 100644 0086-lib-kernel-Fix-rt-kernel-detection-on-centos.patch delete mode 100644 0087-Add-CLAUDE.md-and-YAML-frontmatter-to-skills-for-Cla.patch delete mode 100644 0088-Add-inhibitor-for-incorrect-var-run-symlink-configur.patch delete mode 100644 0089-checkvarrunsymlink-Log-warning-when-var-run-is-missi.patch delete mode 100644 0090-Add-scandnfcomps-actor-to-scan-installed-DNF-comps.patch delete mode 100644 0091-IPU-9-10-Fix-the-upgrade-of-Graphical-Server.patch delete mode 100644 0092-Bump-leapp-framework.patch delete mode 100644 0093-raid-mdadm-include-configuration-in-initramfs.patch delete mode 100644 0094-feat-upgrade_initramfs_gen-write-rd.md.uuids-into-cm.patch delete mode 100644 0095-Update-actions-checkout-action-to-v7.patch delete mode 100644 0096-Update-distro-values-for-rhui-jobs-follow-up-on-9to1.patch delete mode 100644 0097-1-9-Merge-and-refactor-of-repo-related-actors.patch delete mode 100644 0098-2-9-targetcontentresolver-Init-message-consumption-c.patch delete mode 100644 0099-3-9-targetcontentresolver-RepositoriesBlacklisted-us.patch delete mode 100644 0100-4-9-targetcontentresolver-repositoriesblacklist-rede.patch delete mode 100644 0101-5-9-repositoriesmapping-scan_repositories-load_repos.patch delete mode 100644 0102-6-9-targetcontentresolver-operate-with-RepoMapDataHa.patch delete mode 100644 0103-7-9-targetcontentresolver-setuptargetrepos-minor-upd.patch delete mode 100644 0104-8-9-targetcontentresolver-pes_events_scanner-refacto.patch delete mode 100644 0105-9-9-targetcontentresolver-Make-TODO-notes.patch diff --git a/.gitignore b/.gitignore index 339ba06..d8a8049 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ /leapp-repository-0.22.0.tar.gz /leapp-repository-0.23.0.tar.gz /leapp-repository-0.24.0.tar.gz +/leapp-repository-0.25.0.tar.gz diff --git a/0001-Remove-legacy-overlay-solution-leftovers.patch b/0001-Remove-legacy-overlay-solution-leftovers.patch deleted file mode 100644 index 4f8c3aa..0000000 --- a/0001-Remove-legacy-overlay-solution-leftovers.patch +++ /dev/null @@ -1,112 +0,0 @@ -From 34aeae1e023e61345a1bb020b42231a79a0be4a8 Mon Sep 17 00:00:00 2001 -From: Matej Matuska -Date: Thu, 26 Feb 2026 17:52:23 +0100 -Subject: [PATCH 01/44] Remove legacy overlay solution leftovers - -The solution was removed in 93429, however there are some leftovers. ---- - .../libraries/userspacegen.py | 19 ------------------- - .../common/libraries/dnfplugin.py | 15 ++++----------- - .../common/libraries/overlaygen.py | 6 ------ - 3 files changed, 4 insertions(+), 36 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py b/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py -index 66843645..7dbadae2 100644 ---- a/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py -+++ b/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py -@@ -178,26 +178,7 @@ def _import_gpg_keys(context, install_root_dir, target_major_version): - ) - - --def _handle_transaction_err_msg_size_old(err): -- # NOTE(pstodulk): This is going to be removed in future! -- -- article_section = 'Generic case' -- xfs_info = next(api.consume(XFSPresence), XFSPresence()) -- if xfs_info.present and xfs_info.without_ftype: -- article_section = 'XFS ftype=0 case' -- -- message = ('There is not enough space on the file system hosting /var/lib/leapp directory ' -- 'to extract the packages.') -- details = {'hint': "Please follow the instructions in the '{}' section of the article at: " -- "link: https://access.redhat.com/solutions/5057391".format(article_section)} -- -- raise StopActorExecutionError(message=message, details=details) -- -- - def _handle_transaction_err_msg_size(err): -- if get_env('LEAPP_OVL_LEGACY', '0') == '1': -- _handle_transaction_err_msg_size_old(err) -- return # not needed actually as the above function raises error, but for visibility - NO_SPACE_STR = 'more space needed on the' - - # Disk Requirements: -diff --git a/repos/system_upgrade/common/libraries/dnfplugin.py b/repos/system_upgrade/common/libraries/dnfplugin.py -index d50dbbce..b91bcbe7 100644 ---- a/repos/system_upgrade/common/libraries/dnfplugin.py -+++ b/repos/system_upgrade/common/libraries/dnfplugin.py -@@ -7,7 +7,6 @@ import shutil - - from leapp.exceptions import StopActorExecutionError - from leapp.libraries.common import dnfconfig, guards, mounting, overlaygen, rhsm, utils --from leapp.libraries.common.config import get_env - from leapp.libraries.common.config.version import get_target_major_version, get_target_version - from leapp.libraries.common.gpg import is_nogpgcheck_set - from leapp.libraries.stdlib import api, CalledProcessError, config -@@ -166,16 +165,10 @@ def _handle_transaction_err_msg_old(stage, xfs_info, err): - raise StopActorExecutionError(message=message, details=details) - - --def _handle_transaction_err_msg(stage, xfs_info, err, is_container=False): -- # ignore the fallback when the error is related to the container issue -- # e.g. installation of packages inside the container; so it's unrelated -- # to the upgrade transactions. -- if get_env('LEAPP_OVL_LEGACY', '0') == '1' and not is_container: -- _handle_transaction_err_msg_old(stage, xfs_info, err) -- return # not needed actually as the above function raises error, but for visibility -+def _handle_transaction_err_msg(err, is_container=False): - NO_SPACE_STR = 'more space needed on the' -- message = 'DNF execution failed with non zero exit code.' - if NO_SPACE_STR not in err.stderr: -+ message = 'DNF execution failed with non zero exit code.' - # if there was a problem reaching repos and proxy is configured in DNF/YUM configs, the - # proxy is likely the problem. - # NOTE(mmatuska): We can't consistently detect there was a problem reaching some repos, -@@ -308,7 +301,7 @@ def _transaction(context, stage, target_repoids, tasks, plugin_info, xfs_info, - ) - except CalledProcessError as e: - api.current_logger().error('Cannot calculate, check, test, or perform the upgrade transaction.') -- _handle_transaction_err_msg(stage, xfs_info, e, is_container=False) -+ _handle_transaction_err_msg(e, is_container=False) - finally: - if stage == 'check': - backup_debug_data(context=context) -@@ -384,7 +377,7 @@ def install_initramdisk_requirements(packages, target_userspace_info, used_repos - api.current_logger().error( - 'Cannot install packages in the target container required to build the upgrade initramfs.' - ) -- _handle_transaction_err_msg('', None, e, is_container=True) -+ _handle_transaction_err_msg(e, is_container=True) - - - def perform_transaction_install(target_userspace_info, storage_info, used_repos, tasks, plugin_info, xfs_info): -diff --git a/repos/system_upgrade/common/libraries/overlaygen.py b/repos/system_upgrade/common/libraries/overlaygen.py -index f0d0ba1d..3091ab5b 100644 ---- a/repos/system_upgrade/common/libraries/overlaygen.py -+++ b/repos/system_upgrade/common/libraries/overlaygen.py -@@ -594,12 +594,6 @@ def create_source_overlay( - in the OVERLAY_DO_NOT_MOUNT set. Such prepared OVL images are then composed - together to reflect the real host filesystem. In the end everything is cleaned. - -- The new solution can be now problematic for system with too many partitions -- and loop devices. For such systems we keep for now the possibility of the -- fallback to an old solution, which has however number of issues that are -- fixed by the new design. To fallback to the old solution, set envar: -- LEAPP_OVL_LEGACY=1 -- - Disk images created for OVL are formatted with XFS by default. In case of - problems, it's possible to switch to Ext4 FS using: - LEAPP_OVL_IMG_FS_EXT4=1 --- -2.53.0 - diff --git a/0002-update-upgrade-paths-8.10-9.9-10.3.patch b/0002-update-upgrade-paths-8.10-9.9-10.3.patch deleted file mode 100644 index fc20baa..0000000 --- a/0002-update-upgrade-paths-8.10-9.9-10.3.patch +++ /dev/null @@ -1,1053 +0,0 @@ -From 763077b15bdb019984186034c026ecaefb6de7f9 Mon Sep 17 00:00:00 2001 -From: Peter Mocary -Date: Tue, 24 Feb 2026 19:12:00 +0100 -Subject: [PATCH 02/44] update upgrade paths: 8.10 -> 9.9 -> 10.3 - -Introduce new upgrade paths and add them to the CI tests. - -Jira: RHEL-150279 ---- - .packit.yaml | 193 +++++++++++++++--- - .../common/files/prod-certs/10.3/279.pem | 35 ++++ - .../common/files/prod-certs/10.3/362.pem | 36 ++++ - .../common/files/prod-certs/10.3/363.pem | 35 ++++ - .../common/files/prod-certs/10.3/419.pem | 35 ++++ - .../common/files/prod-certs/10.3/433.pem | 35 ++++ - .../common/files/prod-certs/10.3/479.pem | 35 ++++ - .../common/files/prod-certs/10.3/486.pem | 35 ++++ - .../common/files/prod-certs/10.3/72.pem | 35 ++++ - .../common/files/prod-certs/9.9/279.pem | 35 ++++ - .../common/files/prod-certs/9.9/362.pem | 36 ++++ - .../common/files/prod-certs/9.9/363.pem | 35 ++++ - .../common/files/prod-certs/9.9/419.pem | 35 ++++ - .../common/files/prod-certs/9.9/433.pem | 35 ++++ - .../common/files/prod-certs/9.9/479.pem | 35 ++++ - .../common/files/prod-certs/9.9/486.pem | 35 ++++ - .../common/files/prod-certs/9.9/72.pem | 35 ++++ - .../common/files/upgrade_paths.json | 17 +- - 18 files changed, 735 insertions(+), 37 deletions(-) - create mode 100644 repos/system_upgrade/common/files/prod-certs/10.3/279.pem - create mode 100644 repos/system_upgrade/common/files/prod-certs/10.3/362.pem - create mode 100644 repos/system_upgrade/common/files/prod-certs/10.3/363.pem - create mode 100644 repos/system_upgrade/common/files/prod-certs/10.3/419.pem - create mode 100644 repos/system_upgrade/common/files/prod-certs/10.3/433.pem - create mode 100644 repos/system_upgrade/common/files/prod-certs/10.3/479.pem - create mode 100644 repos/system_upgrade/common/files/prod-certs/10.3/486.pem - create mode 100644 repos/system_upgrade/common/files/prod-certs/10.3/72.pem - create mode 100644 repos/system_upgrade/common/files/prod-certs/9.9/279.pem - create mode 100644 repos/system_upgrade/common/files/prod-certs/9.9/362.pem - create mode 100644 repos/system_upgrade/common/files/prod-certs/9.9/363.pem - create mode 100644 repos/system_upgrade/common/files/prod-certs/9.9/419.pem - create mode 100644 repos/system_upgrade/common/files/prod-certs/9.9/433.pem - create mode 100644 repos/system_upgrade/common/files/prod-certs/9.9/479.pem - create mode 100644 repos/system_upgrade/common/files/prod-certs/9.9/486.pem - create mode 100644 repos/system_upgrade/common/files/prod-certs/9.9/72.pem - -diff --git a/.packit.yaml b/.packit.yaml -index 37fa7849..49e251d8 100644 ---- a/.packit.yaml -+++ b/.packit.yaml -@@ -141,6 +141,15 @@ jobs: - provisioning: - tags: - BusinessUnit: sst_upgrades@leapp_upstream_test -+ - &tmt-env-settings-810to99 -+ tmt: -+ context: &tmt-context-810to99 -+ distro: "rhel-8.10" -+ distro_target: "rhel-9.9" -+ settings: -+ provisioning: -+ tags: -+ BusinessUnit: sst_upgrades@leapp_upstream_test - - - &sanity-abstract-8to9-aws - <<: *sanity-abstract-8to9 -@@ -220,7 +229,7 @@ jobs: - tf_extra_params: - test: - tmt: -- plan_filter: 'tag:8to9 & tag:kernel-rt & enabled:true & tag:-rhsm' -+ plan_filter: 'tag:8to9 & tag:kernel-rt & enabled:true & tag:-rhsm' - environments: - - *tmt-env-settings-810to96 - env: -@@ -296,14 +305,68 @@ jobs: - tf_extra_params: - test: - tmt: -- plan_filter: 'tag:8to9 & tag:kernel-rt & enabled:true' -+ plan_filter: 'tag:8to9 & tag:kernel-rt & enabled:true' - environments: - - *tmt-env-settings-810to98 - env: - <<: *env-810to98 - - # ###################################################################### # --# ############################## 9 TO 10 ################################ # -+# ############################# 8.10 > 9.9 ############################# # -+# ###################################################################### # -+ -+- &sanity-810to99 -+ <<: *sanity-abstract-8to9 -+ trigger: pull_request -+ identifier: sanity-8.10to9.9 -+ tf_extra_params: -+ test: -+ tmt: -+ plan_filter: 'tag:8to9 & tag:tier0 & enabled:true' -+ environments: -+ - *tmt-env-settings-810to99 -+ env: &env-810to99 -+ SOURCE_RELEASE: "8.10" -+ TARGET_RELEASE: "9.9" -+ -+# On-demand minimal beaker tests -+- &beaker-minimal-810to99 -+ <<: *beaker-minimal-8to9-abstract-ondemand -+ trigger: pull_request -+ labels: -+ - beaker-minimal -+ - beaker-minimal-8.10to9.9 -+ - 8.10to9.9 -+ identifier: sanity-8.10to9.9-beaker-minimal-ondemand -+ tf_extra_params: -+ test: -+ tmt: -+ plan_filter: 'tag:8to9 & tag:partitioning & enabled:true' -+ environments: -+ - *tmt-env-settings-810to99 -+ env: -+ <<: *env-810to99 -+ -+# On-demand kernel-rt tests -+- &kernel-rt-810to99 -+ <<: *kernel-rt-abstract-8to9-ondemand -+ trigger: pull_request -+ labels: -+ - kernel-rt -+ - kernel-rt-8.10to9.9 -+ - 8.10to9.9 -+ identifier: sanity-8.10to9.9-kernel-rt-ondemand -+ tf_extra_params: -+ test: -+ tmt: -+ plan_filter: 'tag:8to9 & tag:kernel-rt & enabled:true' -+ environments: -+ - *tmt-env-settings-810to99 -+ env: -+ <<: *env-810to99 -+ -+# ###################################################################### # -+# ############################## 9 TO 10 ############################### # - # ###################################################################### # - - # ###################################################################### # -@@ -345,15 +408,6 @@ jobs: - provisioning: - tags: - BusinessUnit: sst_upgrades@leapp_upstream_test -- - &tmt-env-settings-centos9torhel101 -- tmt: -- context: &tmt-context-centos9torhel101 -- distro: "centos-9" -- distro_target: "rhel-10.1" -- settings: -- provisioning: -- tags: -- BusinessUnit: sst_upgrades@leapp_upstream_test - - &tmt-env-settings-98to102 - tmt: - context: &tmt-context-98to102 -@@ -372,6 +426,24 @@ jobs: - provisioning: - tags: - BusinessUnit: sst_upgrades@leapp_upstream_test -+ - &tmt-env-settings-99to103 -+ tmt: -+ context: &tmt-context-99to103 -+ distro: "rhel-9.9" -+ distro_target: "rhel-10.3" -+ settings: -+ provisioning: -+ tags: -+ BusinessUnit: sst_upgrades@leapp_upstream_test -+ - &tmt-env-settings-centos9torhel103 -+ tmt: -+ context: &tmt-context-centos9torhel103 -+ distro: "centos-9" -+ distro_target: "rhel-10.3" -+ settings: -+ provisioning: -+ tags: -+ BusinessUnit: sst_upgrades@leapp_upstream_test - - - &sanity-abstract-9to10-aws - <<: *sanity-abstract-9to10 -@@ -439,7 +511,7 @@ jobs: - tf_extra_params: - test: - tmt: -- plan_filter: 'tag:8to9 & tag:partitioning & enabled:true & tag:-rhsm' -+ plan_filter: 'tag:9to10 & tag:partitioning & enabled:true & tag:-rhsm' - environments: - - *tmt-env-settings-96to100 - env: -@@ -457,7 +529,7 @@ jobs: - tf_extra_params: - test: - tmt: -- plan_filter: 'tag:8to9 & tag:kernel-rt & enabled:true & tag:-rhsm' -+ plan_filter: 'tag:9to10 & tag:kernel-rt & enabled:true & tag:-rhsm' - environments: - - *tmt-env-settings-96to100 - env: -@@ -499,7 +571,7 @@ jobs: - tf_extra_params: - test: - tmt: -- plan_filter: 'tag:8to9 & tag:partitioning & enabled:true & tag:-rhsm' -+ plan_filter: 'tag:9to10 & tag:partitioning & enabled:true & tag:-rhsm' - environments: - - *tmt-env-settings-98to102 - env: -@@ -520,12 +592,76 @@ jobs: - tf_extra_params: - test: - tmt: -- plan_filter: 'tag:8to9 & tag:kernel-rt & enabled:true & tag:-rhsm' -+ plan_filter: 'tag:9to10 & tag:kernel-rt & enabled:true & tag:-rhsm' - environments: - - *tmt-env-settings-98to102 - env: - <<: *env-98to102 - -+# ###################################################################### # -+# ############################# 9.9 > 10.3 ############################# # -+# ###################################################################### # -+ -+- &sanity-99to103 -+ <<: *sanity-abstract-9to10 -+ trigger: pull_request -+ identifier: sanity-9.9to10.3 -+ targets: -+ epel-9-x86_64: -+ distros: [RHEL-9.9.0-Nightly] -+ tf_extra_params: -+ test: -+ tmt: -+ plan_filter: 'tag:9to10 & tag:tier0 & enabled:true & tag:-rhsm' -+ environments: -+ - *tmt-env-settings-99to103 -+ env: &env-99to103 -+ SOURCE_RELEASE: "9.9" -+ TARGET_RELEASE: "10.3" -+ -+# On-demand minimal beaker tests -+- &beaker-minimal-99to103 -+ <<: *beaker-minimal-9to10-abstract-ondemand -+ trigger: pull_request -+ labels: -+ - beaker-minimal -+ - beaker-minimal-9.9to10.3 -+ - 9.9to10.3 -+ identifier: sanity-9.9to10.3-beaker-minimal-ondemand -+ targets: -+ epel-9-x86_64: -+ distros: [RHEL-9.9-Nightly] -+ tf_extra_params: -+ test: -+ tmt: -+ plan_filter: 'tag:9to10 & tag:partitioning & enabled:true & tag:-rhsm' -+ environments: -+ - *tmt-env-settings-99to103 -+ env: -+ <<: *env-99to103 -+ -+# On-demand kernel-rt tests -+- &kernel-rt-99to103 -+ <<: *kernel-rt-abstract-9to10-ondemand -+ trigger: pull_request -+ labels: -+ - kernel-rt -+ - kernel-rt-9.9to10.3 -+ - 9.9to10.3 -+ identifier: sanity-9.9to10.3-kernel-rt-ondemand -+ targets: -+ epel-9-x86_64: -+ distros: [RHEL-9.9-Nightly] -+ tf_extra_params: -+ test: -+ tmt: -+ plan_filter: 'tag:9to10 & tag:kernel-rt & enabled:true & tag:-rhsm' -+ environments: -+ - *tmt-env-settings-99to103 -+ env: -+ <<: *env-99to103 -+ -+ - # ###################################################################### # - # ########################## CentOS Stream ############################# # - # ###################################################################### # -@@ -535,13 +671,13 @@ jobs: - # ###################################################################### # - - # ###################################################################### # --# ############################ 9 > 10.1 ################################ # -+# ############################ 9 > 10.2 ################################ # - # ###################################################################### # - --- &sanity-centos9torhel101 -+- &sanity-centos9torhel102 - <<: *sanity-abstract-9to10 - trigger: pull_request -- identifier: sanity-CentOS9toRHEL10.1 -+ identifier: sanity-CentOS9toRHEL10.2 - targets: - epel-9-x86_64: - distros: [CentOS-Stream-9] -@@ -550,19 +686,19 @@ jobs: - tmt: - plan_filter: 'tag:9to10 & tag:tier0 & enabled:true & tag:-rhsm' - environments: -- - *tmt-env-settings-centos9torhel101 -- env: &env-centos9to101 -+ - *tmt-env-settings-centos9torhel102 -+ env: &env-centos9torhel102 - SOURCE_RELEASE: "9" -- TARGET_RELEASE: "10.1" -+ TARGET_RELEASE: "10.2" - - # ###################################################################### # --# ############################ 9 > 10.2 ################################ # -+# ############################ 9 > 10.3 ################################ # - # ###################################################################### # - --- &sanity-centos9torhel102 -+- &sanity-centos9torhel103 - <<: *sanity-abstract-9to10 - trigger: pull_request -- identifier: sanity-CentOS9toRHEL10.2 -+ identifier: sanity-CentOS9toRHEL10.3 - targets: - epel-9-x86_64: - distros: [CentOS-Stream-9] -@@ -570,12 +706,11 @@ jobs: - test: - tmt: - plan_filter: 'tag:9to10 & tag:tier0 & enabled:true & tag:-rhsm' -- name: - environments: -- - *tmt-env-settings-centos9torhel102 -- env: &env-centos9torhel102 -+ - *tmt-env-settings-centos9torhel103 -+ env: &env-centos9torhel103 - SOURCE_RELEASE: "9" -- TARGET_RELEASE: "10.2" -+ TARGET_RELEASE: "10.3" - - # ###################################################################### # - # ################## CentOS Stream > CentOS Stream ##################### # -diff --git a/repos/system_upgrade/common/files/prod-certs/10.3/279.pem b/repos/system_upgrade/common/files/prod-certs/10.3/279.pem -new file mode 100644 -index 00000000..2c0a1998 ---- /dev/null -+++ b/repos/system_upgrade/common/files/prod-certs/10.3/279.pem -@@ -0,0 +1,35 @@ -+-----BEGIN CERTIFICATE----- -+MIIGKDCCBBCgAwIBAgIJALDxRLt/tVClMA0GCSqGSIb3DQEBCwUAMIGuMQswCQYD -+VQQGEwJVUzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExFjAUBgNVBAoMDVJlZCBI -+YXQsIEluYy4xGDAWBgNVBAsMD1JlZCBIYXQgTmV0d29yazEuMCwGA1UEAwwlUmVk -+IEhhdCBFbnRpdGxlbWVudCBQcm9kdWN0IEF1dGhvcml0eTEkMCIGCSqGSIb3DQEJ -+ARYVY2Etc3VwcG9ydEByZWRoYXQuY29tMB4XDTI2MDExOTE4MjA0N1oXDTQ2MDEx -+OTE4MjA0N1owRDFCMEAGA1UEAww5UmVkIEhhdCBQcm9kdWN0IElEIFs3MWZmZWUx -+Yy1kMjE5LTRiZjgtYTJjYi1lOTM0OTk3ODQ1ZmRdMIICIjANBgkqhkiG9w0BAQEF -+AAOCAg8AMIICCgKCAgEAxj9J04z+Ezdyx1U33kFftLv0ntNS1BSeuhoZLDhs18yk -+sepG7hXXtHh2CMFfLZmTjAyL9i1XsxykQpVQdXTGpUF33C2qBQHB5glYs9+d781x -+8p8m8zFxbPcW82TIJXbgW3ErVh8vk5qCbG1cCAAHb+DWMq0EAyy1bl/JgAghYNGB -+RvKJObTdCrdpYh02KUqBLkSPZHvo6DUJFN37MXDpVeQq9VtqRjpKLLwuEfXb0Y7I -+5xEOrR3kYbOaBAWVt3mYZ1t0L/KfY2jVOdU5WFyyB9PhbMdLi1xE801j+GJrwcLa -+xmqvj4UaICRzcPATP86zVM1BBQa+lilkRQes5HyjZzZDiGYudnXhbqmLo/n0cuXo -+QBVVjhzRTMx71Eiiahmiw+U1vGqkHhQNxb13HtN1lcAhUCDrxxeMvrAjYdWpYlpI -+yW3NssPWt1YUHidMBSAJ4KctIf91dyE93aStlxwC/QnyFsZOmcEsBzVCnz9GmWMl -+1/6XzBS1yDUqByklx0TLH+z/sK9A+O2rZAy1mByCYwVxvbOZhnqGxAuToIS+A81v -+5hCjsCiOScVB+cil30YBu0cH85RZ0ILNkHdKdrLLWW4wjphK2nBn2g2i3+ztf+nQ -+ED2pQqZ/rhuW79jcyCZl9kXqe1wOdF0Cwah4N6/3LzIXEEKyEJxNqQwtNc2IVE8C -+AwEAAaOBsTCBrjAJBgNVHRMEAjAAMEMGDCsGAQQBkggJAYIXAQQzDDFSZWQgSGF0 -+IEVudGVycHJpc2UgTGludXggZm9yIFBvd2VyLCBsaXR0bGUgZW5kaWFuMBYGDCsG -+AQQBkggJAYIXAgQGDAQxMC4zMBkGDCsGAQQBkggJAYIXAwQJDAdwcGM2NGxlMCkG -+DCsGAQQBkggJAYIXBAQZDBdyaGVsLTEwLHJoZWwtMTAtcHBjNjRsZTANBgkqhkiG -+9w0BAQsFAAOCAgEAjQejd3CXVRh3f2L3nZquYzHO2WT+oyRVhCYkyXHVGyQ2x61j -+wdjlbciDyoa01BmcaTUP72tKeT0TPF94NDaznht2BaB64qJMPTTRFngcTTPEN3Mw -+f/nc6W/dQQ0bGf9VfYm1WXuOdhoDTxjimaw2zEDzPSVl0lLGG+jNHTjoYlnpTOS6 -+BhrJ7lOvs/uC5KUsHJld1bLWPU/ApqyMjJjz+Oc1hLI8JFg8ekZxO+B8h5us7mTs -+VidnlUq2agyrVi2yHVQ892cOBlpzkRSXr+8SNwyezq/8iw/pmu9WT3Ibfqb1SRwY -+iMQV+4RgGzb+c+MAjJQVZAUH50cS1w7KsuQLGthUs2ZUvJJJfYHvPll7F1OWSZO7 -+lFor5EQ9GK1LTUv266OyozwIjXfK4ZzIM+bYURGAejQonSeHdsBJEQMC9yVidSon -+7UeIsznzoXTL0nuFTDD3viL0loE5rq06L7Gn2jhQMp9TeUU/ZWmjBA+fZMQsYXO2 -+P5S0zKOVNFtzJPoPUjsym/qjhRx2tBBVrTt9HilQ3omEC1dgv5IBNs+NDQvTRSqC -+8JZw9nsdL37lvj2k5bMyFlcR76yN9KLROZ7vFZM85vkhuamyve3GnXWcVPCKOHNI -+umvjh5/Zxoz3yy6YnensCbHe88wBQ4jDVN3VMngQ7lw5jJF0aPy40cE1vVU= -+-----END CERTIFICATE----- -diff --git a/repos/system_upgrade/common/files/prod-certs/10.3/362.pem b/repos/system_upgrade/common/files/prod-certs/10.3/362.pem -new file mode 100644 -index 00000000..fcab2303 ---- /dev/null -+++ b/repos/system_upgrade/common/files/prod-certs/10.3/362.pem -@@ -0,0 +1,36 @@ -+-----BEGIN CERTIFICATE----- -+MIIGNzCCBB+gAwIBAgIJALDxRLt/tVD/MA0GCSqGSIb3DQEBCwUAMIGuMQswCQYD -+VQQGEwJVUzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExFjAUBgNVBAoMDVJlZCBI -+YXQsIEluYy4xGDAWBgNVBAsMD1JlZCBIYXQgTmV0d29yazEuMCwGA1UEAwwlUmVk -+IEhhdCBFbnRpdGxlbWVudCBQcm9kdWN0IEF1dGhvcml0eTEkMCIGCSqGSIb3DQEJ -+ARYVY2Etc3VwcG9ydEByZWRoYXQuY29tMB4XDTI2MDIxOTEwMzczOVoXDTQ2MDIx -+OTEwMzczOVowRDFCMEAGA1UEAww5UmVkIEhhdCBQcm9kdWN0IElEIFsyZmQwM2Ez -+My0yMmI0LTQyYzktYjFlYS00NWNmOTc1YTNmMzddMIICIjANBgkqhkiG9w0BAQEF -+AAOCAg8AMIICCgKCAgEAxj9J04z+Ezdyx1U33kFftLv0ntNS1BSeuhoZLDhs18yk -+sepG7hXXtHh2CMFfLZmTjAyL9i1XsxykQpVQdXTGpUF33C2qBQHB5glYs9+d781x -+8p8m8zFxbPcW82TIJXbgW3ErVh8vk5qCbG1cCAAHb+DWMq0EAyy1bl/JgAghYNGB -+RvKJObTdCrdpYh02KUqBLkSPZHvo6DUJFN37MXDpVeQq9VtqRjpKLLwuEfXb0Y7I -+5xEOrR3kYbOaBAWVt3mYZ1t0L/KfY2jVOdU5WFyyB9PhbMdLi1xE801j+GJrwcLa -+xmqvj4UaICRzcPATP86zVM1BBQa+lilkRQes5HyjZzZDiGYudnXhbqmLo/n0cuXo -+QBVVjhzRTMx71Eiiahmiw+U1vGqkHhQNxb13HtN1lcAhUCDrxxeMvrAjYdWpYlpI -+yW3NssPWt1YUHidMBSAJ4KctIf91dyE93aStlxwC/QnyFsZOmcEsBzVCnz9GmWMl -+1/6XzBS1yDUqByklx0TLH+z/sK9A+O2rZAy1mByCYwVxvbOZhnqGxAuToIS+A81v -+5hCjsCiOScVB+cil30YBu0cH85RZ0ILNkHdKdrLLWW4wjphK2nBn2g2i3+ztf+nQ -+ED2pQqZ/rhuW79jcyCZl9kXqe1wOdF0Cwah4N6/3LzIXEEKyEJxNqQwtNc2IVE8C -+AwEAAaOBwDCBvTAJBgNVHRMEAjAAMEgGDCsGAQQBkggJAYJqAQQ4DDZSZWQgSGF0 -+IEVudGVycHJpc2UgTGludXggZm9yIFBvd2VyLCBsaXR0bGUgZW5kaWFuIEJldGEw -+GwYMKwYBBAGSCAkBgmoCBAsMCTEwLjMgQmV0YTAZBgwrBgEEAZIICQGCagMECQwH -+cHBjNjRsZTAuBgwrBgEEAZIICQGCagQEHgwccmhlbC0xMCxyaGVsLTEwLWJldGEt -+cHBjNjRsZTANBgkqhkiG9w0BAQsFAAOCAgEAMDrgDsBEldXi0BAkyWaHShs9UINL -+pkqFi4Xizj3x8TsnAawgRSVnctoOif9xKaSCU1FabDKjJKMsB3/tS/trgRF6YOdT -+UWfnUYkLb0KWlwlRHGEAcymzR+9UqCtnicOW/rLHuOfBBMQPZuT3zIoNn9UUS40/ -+wE5GVnpdZ/xUMSDBbbCusZmkhLXnuzZMRz517O5ST/N8ilQtSaTg1Ea2O2inmEGb -+tc3zZSDhOi/KEVwUSaIa8U5w/L7jNaisCRPXnnNumzc7kLUJmJnmXee++qpjyLiT -+41IP1EDGHYReCsHjCw+0CWkDNvtyc172eTgKUZNPvmTwGoGknX0h6RSvc5pejcI5 -+/aOahHYI11EKt76WNdFCUSD7s2xQOYTu72DJvHXkb/DxcmZmVWjetip9g3Wd29cg -+NwO6l47bsMY6IO76wz3YAh+GE430UHNPyqyog43Yp0TDN404wyJRhg8zHe8cL0UO -+c7jDnsL7eLFQhcF9tKPdBvtlkPt18QcaHVoYYseovUA9ICrgkYQrqAR8cLfD+L01 -+64IU4Ao8MKl8MJbSAcc12m4OnwKLS5ZhybbKa4CAZI3rFLl+XP8ZPCEkF18shKwW -+Y7eezpUuJyRhmG8VlpWECMRuXlQPiJ7AxtBp2/ITwRcS9nRVEat0a6zSaxe3jB/C -+FqTGy2sKtQ5i0bg= -+-----END CERTIFICATE----- -diff --git a/repos/system_upgrade/common/files/prod-certs/10.3/363.pem b/repos/system_upgrade/common/files/prod-certs/10.3/363.pem -new file mode 100644 -index 00000000..e2d07f1c ---- /dev/null -+++ b/repos/system_upgrade/common/files/prod-certs/10.3/363.pem -@@ -0,0 +1,35 @@ -+-----BEGIN CERTIFICATE----- -+MIIGKTCCBBGgAwIBAgIJALDxRLt/tVD+MA0GCSqGSIb3DQEBCwUAMIGuMQswCQYD -+VQQGEwJVUzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExFjAUBgNVBAoMDVJlZCBI -+YXQsIEluYy4xGDAWBgNVBAsMD1JlZCBIYXQgTmV0d29yazEuMCwGA1UEAwwlUmVk -+IEhhdCBFbnRpdGxlbWVudCBQcm9kdWN0IEF1dGhvcml0eTEkMCIGCSqGSIb3DQEJ -+ARYVY2Etc3VwcG9ydEByZWRoYXQuY29tMB4XDTI2MDIxOTEwMzczOFoXDTQ2MDIx -+OTEwMzczOFowRDFCMEAGA1UEAww5UmVkIEhhdCBQcm9kdWN0IElEIFszNWQ2NmQz -+NS00YjkwLTRjOWMtODBmOC0xNTEwZWI3Yzg5NGFdMIICIjANBgkqhkiG9w0BAQEF -+AAOCAg8AMIICCgKCAgEAxj9J04z+Ezdyx1U33kFftLv0ntNS1BSeuhoZLDhs18yk -+sepG7hXXtHh2CMFfLZmTjAyL9i1XsxykQpVQdXTGpUF33C2qBQHB5glYs9+d781x -+8p8m8zFxbPcW82TIJXbgW3ErVh8vk5qCbG1cCAAHb+DWMq0EAyy1bl/JgAghYNGB -+RvKJObTdCrdpYh02KUqBLkSPZHvo6DUJFN37MXDpVeQq9VtqRjpKLLwuEfXb0Y7I -+5xEOrR3kYbOaBAWVt3mYZ1t0L/KfY2jVOdU5WFyyB9PhbMdLi1xE801j+GJrwcLa -+xmqvj4UaICRzcPATP86zVM1BBQa+lilkRQes5HyjZzZDiGYudnXhbqmLo/n0cuXo -+QBVVjhzRTMx71Eiiahmiw+U1vGqkHhQNxb13HtN1lcAhUCDrxxeMvrAjYdWpYlpI -+yW3NssPWt1YUHidMBSAJ4KctIf91dyE93aStlxwC/QnyFsZOmcEsBzVCnz9GmWMl -+1/6XzBS1yDUqByklx0TLH+z/sK9A+O2rZAy1mByCYwVxvbOZhnqGxAuToIS+A81v -+5hCjsCiOScVB+cil30YBu0cH85RZ0ILNkHdKdrLLWW4wjphK2nBn2g2i3+ztf+nQ -+ED2pQqZ/rhuW79jcyCZl9kXqe1wOdF0Cwah4N6/3LzIXEEKyEJxNqQwtNc2IVE8C -+AwEAAaOBsjCBrzAJBgNVHRMEAjAAMDoGDCsGAQQBkggJAYJrAQQqDChSZWQgSGF0 -+IEVudGVycHJpc2UgTGludXggZm9yIEFSTSA2NCBCZXRhMBsGDCsGAQQBkggJAYJr -+AgQLDAkxMC4zIEJldGEwGQYMKwYBBAGSCAkBgmsDBAkMB2FhcmNoNjQwLgYMKwYB -+BAGSCAkBgmsEBB4MHHJoZWwtMTAscmhlbC0xMC1iZXRhLWFhcmNoNjQwDQYJKoZI -+hvcNAQELBQADggIBAG97elYADAREgylUKzIDOJtbs8G41TQ/7Wr9u7ND2LR97TPD -+g3wECnVP7ZcFnQmdGpPdOhiwsz2QIS5caHFY6geg/8tjSQ5U8Z9PTCXkPFPEd7qz -+oFTgN6bNTPnrSJonEw1Cj3SzD4zKjvWhwlCg1Yz8KBjijblRiSzLZMO73807U1O5 -+IJ5cPsV7xpnsxmzmFD5CZUojIg9yZptGgeeFkTR1wPM6Pu30KAzzDHlXEcnmRAg4 -+yl34VNEFQTNy2Zf/UlrvqORx91ybhZ3iCkipAQy40zhHo+/v37+gPYfM7+QtKZ83 -+0zP5V3JosOQPnHmED22liqb5XGMIyDXqqHL6VTfYd1aebEHmCxVRt8MePeSMO83A -+WjrsIb2PPiql5NJ+fF1utmBHHbzBWpgDAYy2Iws8BUikx8m2vUiGaGytPF4DI0O3 -+jhQeO6OU0uSEy/n66Bq+HbiB0VTCYVWu56s/Xb2P2FyGAh9UL4qiJJXnJnkDVpnC -++DsMEroGaall+pWo/CtNG7zuEAbhpPHtB9MSlujDPViCtBlaBY52j3mtWdQQZvda -+VSOG9R9e9cwvWtHFCtP2gHqZ0ZYL1HJL413OgseGkybfVoNs4nVqpADDX8dKgNNo -+vuXzqBBASr+VuXuBnLS3fu4cmRb3MRc2ByJBNTE+WE7f89Typ7yJ9YCWG8s0 -+-----END CERTIFICATE----- -diff --git a/repos/system_upgrade/common/files/prod-certs/10.3/419.pem b/repos/system_upgrade/common/files/prod-certs/10.3/419.pem -new file mode 100644 -index 00000000..06dcf21d ---- /dev/null -+++ b/repos/system_upgrade/common/files/prod-certs/10.3/419.pem -@@ -0,0 +1,35 @@ -+-----BEGIN CERTIFICATE----- -+MIIGGjCCBAKgAwIBAgIJALDxRLt/tVCkMA0GCSqGSIb3DQEBCwUAMIGuMQswCQYD -+VQQGEwJVUzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExFjAUBgNVBAoMDVJlZCBI -+YXQsIEluYy4xGDAWBgNVBAsMD1JlZCBIYXQgTmV0d29yazEuMCwGA1UEAwwlUmVk -+IEhhdCBFbnRpdGxlbWVudCBQcm9kdWN0IEF1dGhvcml0eTEkMCIGCSqGSIb3DQEJ -+ARYVY2Etc3VwcG9ydEByZWRoYXQuY29tMB4XDTI2MDExOTE4MjA0N1oXDTQ2MDEx -+OTE4MjA0N1owRDFCMEAGA1UEAww5UmVkIEhhdCBQcm9kdWN0IElEIFthMDgwMGY5 -+YS1lMTYyLTRiNGQtYmZkMS04MTIwNWQ0Yzc2ZGFdMIICIjANBgkqhkiG9w0BAQEF -+AAOCAg8AMIICCgKCAgEAxj9J04z+Ezdyx1U33kFftLv0ntNS1BSeuhoZLDhs18yk -+sepG7hXXtHh2CMFfLZmTjAyL9i1XsxykQpVQdXTGpUF33C2qBQHB5glYs9+d781x -+8p8m8zFxbPcW82TIJXbgW3ErVh8vk5qCbG1cCAAHb+DWMq0EAyy1bl/JgAghYNGB -+RvKJObTdCrdpYh02KUqBLkSPZHvo6DUJFN37MXDpVeQq9VtqRjpKLLwuEfXb0Y7I -+5xEOrR3kYbOaBAWVt3mYZ1t0L/KfY2jVOdU5WFyyB9PhbMdLi1xE801j+GJrwcLa -+xmqvj4UaICRzcPATP86zVM1BBQa+lilkRQes5HyjZzZDiGYudnXhbqmLo/n0cuXo -+QBVVjhzRTMx71Eiiahmiw+U1vGqkHhQNxb13HtN1lcAhUCDrxxeMvrAjYdWpYlpI -+yW3NssPWt1YUHidMBSAJ4KctIf91dyE93aStlxwC/QnyFsZOmcEsBzVCnz9GmWMl -+1/6XzBS1yDUqByklx0TLH+z/sK9A+O2rZAy1mByCYwVxvbOZhnqGxAuToIS+A81v -+5hCjsCiOScVB+cil30YBu0cH85RZ0ILNkHdKdrLLWW4wjphK2nBn2g2i3+ztf+nQ -+ED2pQqZ/rhuW79jcyCZl9kXqe1wOdF0Cwah4N6/3LzIXEEKyEJxNqQwtNc2IVE8C -+AwEAAaOBozCBoDAJBgNVHRMEAjAAMDUGDCsGAQQBkggJAYMjAQQlDCNSZWQgSGF0 -+IEVudGVycHJpc2UgTGludXggZm9yIEFSTSA2NDAWBgwrBgEEAZIICQGDIwIEBgwE -+MTAuMzAZBgwrBgEEAZIICQGDIwMECQwHYWFyY2g2NDApBgwrBgEEAZIICQGDIwQE -+GQwXcmhlbC0xMCxyaGVsLTEwLWFhcmNoNjQwDQYJKoZIhvcNAQELBQADggIBAK16 -+YDfEKnzYJy24cIbGgoc1k8/yRHTH6uXcOTcMrey3Jug2bBjIpgVKw7tUJIus5hyQ -+qcIoLIZFmOIR71viSdBYqAl2Q49creclu9JPWEjmTXCmU9Jxa0YPX1nzgPBgE9E8 -+l/GeU4bjWuQHGJx7tlHOJQVK0TU8LpE9XtEdPZQh9XPIIPPdCou7s8slkouLGoMG -+m4Or6ThhLo2KPUjVwkgkLbOP5ThlLckCbXmKOZfP4FhMOAUlk42FY7SWvfLBJzal -+nY2XS4q4yB2EOx/6B5RciALALg/aP4373ui9CUfzQ0o6ABLdu5fhsyViVNNwDbIv -+zGFFimWX9GzGweBn1fg6QFhefYSxa9lcTREih2wB5QN9WsKPh75BlnM5iraSW3Ln -+W5W2nrbXTDlFrEFvS5t7qKSPJQjZXSYIr6zOOyDySuu0GNG12gmHzUzzcgwNO4bC -+obFCHIulnpJn+dNWCV8MqTTR7sSwzp3Lco9egyKT8QPZh/bN6pLYIS3NhLUoAQYz -+xKTTSBZsGfN5WfvKqozHFWmv5i4Xl/QV3UmxB7Jv1MMTjSsuQdBJ50wyX7brkK1G -+ezO9onaS7XcwFZp59NeAqsyoqyJH1uKAxwuernv5uU1G0lLAMlFj/UVyA0BUUtCO -+ViawI/DBLNPXK83W961HMAElx73V5A2E8aYVoWEV -+-----END CERTIFICATE----- -diff --git a/repos/system_upgrade/common/files/prod-certs/10.3/433.pem b/repos/system_upgrade/common/files/prod-certs/10.3/433.pem -new file mode 100644 -index 00000000..62c4b25e ---- /dev/null -+++ b/repos/system_upgrade/common/files/prod-certs/10.3/433.pem -@@ -0,0 +1,35 @@ -+-----BEGIN CERTIFICATE----- -+MIIGLDCCBBSgAwIBAgIJALDxRLt/tVEAMA0GCSqGSIb3DQEBCwUAMIGuMQswCQYD -+VQQGEwJVUzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExFjAUBgNVBAoMDVJlZCBI -+YXQsIEluYy4xGDAWBgNVBAsMD1JlZCBIYXQgTmV0d29yazEuMCwGA1UEAwwlUmVk -+IEhhdCBFbnRpdGxlbWVudCBQcm9kdWN0IEF1dGhvcml0eTEkMCIGCSqGSIb3DQEJ -+ARYVY2Etc3VwcG9ydEByZWRoYXQuY29tMB4XDTI2MDIxOTEwMzczOVoXDTQ2MDIx -+OTEwMzczOVowRDFCMEAGA1UEAww5UmVkIEhhdCBQcm9kdWN0IElEIFsxNzI4NTFi -+YS03YzhlLTQ2YTYtOGVlNi0xZmExY2YwMDRlOTddMIICIjANBgkqhkiG9w0BAQEF -+AAOCAg8AMIICCgKCAgEAxj9J04z+Ezdyx1U33kFftLv0ntNS1BSeuhoZLDhs18yk -+sepG7hXXtHh2CMFfLZmTjAyL9i1XsxykQpVQdXTGpUF33C2qBQHB5glYs9+d781x -+8p8m8zFxbPcW82TIJXbgW3ErVh8vk5qCbG1cCAAHb+DWMq0EAyy1bl/JgAghYNGB -+RvKJObTdCrdpYh02KUqBLkSPZHvo6DUJFN37MXDpVeQq9VtqRjpKLLwuEfXb0Y7I -+5xEOrR3kYbOaBAWVt3mYZ1t0L/KfY2jVOdU5WFyyB9PhbMdLi1xE801j+GJrwcLa -+xmqvj4UaICRzcPATP86zVM1BBQa+lilkRQes5HyjZzZDiGYudnXhbqmLo/n0cuXo -+QBVVjhzRTMx71Eiiahmiw+U1vGqkHhQNxb13HtN1lcAhUCDrxxeMvrAjYdWpYlpI -+yW3NssPWt1YUHidMBSAJ4KctIf91dyE93aStlxwC/QnyFsZOmcEsBzVCnz9GmWMl -+1/6XzBS1yDUqByklx0TLH+z/sK9A+O2rZAy1mByCYwVxvbOZhnqGxAuToIS+A81v -+5hCjsCiOScVB+cil30YBu0cH85RZ0ILNkHdKdrLLWW4wjphK2nBn2g2i3+ztf+nQ -+ED2pQqZ/rhuW79jcyCZl9kXqe1wOdF0Cwah4N6/3LzIXEEKyEJxNqQwtNc2IVE8C -+AwEAAaOBtTCBsjAJBgNVHRMEAjAAMEEGDCsGAQQBkggJAYMxAQQxDC9SZWQgSGF0 -+IEVudGVycHJpc2UgTGludXggZm9yIElCTSB6IFN5c3RlbXMgQmV0YTAbBgwrBgEE -+AZIICQGDMQIECwwJMTAuMyBCZXRhMBcGDCsGAQQBkggJAYMxAwQHDAVzMzkweDAs -+BgwrBgEEAZIICQGDMQQEHAwacmhlbC0xMCxyaGVsLTEwLWJldGEtczM5MHgwDQYJ -+KoZIhvcNAQELBQADggIBAE8oESBqJk1DJwYFe6gOSdrWHk1JDkNfqXHqJoyv7BLi -+KpqVIwEKO4b4Yhv4Hk9RmTgGme4nOo3QlyLSuA5aiQj9smF1b32ZgJmvqJQc5HhI -+rUo8OzFpIQntBNgTu4uL7Co38fXYwogMTfFF3pA2Vi5xA7THz22DZ5UlZMDzF2QT -++OrswAPqMwhWzEyR9yVhPSqN/45YL2FxAqvNgVey+bzZymxCLMS2Sl/Bt2p9foq9 -+sXC+v7C7OlVQl/c/8OW6wVp4bZrP1Q93dNptP2UJnwgiP8I1fCknntBRzajsve6I -+bbsfAyhpUrjpOm8bE0BO2EgGw2C9O8gDPQmQbgGF3sHWtMaog/bhe3u14ZMLoR6j -+wGsYSo0cFfEbksHZfm/JSWL0ILvqaBi65snItdhjGlIx3/I/MvpkiYdrzmowFrMs -+SsQVRaDAhC+0rD48Upt9K5yyuf23Yzae5JKgw5BLiNtdoqJjyj0KoyBeKxfX/f4D -+FfBj63Il7EuHLJI3MgLaKejrHY/YBIPtc2fP5fZfcNNHgMpVlBgLK7Huz/Vnp0tk -+TTyVlzyMSFQDedyws90iulm5ZFjpzkVhr1NUZosbFnmURzCD3a09BuKEMInVaGSQ -+F9LVFcoUuot+U0jqueWCw90XCx7/8bVyTx4uPfj8uSCLT5XV9SQ2gE3qGXe6uozK -+-----END CERTIFICATE----- -diff --git a/repos/system_upgrade/common/files/prod-certs/10.3/479.pem b/repos/system_upgrade/common/files/prod-certs/10.3/479.pem -new file mode 100644 -index 00000000..a3061d14 ---- /dev/null -+++ b/repos/system_upgrade/common/files/prod-certs/10.3/479.pem -@@ -0,0 +1,35 @@ -+-----BEGIN CERTIFICATE----- -+MIIGGDCCBACgAwIBAgIJALDxRLt/tVCnMA0GCSqGSIb3DQEBCwUAMIGuMQswCQYD -+VQQGEwJVUzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExFjAUBgNVBAoMDVJlZCBI -+YXQsIEluYy4xGDAWBgNVBAsMD1JlZCBIYXQgTmV0d29yazEuMCwGA1UEAwwlUmVk -+IEhhdCBFbnRpdGxlbWVudCBQcm9kdWN0IEF1dGhvcml0eTEkMCIGCSqGSIb3DQEJ -+ARYVY2Etc3VwcG9ydEByZWRoYXQuY29tMB4XDTI2MDExOTE4MjA0N1oXDTQ2MDEx -+OTE4MjA0N1owRDFCMEAGA1UEAww5UmVkIEhhdCBQcm9kdWN0IElEIFswOWVlMmQx -+Ni02MDM0LTQ1NWItYmQzMi1kZDFjNTc3YzY4OWZdMIICIjANBgkqhkiG9w0BAQEF -+AAOCAg8AMIICCgKCAgEAxj9J04z+Ezdyx1U33kFftLv0ntNS1BSeuhoZLDhs18yk -+sepG7hXXtHh2CMFfLZmTjAyL9i1XsxykQpVQdXTGpUF33C2qBQHB5glYs9+d781x -+8p8m8zFxbPcW82TIJXbgW3ErVh8vk5qCbG1cCAAHb+DWMq0EAyy1bl/JgAghYNGB -+RvKJObTdCrdpYh02KUqBLkSPZHvo6DUJFN37MXDpVeQq9VtqRjpKLLwuEfXb0Y7I -+5xEOrR3kYbOaBAWVt3mYZ1t0L/KfY2jVOdU5WFyyB9PhbMdLi1xE801j+GJrwcLa -+xmqvj4UaICRzcPATP86zVM1BBQa+lilkRQes5HyjZzZDiGYudnXhbqmLo/n0cuXo -+QBVVjhzRTMx71Eiiahmiw+U1vGqkHhQNxb13HtN1lcAhUCDrxxeMvrAjYdWpYlpI -+yW3NssPWt1YUHidMBSAJ4KctIf91dyE93aStlxwC/QnyFsZOmcEsBzVCnz9GmWMl -+1/6XzBS1yDUqByklx0TLH+z/sK9A+O2rZAy1mByCYwVxvbOZhnqGxAuToIS+A81v -+5hCjsCiOScVB+cil30YBu0cH85RZ0ILNkHdKdrLLWW4wjphK2nBn2g2i3+ztf+nQ -+ED2pQqZ/rhuW79jcyCZl9kXqe1wOdF0Cwah4N6/3LzIXEEKyEJxNqQwtNc2IVE8C -+AwEAAaOBoTCBnjAJBgNVHRMEAjAAMDUGDCsGAQQBkggJAYNfAQQlDCNSZWQgSGF0 -+IEVudGVycHJpc2UgTGludXggZm9yIHg4Nl82NDAWBgwrBgEEAZIICQGDXwIEBgwE -+MTAuMzAYBgwrBgEEAZIICQGDXwMECAwGeDg2XzY0MCgGDCsGAQQBkggJAYNfBAQY -+DBZyaGVsLTEwLHJoZWwtMTAteDg2XzY0MA0GCSqGSIb3DQEBCwUAA4ICAQBSUGgZ -+gPVDWm2ioWlWvM6cLVx4LeLT+oDiG96VFsB/DNTRERKdT8bxM1Cnk4O42JoZcRMU -+Hn4LxCNQ1jXuQvKMJds5us9XOViPJ9h3JuLcMTnexBcYedRqmXRact2xvNK8NDFQ -+tNGh4sbe2qBRX+wN1wRgEu/FAz/QtzGyL9BTKja2EFbhesfKod69AAkN8nQ7aIrd -+7mLaTKkws8sFuD77Z5eBNj90WxOnC+pa1wVI/XlKDpxtaMJgxVls36i2lefN9JH1 -+mUIopriBDpld489gdo40qO7kJK8rSRxttZqkIfZQRw6jZIV1vcw+JfI0fCm0OLAP -+ynT76/zq6MdMjXUYtMpmniq1hvmnmQduUlytDZ0jRIXfTZebare7AzL/NPgvDGF3 -+OhmpB9JSdaVzjYoNe/621MObespjh5et7sMyQDnDYm4M0aMRK0mIkzGP8ROEcsQF -+buVJLxYPvqs73MNiwp9UJdyeVByQjTC5IwDMpwHWV3z67d82YbMCH5U3rtNLblw0 -+pBAujM8gQYvP7EhvuyRePUA44n1Ub5pewd0oxZKozM+mC/U7zhLQsnX4Y/qJQHOO -+FIgiejsjrqFNSivly9f0StkI3ZC/MYGRuCADfp/l4vW+fhVnMEnIFF1qIX9Yy39F -+Dy8aW/QtjTXMRqFi/hokF6Q/trZiyAsVt98UZg== -+-----END CERTIFICATE----- -diff --git a/repos/system_upgrade/common/files/prod-certs/10.3/486.pem b/repos/system_upgrade/common/files/prod-certs/10.3/486.pem -new file mode 100644 -index 00000000..ec1125a1 ---- /dev/null -+++ b/repos/system_upgrade/common/files/prod-certs/10.3/486.pem -@@ -0,0 +1,35 @@ -+-----BEGIN CERTIFICATE----- -+MIIGJzCCBA+gAwIBAgIJALDxRLt/tVEBMA0GCSqGSIb3DQEBCwUAMIGuMQswCQYD -+VQQGEwJVUzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExFjAUBgNVBAoMDVJlZCBI -+YXQsIEluYy4xGDAWBgNVBAsMD1JlZCBIYXQgTmV0d29yazEuMCwGA1UEAwwlUmVk -+IEhhdCBFbnRpdGxlbWVudCBQcm9kdWN0IEF1dGhvcml0eTEkMCIGCSqGSIb3DQEJ -+ARYVY2Etc3VwcG9ydEByZWRoYXQuY29tMB4XDTI2MDIxOTEwMzczOVoXDTQ2MDIx -+OTEwMzczOVowRDFCMEAGA1UEAww5UmVkIEhhdCBQcm9kdWN0IElEIFs3M2M1Yjhi -+My0wYTZkLTRmNmEtOGZkNS04ZTYyYzM3NTUzMjJdMIICIjANBgkqhkiG9w0BAQEF -+AAOCAg8AMIICCgKCAgEAxj9J04z+Ezdyx1U33kFftLv0ntNS1BSeuhoZLDhs18yk -+sepG7hXXtHh2CMFfLZmTjAyL9i1XsxykQpVQdXTGpUF33C2qBQHB5glYs9+d781x -+8p8m8zFxbPcW82TIJXbgW3ErVh8vk5qCbG1cCAAHb+DWMq0EAyy1bl/JgAghYNGB -+RvKJObTdCrdpYh02KUqBLkSPZHvo6DUJFN37MXDpVeQq9VtqRjpKLLwuEfXb0Y7I -+5xEOrR3kYbOaBAWVt3mYZ1t0L/KfY2jVOdU5WFyyB9PhbMdLi1xE801j+GJrwcLa -+xmqvj4UaICRzcPATP86zVM1BBQa+lilkRQes5HyjZzZDiGYudnXhbqmLo/n0cuXo -+QBVVjhzRTMx71Eiiahmiw+U1vGqkHhQNxb13HtN1lcAhUCDrxxeMvrAjYdWpYlpI -+yW3NssPWt1YUHidMBSAJ4KctIf91dyE93aStlxwC/QnyFsZOmcEsBzVCnz9GmWMl -+1/6XzBS1yDUqByklx0TLH+z/sK9A+O2rZAy1mByCYwVxvbOZhnqGxAuToIS+A81v -+5hCjsCiOScVB+cil30YBu0cH85RZ0ILNkHdKdrLLWW4wjphK2nBn2g2i3+ztf+nQ -+ED2pQqZ/rhuW79jcyCZl9kXqe1wOdF0Cwah4N6/3LzIXEEKyEJxNqQwtNc2IVE8C -+AwEAAaOBsDCBrTAJBgNVHRMEAjAAMDoGDCsGAQQBkggJAYNmAQQqDChSZWQgSGF0 -+IEVudGVycHJpc2UgTGludXggZm9yIHg4Nl82NCBCZXRhMBsGDCsGAQQBkggJAYNm -+AgQLDAkxMC4zIEJldGEwGAYMKwYBBAGSCAkBg2YDBAgMBng4Nl82NDAtBgwrBgEE -+AZIICQGDZgQEHQwbcmhlbC0xMCxyaGVsLTEwLWJldGEteDg2XzY0MA0GCSqGSIb3 -+DQEBCwUAA4ICAQBw3b6RD5lMEEugZlTOPPfgSBFU3pj5LmaRKSKPGV3Icq/rF4i1 -+kNyaOReX7M4klZ0cGQJfpy77W9P97T7srcbOXwKXXW8N0PViC49RqlvD+IgOx3ei -+Ga3zq5ynFyqdnEg3UlG7XMpHW5xZa9+8sw/tBgoVnXKCPq6iy4pU9wy95PbTGhBV -+3MfMqvw6MpTyVa3RNcFwqn/MN8ZbdpBwXh3W+7s+7opZmNoewxc3qZe/Yui1fK1s -+ldnJaWLlRPpRYrvuSMflWiD9Ju1cMi2dPAQ0R9cNRuTZ6i8OvrPJKbXnn/5g2GjY -+M5EZ/mjNAHXQTO1H1P1/vTUcHqO9NlDbjNQJ/Otz6TzbBQQ88px8NQvygaq8aoPF -+t/xTXQbVHSC+OmeVUgElYW8o0ITxvxo1/k8gu/np8jPwtXdtLb9Xpna/bd+Vua2Q -+OVO6GHl4YXkOXgnX1KJWgCKR77q07JUW5vpb8pFMHGCMBDYwVlRVWqwQy5qRmFXU -+ILTwr4ue22D5xB01OgapYGw92nmeojGZ6a7bkIhfp8i6NhvwkSXKuyzZmRkdpNm1 -+qydvt0JICGMAJ3LNqHAIjlc8I+eiVa3EbWNbVSBOM/E59PJ4cmmoIkzRbSJrW+8/ -+ys5fj/MB7KL8DcWkvRtRaYZqfrkslnoKa8f9qT13OeLN6j1woOy0pufRNA== -+-----END CERTIFICATE----- -diff --git a/repos/system_upgrade/common/files/prod-certs/10.3/72.pem b/repos/system_upgrade/common/files/prod-certs/10.3/72.pem -new file mode 100644 -index 00000000..af965bd5 ---- /dev/null -+++ b/repos/system_upgrade/common/files/prod-certs/10.3/72.pem -@@ -0,0 +1,35 @@ -+-----BEGIN CERTIFICATE----- -+MIIGGTCCBAGgAwIBAgIJALDxRLt/tVCmMA0GCSqGSIb3DQEBCwUAMIGuMQswCQYD -+VQQGEwJVUzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExFjAUBgNVBAoMDVJlZCBI -+YXQsIEluYy4xGDAWBgNVBAsMD1JlZCBIYXQgTmV0d29yazEuMCwGA1UEAwwlUmVk -+IEhhdCBFbnRpdGxlbWVudCBQcm9kdWN0IEF1dGhvcml0eTEkMCIGCSqGSIb3DQEJ -+ARYVY2Etc3VwcG9ydEByZWRoYXQuY29tMB4XDTI2MDExOTE4MjA0N1oXDTQ2MDEx -+OTE4MjA0N1owRDFCMEAGA1UEAww5UmVkIEhhdCBQcm9kdWN0IElEIFtkMGNlYzg1 -+MC1iNjk3LTQyMWQtYjhmZi1hNjczM2ZkODJmMDZdMIICIjANBgkqhkiG9w0BAQEF -+AAOCAg8AMIICCgKCAgEAxj9J04z+Ezdyx1U33kFftLv0ntNS1BSeuhoZLDhs18yk -+sepG7hXXtHh2CMFfLZmTjAyL9i1XsxykQpVQdXTGpUF33C2qBQHB5glYs9+d781x -+8p8m8zFxbPcW82TIJXbgW3ErVh8vk5qCbG1cCAAHb+DWMq0EAyy1bl/JgAghYNGB -+RvKJObTdCrdpYh02KUqBLkSPZHvo6DUJFN37MXDpVeQq9VtqRjpKLLwuEfXb0Y7I -+5xEOrR3kYbOaBAWVt3mYZ1t0L/KfY2jVOdU5WFyyB9PhbMdLi1xE801j+GJrwcLa -+xmqvj4UaICRzcPATP86zVM1BBQa+lilkRQes5HyjZzZDiGYudnXhbqmLo/n0cuXo -+QBVVjhzRTMx71Eiiahmiw+U1vGqkHhQNxb13HtN1lcAhUCDrxxeMvrAjYdWpYlpI -+yW3NssPWt1YUHidMBSAJ4KctIf91dyE93aStlxwC/QnyFsZOmcEsBzVCnz9GmWMl -+1/6XzBS1yDUqByklx0TLH+z/sK9A+O2rZAy1mByCYwVxvbOZhnqGxAuToIS+A81v -+5hCjsCiOScVB+cil30YBu0cH85RZ0ILNkHdKdrLLWW4wjphK2nBn2g2i3+ztf+nQ -+ED2pQqZ/rhuW79jcyCZl9kXqe1wOdF0Cwah4N6/3LzIXEEKyEJxNqQwtNc2IVE8C -+AwEAAaOBojCBnzAJBgNVHRMEAjAAMDsGCysGAQQBkggJAUgBBCwMKlJlZCBIYXQg -+RW50ZXJwcmlzZSBMaW51eCBmb3IgSUJNIHogU3lzdGVtczAVBgsrBgEEAZIICQFI -+AgQGDAQxMC4zMBYGCysGAQQBkggJAUgDBAcMBXMzOTB4MCYGCysGAQQBkggJAUgE -+BBcMFXJoZWwtMTAscmhlbC0xMC1zMzkweDANBgkqhkiG9w0BAQsFAAOCAgEAvObN -+JmEuBIppsXYeD6C8Gr2xSyiKy4cfs9AFeCYdVA/ETGWewdKMUXT9e+LyqwSPFPmO -+PD+bIHsr27hlU+Yy1UHgkGubWv5ZbRxRHsEASqCahKNv4+z6CzoIrM4s2X7CK7Mz -+n5APXrpUQG5RtktP3pVkaZo7rGSHv+lHNLe5uFbMi86xBBGPwJQzEmryhmjrcmZA -+um6nCG4j1wxH4pBqgY8t/UZ2maQQUI1XiE+7a4Gk7CmzlFPRUw46gY4+W9DMskC8 -+r2Z4gCKAdq4WSLdJY/Gj+PsU+BUbDNZBGzzybiiWdIQfxvj+zjhKKB0v6n5KGGFW -+We7Virj70o8NZIb6F5sUBY9xSJDZ/aHhZul+Y30mFfqDxNqIBTFiUHuZ0/nPdGEO -+J2CWr3YsdjMWefueVhaNfEOBCnwx1BVO4Wt/r6wHWAz0FnfWveuw//WoUCxi7bD4 -+XC9M6jK2JsnQ8bkJJP4m4NA5LjXGx+TDQsOvntq+UGZ2/BDtt27/c0oRWjjYTHVJ -+GWMqYvLDGuaf8ieYU0wF+k/kF1Ph0F+Wx7XVypLbC04NUftKi5ZoQuBzHhPj+41t -+yFeh/h1oIMR2Un9D20kWcVzgPuisYmUGHzjN3aTVbwLYOdJz0oLKIePRx8/uHDOU -+hfyGGdFJGmv3K0/kmiJBs8HZL8bp2B9Geb09Vt0= -+-----END CERTIFICATE----- -diff --git a/repos/system_upgrade/common/files/prod-certs/9.9/279.pem b/repos/system_upgrade/common/files/prod-certs/9.9/279.pem -new file mode 100644 -index 00000000..cb6f4fc6 ---- /dev/null -+++ b/repos/system_upgrade/common/files/prod-certs/9.9/279.pem -@@ -0,0 +1,35 @@ -+-----BEGIN CERTIFICATE----- -+MIIGJTCCBA2gAwIBAgIJALDxRLt/tVDNMA0GCSqGSIb3DQEBCwUAMIGuMQswCQYD -+VQQGEwJVUzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExFjAUBgNVBAoMDVJlZCBI -+YXQsIEluYy4xGDAWBgNVBAsMD1JlZCBIYXQgTmV0d29yazEuMCwGA1UEAwwlUmVk -+IEhhdCBFbnRpdGxlbWVudCBQcm9kdWN0IEF1dGhvcml0eTEkMCIGCSqGSIb3DQEJ -+ARYVY2Etc3VwcG9ydEByZWRoYXQuY29tMB4XDTI2MDExOTE4MjMxMVoXDTQ2MDEx -+OTE4MjMxMVowRDFCMEAGA1UEAww5UmVkIEhhdCBQcm9kdWN0IElEIFtiMjk3Y2I1 -+Ni03NTM0LTQxNGUtODE3My04Yzk1MmNhMzZlZWRdMIICIjANBgkqhkiG9w0BAQEF -+AAOCAg8AMIICCgKCAgEAxj9J04z+Ezdyx1U33kFftLv0ntNS1BSeuhoZLDhs18yk -+sepG7hXXtHh2CMFfLZmTjAyL9i1XsxykQpVQdXTGpUF33C2qBQHB5glYs9+d781x -+8p8m8zFxbPcW82TIJXbgW3ErVh8vk5qCbG1cCAAHb+DWMq0EAyy1bl/JgAghYNGB -+RvKJObTdCrdpYh02KUqBLkSPZHvo6DUJFN37MXDpVeQq9VtqRjpKLLwuEfXb0Y7I -+5xEOrR3kYbOaBAWVt3mYZ1t0L/KfY2jVOdU5WFyyB9PhbMdLi1xE801j+GJrwcLa -+xmqvj4UaICRzcPATP86zVM1BBQa+lilkRQes5HyjZzZDiGYudnXhbqmLo/n0cuXo -+QBVVjhzRTMx71Eiiahmiw+U1vGqkHhQNxb13HtN1lcAhUCDrxxeMvrAjYdWpYlpI -+yW3NssPWt1YUHidMBSAJ4KctIf91dyE93aStlxwC/QnyFsZOmcEsBzVCnz9GmWMl -+1/6XzBS1yDUqByklx0TLH+z/sK9A+O2rZAy1mByCYwVxvbOZhnqGxAuToIS+A81v -+5hCjsCiOScVB+cil30YBu0cH85RZ0ILNkHdKdrLLWW4wjphK2nBn2g2i3+ztf+nQ -+ED2pQqZ/rhuW79jcyCZl9kXqe1wOdF0Cwah4N6/3LzIXEEKyEJxNqQwtNc2IVE8C -+AwEAAaOBrjCBqzAJBgNVHRMEAjAAMEMGDCsGAQQBkggJAYIXAQQzDDFSZWQgSGF0 -+IEVudGVycHJpc2UgTGludXggZm9yIFBvd2VyLCBsaXR0bGUgZW5kaWFuMBUGDCsG -+AQQBkggJAYIXAgQFDAM5LjkwGQYMKwYBBAGSCAkBghcDBAkMB3BwYzY0bGUwJwYM -+KwYBBAGSCAkBghcEBBcMFXJoZWwtOSxyaGVsLTktcHBjNjRsZTANBgkqhkiG9w0B -+AQsFAAOCAgEArnuDgKvwfomGhWQiWrpRqwOEuh47QvnTqdmgwiqDKrD9LX5PGKqU -+5mmEUen7pxop465hRny0+FOylsmuKRuZ0R0uUD1VCFXh5sP/TwkPZsgqdCK0hxMT -+si+vp0j6l0z8PwJoskFAl0YUdPnic7QTigRJrS4/HSMiIeABml6AD965+p45vbKI -+4TvB8tA0NkVYjFmyoHZomopyj1GfXavn4yd1iEDjSATO3ElDV56Jr1d6QLNanVPc -+j3+clP5dsq29DsNx6mwK/yJWRAnsgV3pJhhHLag4eN560REJLVDuFhvw+wk+rhGr -+2UGNJAdWgIt0wV3EIsu5eQtWgzKCaLooqxkLdGXWvINkwIXMGfQtO7qyC+GLba9q -+Kwdk9OoIwH+L20vCH5clCveVDA2dtTO/HZjokbivJaOBeCl7y9y38ePr6q90Xccw -+6JQVLqej8hmJdZh9V+p7V1Y6Aa2IxTbQyjITF/zwBJlbe6lGApthUDk44fgo1NIP -+/chrC4bPlzuigS2BPB4lnoeWn0AutWpBrbmMTCeL67rxKZ4rUD0Yb+19yHKbSSy9 -+PJQ6IOM0b+fWXIrvgCx108RET6gD+RDskgmrhOxm5mE1Xbff1CKVFco4+mrWpzLQ -+1VoOL3HLfyA0NFLzpIUY4EEaj4mW+rHekJrmcvCY+Sr7qOmLXPNSV3I= -+-----END CERTIFICATE----- -diff --git a/repos/system_upgrade/common/files/prod-certs/9.9/362.pem b/repos/system_upgrade/common/files/prod-certs/9.9/362.pem -new file mode 100644 -index 00000000..a3e4fddf ---- /dev/null -+++ b/repos/system_upgrade/common/files/prod-certs/9.9/362.pem -@@ -0,0 +1,36 @@ -+-----BEGIN CERTIFICATE----- -+MIIGNDCCBBygAwIBAgIJALDxRLt/tVDnMA0GCSqGSIb3DQEBCwUAMIGuMQswCQYD -+VQQGEwJVUzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExFjAUBgNVBAoMDVJlZCBI -+YXQsIEluYy4xGDAWBgNVBAsMD1JlZCBIYXQgTmV0d29yazEuMCwGA1UEAwwlUmVk -+IEhhdCBFbnRpdGxlbWVudCBQcm9kdWN0IEF1dGhvcml0eTEkMCIGCSqGSIb3DQEJ -+ARYVY2Etc3VwcG9ydEByZWRoYXQuY29tMB4XDTI2MDIxOTEwMzYwMVoXDTQ2MDIx -+OTEwMzYwMVowRDFCMEAGA1UEAww5UmVkIEhhdCBQcm9kdWN0IElEIFsyMmE5NjIx -+Zi1jZDg3LTQ1M2EtOGVhYS1hZTU3MTI0YzgwMThdMIICIjANBgkqhkiG9w0BAQEF -+AAOCAg8AMIICCgKCAgEAxj9J04z+Ezdyx1U33kFftLv0ntNS1BSeuhoZLDhs18yk -+sepG7hXXtHh2CMFfLZmTjAyL9i1XsxykQpVQdXTGpUF33C2qBQHB5glYs9+d781x -+8p8m8zFxbPcW82TIJXbgW3ErVh8vk5qCbG1cCAAHb+DWMq0EAyy1bl/JgAghYNGB -+RvKJObTdCrdpYh02KUqBLkSPZHvo6DUJFN37MXDpVeQq9VtqRjpKLLwuEfXb0Y7I -+5xEOrR3kYbOaBAWVt3mYZ1t0L/KfY2jVOdU5WFyyB9PhbMdLi1xE801j+GJrwcLa -+xmqvj4UaICRzcPATP86zVM1BBQa+lilkRQes5HyjZzZDiGYudnXhbqmLo/n0cuXo -+QBVVjhzRTMx71Eiiahmiw+U1vGqkHhQNxb13HtN1lcAhUCDrxxeMvrAjYdWpYlpI -+yW3NssPWt1YUHidMBSAJ4KctIf91dyE93aStlxwC/QnyFsZOmcEsBzVCnz9GmWMl -+1/6XzBS1yDUqByklx0TLH+z/sK9A+O2rZAy1mByCYwVxvbOZhnqGxAuToIS+A81v -+5hCjsCiOScVB+cil30YBu0cH85RZ0ILNkHdKdrLLWW4wjphK2nBn2g2i3+ztf+nQ -+ED2pQqZ/rhuW79jcyCZl9kXqe1wOdF0Cwah4N6/3LzIXEEKyEJxNqQwtNc2IVE8C -+AwEAAaOBvTCBujAJBgNVHRMEAjAAMEgGDCsGAQQBkggJAYJqAQQ4DDZSZWQgSGF0 -+IEVudGVycHJpc2UgTGludXggZm9yIFBvd2VyLCBsaXR0bGUgZW5kaWFuIEJldGEw -+GgYMKwYBBAGSCAkBgmoCBAoMCDkuOSBCZXRhMBkGDCsGAQQBkggJAYJqAwQJDAdw -+cGM2NGxlMCwGDCsGAQQBkggJAYJqBAQcDBpyaGVsLTkscmhlbC05LWJldGEtcHBj -+NjRsZTANBgkqhkiG9w0BAQsFAAOCAgEAv76TVAUrltsb5cbSHPKehKND/s5HNF+s -+WxiNebDXNGZvaePl3acr7oZ4BRDyYwxgsxX7sm/zOG/o5vfxTWY8vXNnD+f0UA92 -+28Xdt8o8V8Rl9A+l2r40uwtrILh7++3+NI3BD+mqRh3yWV6dMuB3wjPsaEOBphFf -+LmamvERK9IaqDnxtWhYJkhne+O5ifZ3m3XkBJ3LdZEnz7dQD/AOl48L02PNljCPv -+df+UiKmeQEBUwFCKE4fusw6YKz86V0JvMs3WpkJOL9e/SaseLfyLIU889s3M2E7Q -+aE4hFO5nTNHFuHQKztrENyE+en8N4Q7QGwCTZSL6ZBXV54CUrSdewg9ud5aK2J3g -+scJ0BLVe5o3w0TXjbRWNwRQvODp0Z46C5CTuE2dtCdzFue01zooS853NfH0O6eor -+SQfWlNZe0uE7C1E5RDe12Ts3SALPJgro1WsqUorOaFWRtzfD1Ouj0/ACAUT/zHRz -+pSP78rMyMYoATy1jXrUcaKa4AWDdiaR4j5hEJS5mcftmKawacUALZprfKI25kbSe -+YLwt6sWv9H17+jQIZT5YvJSMmx45SRctLFF3P6d8oBc65se/tq01yBxdJAvjiMQm -+tj3jc0X2hwCTYOZVdvPc4yAked73pZLyfc2+wzUUeiiMKAOSIvgzFJvmplhjjMn+ -++tMhv83qrzw= -+-----END CERTIFICATE----- -diff --git a/repos/system_upgrade/common/files/prod-certs/9.9/363.pem b/repos/system_upgrade/common/files/prod-certs/9.9/363.pem -new file mode 100644 -index 00000000..92e32c0e ---- /dev/null -+++ b/repos/system_upgrade/common/files/prod-certs/9.9/363.pem -@@ -0,0 +1,35 @@ -+-----BEGIN CERTIFICATE----- -+MIIGJjCCBA6gAwIBAgIJALDxRLt/tVDmMA0GCSqGSIb3DQEBCwUAMIGuMQswCQYD -+VQQGEwJVUzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExFjAUBgNVBAoMDVJlZCBI -+YXQsIEluYy4xGDAWBgNVBAsMD1JlZCBIYXQgTmV0d29yazEuMCwGA1UEAwwlUmVk -+IEhhdCBFbnRpdGxlbWVudCBQcm9kdWN0IEF1dGhvcml0eTEkMCIGCSqGSIb3DQEJ -+ARYVY2Etc3VwcG9ydEByZWRoYXQuY29tMB4XDTI2MDIxOTEwMzYwMFoXDTQ2MDIx -+OTEwMzYwMFowRDFCMEAGA1UEAww5UmVkIEhhdCBQcm9kdWN0IElEIFtmNjIxZDhj -+ZC0wNjFhLTQ4ZDEtYjA1NS0xN2RiMjA1NWRiM2VdMIICIjANBgkqhkiG9w0BAQEF -+AAOCAg8AMIICCgKCAgEAxj9J04z+Ezdyx1U33kFftLv0ntNS1BSeuhoZLDhs18yk -+sepG7hXXtHh2CMFfLZmTjAyL9i1XsxykQpVQdXTGpUF33C2qBQHB5glYs9+d781x -+8p8m8zFxbPcW82TIJXbgW3ErVh8vk5qCbG1cCAAHb+DWMq0EAyy1bl/JgAghYNGB -+RvKJObTdCrdpYh02KUqBLkSPZHvo6DUJFN37MXDpVeQq9VtqRjpKLLwuEfXb0Y7I -+5xEOrR3kYbOaBAWVt3mYZ1t0L/KfY2jVOdU5WFyyB9PhbMdLi1xE801j+GJrwcLa -+xmqvj4UaICRzcPATP86zVM1BBQa+lilkRQes5HyjZzZDiGYudnXhbqmLo/n0cuXo -+QBVVjhzRTMx71Eiiahmiw+U1vGqkHhQNxb13HtN1lcAhUCDrxxeMvrAjYdWpYlpI -+yW3NssPWt1YUHidMBSAJ4KctIf91dyE93aStlxwC/QnyFsZOmcEsBzVCnz9GmWMl -+1/6XzBS1yDUqByklx0TLH+z/sK9A+O2rZAy1mByCYwVxvbOZhnqGxAuToIS+A81v -+5hCjsCiOScVB+cil30YBu0cH85RZ0ILNkHdKdrLLWW4wjphK2nBn2g2i3+ztf+nQ -+ED2pQqZ/rhuW79jcyCZl9kXqe1wOdF0Cwah4N6/3LzIXEEKyEJxNqQwtNc2IVE8C -+AwEAAaOBrzCBrDAJBgNVHRMEAjAAMDoGDCsGAQQBkggJAYJrAQQqDChSZWQgSGF0 -+IEVudGVycHJpc2UgTGludXggZm9yIEFSTSA2NCBCZXRhMBoGDCsGAQQBkggJAYJr -+AgQKDAg5LjkgQmV0YTAZBgwrBgEEAZIICQGCawMECQwHYWFyY2g2NDAsBgwrBgEE -+AZIICQGCawQEHAwacmhlbC05LHJoZWwtOS1iZXRhLWFhcmNoNjQwDQYJKoZIhvcN -+AQELBQADggIBAI766OYXjIHfd/zICG8zsEM7PitMyPhoJBr+EQgYCqbTbpc8EmRj -+pxbVtqtusHxFLKctxOepkGXndmWKp3zP+vHhWoMW+o7fN4QRC0LymnGdI04kWjwz -+1uzsiLmz/h2bXUCdiw4kPy77NLWS1rJZgPCuBFsYnoHqHTiw+ETsTv4rgVx+/wBk -+w/gyn63jlUSVOXKr0CA/nlD875B69hlZOWZcqB7kIO8SPsofUI9Gm524jZT0JG8K -+I8QxQ3vgrdP0edEtcNSnNlb59DcVgRX6F08f6YJdmsXAAeeiC0aSsBMiemFZ5G1g -+RRULjXA7C86mTSdSvgoMbAHzl5qL2jfZa1lyOLJhD5eEe+RUxA19YXKn6l+9/Sar -+EjCx4EtmXFEsejnLRdlBwRmkTQZEiEqrIB45lklIvR1AsH5549OlFtwT1F7dnZqb -+llhNdVqIJyfkkZQ5LxIC04PvZRhiO/wF+r0Xm6KEBaReMMolF7puLjU4vvF1qhSd -+hpRsxlmLIT8slgoGMWojDu0ywfqH8e+kDL7xFF7rd4BAYN7yPpi/afdmkxos8gmu -+aJeO6TsqVOzHtdYstQKkjD/wyggd/wyCoisF/lc8Kpa0wKoaC7EATIaCbHM8JgaB -+VZwRdgyiUKAYUIXsJR4o6xw7Vgr+AAcWEQs31lSXz5jNc91l5pEpG8RY -+-----END CERTIFICATE----- -diff --git a/repos/system_upgrade/common/files/prod-certs/9.9/419.pem b/repos/system_upgrade/common/files/prod-certs/9.9/419.pem -new file mode 100644 -index 00000000..28cca770 ---- /dev/null -+++ b/repos/system_upgrade/common/files/prod-certs/9.9/419.pem -@@ -0,0 +1,35 @@ -+-----BEGIN CERTIFICATE----- -+MIIGFzCCA/+gAwIBAgIJALDxRLt/tVDMMA0GCSqGSIb3DQEBCwUAMIGuMQswCQYD -+VQQGEwJVUzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExFjAUBgNVBAoMDVJlZCBI -+YXQsIEluYy4xGDAWBgNVBAsMD1JlZCBIYXQgTmV0d29yazEuMCwGA1UEAwwlUmVk -+IEhhdCBFbnRpdGxlbWVudCBQcm9kdWN0IEF1dGhvcml0eTEkMCIGCSqGSIb3DQEJ -+ARYVY2Etc3VwcG9ydEByZWRoYXQuY29tMB4XDTI2MDExOTE4MjMxMVoXDTQ2MDEx -+OTE4MjMxMVowRDFCMEAGA1UEAww5UmVkIEhhdCBQcm9kdWN0IElEIFs2ZTQ5NTEx -+YS03N2U5LTQ2ZGEtYjlmZC00ZjM5MTExNGRkMDZdMIICIjANBgkqhkiG9w0BAQEF -+AAOCAg8AMIICCgKCAgEAxj9J04z+Ezdyx1U33kFftLv0ntNS1BSeuhoZLDhs18yk -+sepG7hXXtHh2CMFfLZmTjAyL9i1XsxykQpVQdXTGpUF33C2qBQHB5glYs9+d781x -+8p8m8zFxbPcW82TIJXbgW3ErVh8vk5qCbG1cCAAHb+DWMq0EAyy1bl/JgAghYNGB -+RvKJObTdCrdpYh02KUqBLkSPZHvo6DUJFN37MXDpVeQq9VtqRjpKLLwuEfXb0Y7I -+5xEOrR3kYbOaBAWVt3mYZ1t0L/KfY2jVOdU5WFyyB9PhbMdLi1xE801j+GJrwcLa -+xmqvj4UaICRzcPATP86zVM1BBQa+lilkRQes5HyjZzZDiGYudnXhbqmLo/n0cuXo -+QBVVjhzRTMx71Eiiahmiw+U1vGqkHhQNxb13HtN1lcAhUCDrxxeMvrAjYdWpYlpI -+yW3NssPWt1YUHidMBSAJ4KctIf91dyE93aStlxwC/QnyFsZOmcEsBzVCnz9GmWMl -+1/6XzBS1yDUqByklx0TLH+z/sK9A+O2rZAy1mByCYwVxvbOZhnqGxAuToIS+A81v -+5hCjsCiOScVB+cil30YBu0cH85RZ0ILNkHdKdrLLWW4wjphK2nBn2g2i3+ztf+nQ -+ED2pQqZ/rhuW79jcyCZl9kXqe1wOdF0Cwah4N6/3LzIXEEKyEJxNqQwtNc2IVE8C -+AwEAAaOBoDCBnTAJBgNVHRMEAjAAMDUGDCsGAQQBkggJAYMjAQQlDCNSZWQgSGF0 -+IEVudGVycHJpc2UgTGludXggZm9yIEFSTSA2NDAVBgwrBgEEAZIICQGDIwIEBQwD -+OS45MBkGDCsGAQQBkggJAYMjAwQJDAdhYXJjaDY0MCcGDCsGAQQBkggJAYMjBAQX -+DBVyaGVsLTkscmhlbC05LWFhcmNoNjQwDQYJKoZIhvcNAQELBQADggIBAI/SbbIL -+2P+C2n7M4MG3Y5wDItsV2fpcJA9tmf8cLujKwWcXtH/B1vG4zcPL8yC27CXLokUP -+CIGTrRvBbpeGFlL09n57zR5n2npAX9ViIAav//NrZW5gD8oS70Ty8CBsDZs2bAkr -+YFK59o2YEG295Ee/CYCE9rYeOBBNwMnw5VOtQ1p88xPD64bSCnBXvCHqOppsSsoT -+wi456fTBAo4diZoVIboliiCAqGex7GWy42KLdfkbqcDMkd7CEcFWLansGuZ4T1KW -+H7YcMg3iGRh6W1lD7bzPwIxr1cN+st4dHcnhIefWM1GhN/9emauzT0DcPb3O8zHb -+90QS0LlFaW9Za59epnqEE7UMb3IKiVCzSpspXeJLl8C2ebqZ4vpXZa1Lvb323g9T -+WXpPdRXHux9QhqbukBTNq6fAJPUrSmyv669uqxKGDaTx80BychQ0gOwNZwyriUWw -+5bT9uYk1fQvMXDDx990+c/er+jiUrgkQ4pM5k2wYJ6ZoRLMs+FfGsgmCr+faAX7b -+oYb6hNrYf71he6PwvZX1OdBR95FMe8Gp5KhncIuaUakecdmnlQrF1ensZMBpXOkU -+49bLOxRW2qMG82PtE0JjX4SH0R2PPiN+3zzzKx54lwi9sb26R6X+vyj8FGVw6G7Y -+Cq4KPL2SFKWZEQd18KCn7e/Gfy5CqeM4CL+9 -+-----END CERTIFICATE----- -diff --git a/repos/system_upgrade/common/files/prod-certs/9.9/433.pem b/repos/system_upgrade/common/files/prod-certs/9.9/433.pem -new file mode 100644 -index 00000000..0a452280 ---- /dev/null -+++ b/repos/system_upgrade/common/files/prod-certs/9.9/433.pem -@@ -0,0 +1,35 @@ -+-----BEGIN CERTIFICATE----- -+MIIGKTCCBBGgAwIBAgIJALDxRLt/tVDoMA0GCSqGSIb3DQEBCwUAMIGuMQswCQYD -+VQQGEwJVUzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExFjAUBgNVBAoMDVJlZCBI -+YXQsIEluYy4xGDAWBgNVBAsMD1JlZCBIYXQgTmV0d29yazEuMCwGA1UEAwwlUmVk -+IEhhdCBFbnRpdGxlbWVudCBQcm9kdWN0IEF1dGhvcml0eTEkMCIGCSqGSIb3DQEJ -+ARYVY2Etc3VwcG9ydEByZWRoYXQuY29tMB4XDTI2MDIxOTEwMzYwMVoXDTQ2MDIx -+OTEwMzYwMVowRDFCMEAGA1UEAww5UmVkIEhhdCBQcm9kdWN0IElEIFszNjU5Nzli -+Mi0zM2I3LTRkMjUtOWNjZi05NWEyMjEwMThkMWRdMIICIjANBgkqhkiG9w0BAQEF -+AAOCAg8AMIICCgKCAgEAxj9J04z+Ezdyx1U33kFftLv0ntNS1BSeuhoZLDhs18yk -+sepG7hXXtHh2CMFfLZmTjAyL9i1XsxykQpVQdXTGpUF33C2qBQHB5glYs9+d781x -+8p8m8zFxbPcW82TIJXbgW3ErVh8vk5qCbG1cCAAHb+DWMq0EAyy1bl/JgAghYNGB -+RvKJObTdCrdpYh02KUqBLkSPZHvo6DUJFN37MXDpVeQq9VtqRjpKLLwuEfXb0Y7I -+5xEOrR3kYbOaBAWVt3mYZ1t0L/KfY2jVOdU5WFyyB9PhbMdLi1xE801j+GJrwcLa -+xmqvj4UaICRzcPATP86zVM1BBQa+lilkRQes5HyjZzZDiGYudnXhbqmLo/n0cuXo -+QBVVjhzRTMx71Eiiahmiw+U1vGqkHhQNxb13HtN1lcAhUCDrxxeMvrAjYdWpYlpI -+yW3NssPWt1YUHidMBSAJ4KctIf91dyE93aStlxwC/QnyFsZOmcEsBzVCnz9GmWMl -+1/6XzBS1yDUqByklx0TLH+z/sK9A+O2rZAy1mByCYwVxvbOZhnqGxAuToIS+A81v -+5hCjsCiOScVB+cil30YBu0cH85RZ0ILNkHdKdrLLWW4wjphK2nBn2g2i3+ztf+nQ -+ED2pQqZ/rhuW79jcyCZl9kXqe1wOdF0Cwah4N6/3LzIXEEKyEJxNqQwtNc2IVE8C -+AwEAAaOBsjCBrzAJBgNVHRMEAjAAMEEGDCsGAQQBkggJAYMxAQQxDC9SZWQgSGF0 -+IEVudGVycHJpc2UgTGludXggZm9yIElCTSB6IFN5c3RlbXMgQmV0YTAaBgwrBgEE -+AZIICQGDMQIECgwIOS45IEJldGEwFwYMKwYBBAGSCAkBgzEDBAcMBXMzOTB4MCoG -+DCsGAQQBkggJAYMxBAQaDBhyaGVsLTkscmhlbC05LWJldGEtczM5MHgwDQYJKoZI -+hvcNAQELBQADggIBAG+LbhavNNyjFpe73VgrDyFNpzc97qASwH+8LxFDkeNLOABt -+2HLCffvIPtkSHw09NyphSwmLfA8t2QN7t2R+HD7EP0QiM/sybcY6Kra2XwAPawla -+AZvDhWz/ks46szcYBw2FpIXWeXdkZS0qL89iSgWpzcMqXJrUn5n1W0O8dc6zwSXe -+gFUCu51833edg4TEEB0Iek/Wga3f5BS2vhNYae4vAoGSv0IFx4tx0nPdQ7iuDwLF -+uJcrpVtA/gYzxnaFWceydj1/Qy0S6xwdPUkJ4jNnm9lJpFeHKnJfOT3R7YlRbHL1 -+fZYodsMweDf9FD35lTiBRaMOVytZ3Vff7sDwC9p9OpdikaddzIYE+2IZqxzytBgN -+/kTuLWBWebxMeyYTAUsYFjGn2CGCunjvyPzZH7jSSD75MtbwCOA/I1eIP1p6PLXm -+1innvidGqVd6ma+V8CJprOncMzWuFxLFhE59OlYCkGJVB9h2nj1CbSudBeoLWooU -+/O+XBnoVhTPEI8kuNuj6F1A/K6EYysnGMOccGfh5MuZmkep7pKm61pLyjVW7GVek -+VUQusRh1UChQ0ciOcxT41O0Y1RkhUXphscbnfcvwSekw32QnphRK1kL+91rjuv2H -+uhFhs7PFLHJAF+9xSLqbAcwhx1uCO0g00i4rYtBBfF17LhK4MKOULmG0GwzV -+-----END CERTIFICATE----- -diff --git a/repos/system_upgrade/common/files/prod-certs/9.9/479.pem b/repos/system_upgrade/common/files/prod-certs/9.9/479.pem -new file mode 100644 -index 00000000..e2d77de6 ---- /dev/null -+++ b/repos/system_upgrade/common/files/prod-certs/9.9/479.pem -@@ -0,0 +1,35 @@ -+-----BEGIN CERTIFICATE----- -+MIIGFTCCA/2gAwIBAgIJALDxRLt/tVDPMA0GCSqGSIb3DQEBCwUAMIGuMQswCQYD -+VQQGEwJVUzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExFjAUBgNVBAoMDVJlZCBI -+YXQsIEluYy4xGDAWBgNVBAsMD1JlZCBIYXQgTmV0d29yazEuMCwGA1UEAwwlUmVk -+IEhhdCBFbnRpdGxlbWVudCBQcm9kdWN0IEF1dGhvcml0eTEkMCIGCSqGSIb3DQEJ -+ARYVY2Etc3VwcG9ydEByZWRoYXQuY29tMB4XDTI2MDExOTE4MjMxMVoXDTQ2MDEx -+OTE4MjMxMVowRDFCMEAGA1UEAww5UmVkIEhhdCBQcm9kdWN0IElEIFsxYWQyY2Rj -+My1lZjFmLTRmYjgtOWY5Yy1jYTM5YmIxMTY3MmFdMIICIjANBgkqhkiG9w0BAQEF -+AAOCAg8AMIICCgKCAgEAxj9J04z+Ezdyx1U33kFftLv0ntNS1BSeuhoZLDhs18yk -+sepG7hXXtHh2CMFfLZmTjAyL9i1XsxykQpVQdXTGpUF33C2qBQHB5glYs9+d781x -+8p8m8zFxbPcW82TIJXbgW3ErVh8vk5qCbG1cCAAHb+DWMq0EAyy1bl/JgAghYNGB -+RvKJObTdCrdpYh02KUqBLkSPZHvo6DUJFN37MXDpVeQq9VtqRjpKLLwuEfXb0Y7I -+5xEOrR3kYbOaBAWVt3mYZ1t0L/KfY2jVOdU5WFyyB9PhbMdLi1xE801j+GJrwcLa -+xmqvj4UaICRzcPATP86zVM1BBQa+lilkRQes5HyjZzZDiGYudnXhbqmLo/n0cuXo -+QBVVjhzRTMx71Eiiahmiw+U1vGqkHhQNxb13HtN1lcAhUCDrxxeMvrAjYdWpYlpI -+yW3NssPWt1YUHidMBSAJ4KctIf91dyE93aStlxwC/QnyFsZOmcEsBzVCnz9GmWMl -+1/6XzBS1yDUqByklx0TLH+z/sK9A+O2rZAy1mByCYwVxvbOZhnqGxAuToIS+A81v -+5hCjsCiOScVB+cil30YBu0cH85RZ0ILNkHdKdrLLWW4wjphK2nBn2g2i3+ztf+nQ -+ED2pQqZ/rhuW79jcyCZl9kXqe1wOdF0Cwah4N6/3LzIXEEKyEJxNqQwtNc2IVE8C -+AwEAAaOBnjCBmzAJBgNVHRMEAjAAMDUGDCsGAQQBkggJAYNfAQQlDCNSZWQgSGF0 -+IEVudGVycHJpc2UgTGludXggZm9yIHg4Nl82NDAVBgwrBgEEAZIICQGDXwIEBQwD -+OS45MBgGDCsGAQQBkggJAYNfAwQIDAZ4ODZfNjQwJgYMKwYBBAGSCAkBg18EBBYM -+FHJoZWwtOSxyaGVsLTkteDg2XzY0MA0GCSqGSIb3DQEBCwUAA4ICAQCAiyTu7hNM -+WjpoQuttHFFyJKi1doSes65irNUEKjfP+TG3WagiNVAWX6ItUSvpuYJRUP1tPovN -+c8KgLygC5UAKoHik2nWQiELofB4/bqUU0fIbr48OlyrjcM4MDyCLrbUJtGJ/urwn -+XE/X9AzHld/lLOwpP/oLH7ATyH+R0unNrPZmz0Orr4B+B12geL7R1LtCSZwJ2Oc4 -+yWkBQEEsyzogHyD/559SCgqirZAcu3huJhlwEygylX48SGBPhDXQhGyxq0kxAsoS -+swpfjdwzv36AYvWgV2p6/XhIlKC1ffwEoB4mFcPJqV4pS0h3ggFTvT5cakzXxiXN -+tBvokdylBjG5yfyRQxXLIiVkjrm2I1Xk6cm4pQMmG5NJ7iTVmfvHQd0/kRM4Tpk1 -+6LHMlBe0j9ZkWNo1PifmKAeckm+KYsDtkn5jSyHartOlCO+a8BEl5H5RpcI5sbo2 -+2mPqx6+hHBQqe9VBdb/YM7Jd3h+D5uAFB5MkNUEGXf9R5OTkqTLWs6bVaTWD1k9j -+Y76CyBWQLU00jh/SVwGzoaZoa6nx7Q0mFKwgRsahlzV2w3xHtt3bke0IqjxNqcey -+PYs/vGX5fSmNgavw/O+oD8x2jyPYqAJibcfQiVDEJ5liN3UALtfcEcU4cUZWDOkR -+cdcP0vsXkUDltPqfD0WkJUfMuToI+HWPaQ== -+-----END CERTIFICATE----- -diff --git a/repos/system_upgrade/common/files/prod-certs/9.9/486.pem b/repos/system_upgrade/common/files/prod-certs/9.9/486.pem -new file mode 100644 -index 00000000..c88a6324 ---- /dev/null -+++ b/repos/system_upgrade/common/files/prod-certs/9.9/486.pem -@@ -0,0 +1,35 @@ -+-----BEGIN CERTIFICATE----- -+MIIGJDCCBAygAwIBAgIJALDxRLt/tVDpMA0GCSqGSIb3DQEBCwUAMIGuMQswCQYD -+VQQGEwJVUzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExFjAUBgNVBAoMDVJlZCBI -+YXQsIEluYy4xGDAWBgNVBAsMD1JlZCBIYXQgTmV0d29yazEuMCwGA1UEAwwlUmVk -+IEhhdCBFbnRpdGxlbWVudCBQcm9kdWN0IEF1dGhvcml0eTEkMCIGCSqGSIb3DQEJ -+ARYVY2Etc3VwcG9ydEByZWRoYXQuY29tMB4XDTI2MDIxOTEwMzYwMVoXDTQ2MDIx -+OTEwMzYwMVowRDFCMEAGA1UEAww5UmVkIEhhdCBQcm9kdWN0IElEIFtkY2I3ZGU0 -+NC03NzExLTQ2NWUtYjA1Ny1iMTdlZjlmYjhkYTddMIICIjANBgkqhkiG9w0BAQEF -+AAOCAg8AMIICCgKCAgEAxj9J04z+Ezdyx1U33kFftLv0ntNS1BSeuhoZLDhs18yk -+sepG7hXXtHh2CMFfLZmTjAyL9i1XsxykQpVQdXTGpUF33C2qBQHB5glYs9+d781x -+8p8m8zFxbPcW82TIJXbgW3ErVh8vk5qCbG1cCAAHb+DWMq0EAyy1bl/JgAghYNGB -+RvKJObTdCrdpYh02KUqBLkSPZHvo6DUJFN37MXDpVeQq9VtqRjpKLLwuEfXb0Y7I -+5xEOrR3kYbOaBAWVt3mYZ1t0L/KfY2jVOdU5WFyyB9PhbMdLi1xE801j+GJrwcLa -+xmqvj4UaICRzcPATP86zVM1BBQa+lilkRQes5HyjZzZDiGYudnXhbqmLo/n0cuXo -+QBVVjhzRTMx71Eiiahmiw+U1vGqkHhQNxb13HtN1lcAhUCDrxxeMvrAjYdWpYlpI -+yW3NssPWt1YUHidMBSAJ4KctIf91dyE93aStlxwC/QnyFsZOmcEsBzVCnz9GmWMl -+1/6XzBS1yDUqByklx0TLH+z/sK9A+O2rZAy1mByCYwVxvbOZhnqGxAuToIS+A81v -+5hCjsCiOScVB+cil30YBu0cH85RZ0ILNkHdKdrLLWW4wjphK2nBn2g2i3+ztf+nQ -+ED2pQqZ/rhuW79jcyCZl9kXqe1wOdF0Cwah4N6/3LzIXEEKyEJxNqQwtNc2IVE8C -+AwEAAaOBrTCBqjAJBgNVHRMEAjAAMDoGDCsGAQQBkggJAYNmAQQqDChSZWQgSGF0 -+IEVudGVycHJpc2UgTGludXggZm9yIHg4Nl82NCBCZXRhMBoGDCsGAQQBkggJAYNm -+AgQKDAg5LjkgQmV0YTAYBgwrBgEEAZIICQGDZgMECAwGeDg2XzY0MCsGDCsGAQQB -+kggJAYNmBAQbDBlyaGVsLTkscmhlbC05LWJldGEteDg2XzY0MA0GCSqGSIb3DQEB -+CwUAA4ICAQB8PbMkfGPmDhdmofA96A1foPosi9TOpcc8r7XMgWVrfeDEkiYSkxTg -+NzECXe06U+aX3ZfkFYLqTcU2KXStb3diW3FqSA0IMLKw9juA714DgKQsDH289qJo -+rPRNWMHWmA17dbx/vmlD/z8E3zUeHzf7m7GcmmJ4VO2mXeNuEZ0Gt/ASlhgNAolw -+6/KNapD3/BoxD16RSVen7eLn3S9hgsNieaTI9n1srBB7U8X9+JEHbOHAqFMwtFzG -+Xf9w3LtpO877Uh7f5UFKQEERP0ImJoLOPZ57R7c8yDjEuuxxtnrCYpXZMNyoBs6o -+x0E4ScP70UI0+XUFtXHlzmvL/DsTI113biAkjDs3kThE9EXV3f/MP8Wyy1BDvEwr -+v9RUsRbdrhTd98gqJ55tmscettCSe/R4reaJ8eDNlG8EEGrKobTeBaut4683JX+J -+0ffjkAAgBou28pGpUYItTCXVtCN+rWoqrDl0kVdh2BuwHg9hxRAtosr94pjfK+UM -+NT6pzLb5dETxH9eiprL3pQC+Gzr2DpqwHlSvFAZUnUMJEnujkVXSEXliGaWNJ1BR -+lDAqEoDCQiiuB50DyPzC8gh9I7+mxW77o9+Qy22crSEAkh0TiMlwKr4DW0NVcj1n -+RrcJQ8TlIZBR8cxd0IrUjVvWjtYQQ7VMHsdaN3I9pvGbOtM2wiLb+w== -+-----END CERTIFICATE----- -diff --git a/repos/system_upgrade/common/files/prod-certs/9.9/72.pem b/repos/system_upgrade/common/files/prod-certs/9.9/72.pem -new file mode 100644 -index 00000000..d192b1bf ---- /dev/null -+++ b/repos/system_upgrade/common/files/prod-certs/9.9/72.pem -@@ -0,0 +1,35 @@ -+-----BEGIN CERTIFICATE----- -+MIIGFjCCA/6gAwIBAgIJALDxRLt/tVDOMA0GCSqGSIb3DQEBCwUAMIGuMQswCQYD -+VQQGEwJVUzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExFjAUBgNVBAoMDVJlZCBI -+YXQsIEluYy4xGDAWBgNVBAsMD1JlZCBIYXQgTmV0d29yazEuMCwGA1UEAwwlUmVk -+IEhhdCBFbnRpdGxlbWVudCBQcm9kdWN0IEF1dGhvcml0eTEkMCIGCSqGSIb3DQEJ -+ARYVY2Etc3VwcG9ydEByZWRoYXQuY29tMB4XDTI2MDExOTE4MjMxMVoXDTQ2MDEx -+OTE4MjMxMVowRDFCMEAGA1UEAww5UmVkIEhhdCBQcm9kdWN0IElEIFtmOWU0ZTkx -+Mi1mMmFiLTRhMGYtYTM1Mi00M2E1YmY2NTc4YTBdMIICIjANBgkqhkiG9w0BAQEF -+AAOCAg8AMIICCgKCAgEAxj9J04z+Ezdyx1U33kFftLv0ntNS1BSeuhoZLDhs18yk -+sepG7hXXtHh2CMFfLZmTjAyL9i1XsxykQpVQdXTGpUF33C2qBQHB5glYs9+d781x -+8p8m8zFxbPcW82TIJXbgW3ErVh8vk5qCbG1cCAAHb+DWMq0EAyy1bl/JgAghYNGB -+RvKJObTdCrdpYh02KUqBLkSPZHvo6DUJFN37MXDpVeQq9VtqRjpKLLwuEfXb0Y7I -+5xEOrR3kYbOaBAWVt3mYZ1t0L/KfY2jVOdU5WFyyB9PhbMdLi1xE801j+GJrwcLa -+xmqvj4UaICRzcPATP86zVM1BBQa+lilkRQes5HyjZzZDiGYudnXhbqmLo/n0cuXo -+QBVVjhzRTMx71Eiiahmiw+U1vGqkHhQNxb13HtN1lcAhUCDrxxeMvrAjYdWpYlpI -+yW3NssPWt1YUHidMBSAJ4KctIf91dyE93aStlxwC/QnyFsZOmcEsBzVCnz9GmWMl -+1/6XzBS1yDUqByklx0TLH+z/sK9A+O2rZAy1mByCYwVxvbOZhnqGxAuToIS+A81v -+5hCjsCiOScVB+cil30YBu0cH85RZ0ILNkHdKdrLLWW4wjphK2nBn2g2i3+ztf+nQ -+ED2pQqZ/rhuW79jcyCZl9kXqe1wOdF0Cwah4N6/3LzIXEEKyEJxNqQwtNc2IVE8C -+AwEAAaOBnzCBnDAJBgNVHRMEAjAAMDsGCysGAQQBkggJAUgBBCwMKlJlZCBIYXQg -+RW50ZXJwcmlzZSBMaW51eCBmb3IgSUJNIHogU3lzdGVtczAUBgsrBgEEAZIICQFI -+AgQFDAM5LjkwFgYLKwYBBAGSCAkBSAMEBwwFczM5MHgwJAYLKwYBBAGSCAkBSAQE -+FQwTcmhlbC05LHJoZWwtOS1zMzkweDANBgkqhkiG9w0BAQsFAAOCAgEAG3mRXCVq -+a5hdRH/3YW4Gb6WPLq2Y0JjiLJHIc71Z23lvyKa73tr+NdmhIoQpGWL8VRTJ55Zw -+nz4a09omX7FghrlBRQ8mP+LYM5d6XiqCm5j4TwyV6ppsQMG0i4jME7M3NQvCBZbm -+p1dhUYM8kg6jj4UrMG+900eVE3Jl+PutK7gBMHfY3MCVzzvTLdN1TyO/BC6F19zC -+3LsMvexgU1hS+LOCe2KAv45yLgqaIyrkcnWVX7R0TQKEdZSi4IEqHQDHyAHd17j9 -+pHKvb7CAOOF2/GLgBr1qjy7IF2315aHElnVdnF8OOwOx1QHAhE7yAPA40JZHtmOa -+grmPlwmcdtaqru9mlYveTMDTcB5LDpUwbyh9bQOVTS1Opyo8nQWyvvN/ZKZSX2Nd -+yoW52x0oU73qKc0SHye1bF3wq/Wjh+q3k+MRkEmn8wpZJX9rWeda3THPvqlr8WlV -+AzT/UqYu6YyHPHeW5TGsI1Bf45K3dUhW5ZQs54hDDytDyjLpjU6wqOZbHHTMiqtz -+p/uoSBnWYFdcFbZ8hzsfEkxnL+rXhgyUQH4HSZGZjoqJh8SLU4JyFaRq+ERqTTDd -+B47CD7gyW1oKCAQ5kUdrD0oeCJT82z9EI9nAR5nK70yETWOMNBMrKqXlo8dIaw26 -+39nqXtD2tTOl5WcBObeN1I8w6a0zxHdjHJE= -+-----END CERTIFICATE----- -diff --git a/repos/system_upgrade/common/files/upgrade_paths.json b/repos/system_upgrade/common/files/upgrade_paths.json -index ca0fe590..86521672 100644 ---- a/repos/system_upgrade/common/files/upgrade_paths.json -+++ b/repos/system_upgrade/common/files/upgrade_paths.json -@@ -2,11 +2,12 @@ - "rhel": { - "default": { - "7.9": ["8.10"], -- "8.10": ["9.6", "9.8"], -+ "8.10": ["9.6", "9.8", "9.9"], - "9.6": ["10.0"], - "9.8": ["10.2"], -+ "9.9": ["10.3"], - "7": ["8.10"], -- "8": ["9.6", "9.8"], -+ "8": ["9.6", "9.8", "9.9"], - "9": ["10.0"] - }, - "saphana": { -@@ -26,16 +27,16 @@ - }, - "_virtual_versions": { - "8": "8.10", -- "9": "9.8", -- "10": "10.2" -+ "9": "9.9", -+ "10": "10.3" - } - }, - "almalinux": { - "default": { -- "8.10": ["9.0", "9.1", "9.2", "9.3", "9.4", "9.5", "9.6", "9.7", "9.8"], -- "9.7": ["10.0", "10.1"], -- "8": ["9.0", "9.1", "9.2", "9.3", "9.4", "9.5", "9.6", "9.7", "9.8"], -- "9": ["10.0", "10.1"] -+ "8.10": ["9.0", "9.1", "9.2", "9.3", "9.4", "9.5", "9.6", "9.7", "9.8", "9.9"], -+ "9.9": ["10.0", "10.1", "10.2", "10.3"], -+ "8": ["9.0", "9.1", "9.2", "9.3", "9.4", "9.5", "9.6", "9.7", "9.8", "9.9"], -+ "9": ["10.0", "10.1", "10.2", "10.3"] - } - } - } --- -2.53.0 - diff --git a/0003-Enable-logrotate.timer-during-8to9-IPU.patch b/0003-Enable-logrotate.timer-during-8to9-IPU.patch deleted file mode 100644 index 06d205a..0000000 --- a/0003-Enable-logrotate.timer-during-8to9-IPU.patch +++ /dev/null @@ -1,106 +0,0 @@ -From b96c610bb27ab5f7ef1ad65a1a763ed2d31b6816 Mon Sep 17 00:00:00 2001 -From: Matej Matuska -Date: Tue, 17 Feb 2026 10:23:18 +0100 -Subject: [PATCH 03/44] Enable logrotate.timer during 8to9 IPU - -On el8 logrotate uses cron, on el9 a systemd timer is used. This is not -automatically migrated during an IPU, this patch adds a actor that -does so. - -Jira: RHEL-17361 ---- - .../actors/enablelogrotatetimer/actor.py | 22 +++++++++++++ - .../libraries/enablelogrotatetimer.py | 13 ++++++++ - .../tests/test_enablelogrotatetimer.py | 31 +++++++++++++++++++ - 3 files changed, 66 insertions(+) - create mode 100644 repos/system_upgrade/el8toel9/actors/enablelogrotatetimer/actor.py - create mode 100644 repos/system_upgrade/el8toel9/actors/enablelogrotatetimer/libraries/enablelogrotatetimer.py - create mode 100644 repos/system_upgrade/el8toel9/actors/enablelogrotatetimer/tests/test_enablelogrotatetimer.py - -diff --git a/repos/system_upgrade/el8toel9/actors/enablelogrotatetimer/actor.py b/repos/system_upgrade/el8toel9/actors/enablelogrotatetimer/actor.py -new file mode 100644 -index 00000000..bc3c7ec3 ---- /dev/null -+++ b/repos/system_upgrade/el8toel9/actors/enablelogrotatetimer/actor.py -@@ -0,0 +1,22 @@ -+from leapp.actors import Actor -+from leapp.libraries.actor import enablelogrotatetimer -+from leapp.models import SystemdServicesInfoTarget, SystemdServicesTasks -+from leapp.tags import FinalizationPhaseTag, IPUWorkflowTag -+ -+ -+class EnableLogrotateTimer(Actor): -+ """ -+ Enable logrotate.timer on the target system after upgrade -+ -+ The logrotate.timer systemd timer unit replaces the traditional cron-based -+ logrotate execution used in RHEL 8. This actor ensures the timer is enabled -+ after the in-place upgrade is complete. -+ """ -+ -+ name = 'enable_logrotate_timer' -+ consumes = (SystemdServicesInfoTarget,) -+ produces = (SystemdServicesTasks,) -+ tags = (FinalizationPhaseTag, IPUWorkflowTag) -+ -+ def process(self): -+ enablelogrotatetimer.process() -diff --git a/repos/system_upgrade/el8toel9/actors/enablelogrotatetimer/libraries/enablelogrotatetimer.py b/repos/system_upgrade/el8toel9/actors/enablelogrotatetimer/libraries/enablelogrotatetimer.py -new file mode 100644 -index 00000000..c9783ae9 ---- /dev/null -+++ b/repos/system_upgrade/el8toel9/actors/enablelogrotatetimer/libraries/enablelogrotatetimer.py -@@ -0,0 +1,13 @@ -+from leapp.libraries.common.systemd import enable_unit -+from leapp.libraries.stdlib import api, CalledProcessError -+ -+LOGROTATE_TIMER = 'logrotate.timer' -+ -+ -+def process(): -+ try: -+ enable_unit(LOGROTATE_TIMER) -+ except CalledProcessError as e: -+ api.current_logger().error( -+ "Failed to enable {}: {}".format(LOGROTATE_TIMER, e) -+ ) -diff --git a/repos/system_upgrade/el8toel9/actors/enablelogrotatetimer/tests/test_enablelogrotatetimer.py b/repos/system_upgrade/el8toel9/actors/enablelogrotatetimer/tests/test_enablelogrotatetimer.py -new file mode 100644 -index 00000000..f366bd6a ---- /dev/null -+++ b/repos/system_upgrade/el8toel9/actors/enablelogrotatetimer/tests/test_enablelogrotatetimer.py -@@ -0,0 +1,31 @@ -+from leapp.libraries.actor import enablelogrotatetimer -+from leapp.libraries.common.testutils import logger_mocked -+from leapp.libraries.stdlib import api, CalledProcessError -+ -+ -+def test_success(monkeypatch): -+ -+ def mock_enable_unit(unit): -+ assert unit == 'logrotate.timer' -+ -+ monkeypatch.setattr(enablelogrotatetimer, "enable_unit", mock_enable_unit) -+ enablelogrotatetimer.process() -+ -+ -+def test_failed_to_enable(monkeypatch): -+ -+ def mock_enable_unit(unit): -+ assert unit == 'logrotate.timer' -+ raise CalledProcessError( -+ "Failed to enable logrotate.titmer", -+ ["systemctl", "enable", "logrotate.timer"], -+ { -+ "exit_code": 1, -+ "stderr": "err", -+ }, -+ ) -+ -+ monkeypatch.setattr(enablelogrotatetimer, "enable_unit", mock_enable_unit) -+ monkeypatch.setattr(api, "current_logger", logger_mocked()) -+ enablelogrotatetimer.process() -+ assert "Failed to enable logrotate.timer" in api.current_logger.errmsg[0] --- -2.53.0 - diff --git a/0004-feat-net-naming-scheme-enable-by-default-on-CS-9to10.patch b/0004-feat-net-naming-scheme-enable-by-default-on-CS-9to10.patch deleted file mode 100644 index 1e71922..0000000 --- a/0004-feat-net-naming-scheme-enable-by-default-on-CS-9to10.patch +++ /dev/null @@ -1,63 +0,0 @@ -From 0e0db2860587d57193a52135ae88df8917d70538 Mon Sep 17 00:00:00 2001 -From: Matej Matuska -Date: Wed, 11 Feb 2026 16:51:41 +0100 -Subject: [PATCH 04/44] feat(net-naming-scheme): enable by default on CS 9to10 - -This was done for RHEL 9to10 in -91f37a3913c1608b24c9aa583ac3b13a08aace71. - -Jira: RHEL-148291 ---- - .../libraries/emit_net_naming.py | 10 ++++------ - .../tests/test_emit_net_naming_scheme.py | 9 ++------- - 2 files changed, 6 insertions(+), 13 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/emit_net_naming_scheme/libraries/emit_net_naming.py b/repos/system_upgrade/common/actors/emit_net_naming_scheme/libraries/emit_net_naming.py -index 7b112ff0..ef2c0b79 100644 ---- a/repos/system_upgrade/common/actors/emit_net_naming_scheme/libraries/emit_net_naming.py -+++ b/repos/system_upgrade/common/actors/emit_net_naming_scheme/libraries/emit_net_naming.py -@@ -1,5 +1,5 @@ - from leapp.exceptions import StopActorExecutionError --from leapp.libraries.common.config import get_env, get_target_distro_id, version -+from leapp.libraries.common.config import get_env, version - from leapp.libraries.stdlib import api - from leapp.models import ( - KernelCmdline, -@@ -49,11 +49,9 @@ def is_net_scheme_compatible_with_current_cmdline(): - def emit_msgs_to_use_net_naming_schemes(): - is_feature_enabled = get_env('LEAPP_DISABLE_NET_NAMING_SCHEMES', '0') != '1' - -- is_net_naming_available = version.get_target_major_version() == "9" or ( -- version.matches_target_version(">= 10.2") -- # TODO the net-naming-sysattrs pkg is not yet available on CS10, remove -- # this when it becomes -- and not get_target_distro_id() == "centos" -+ is_net_naming_available = ( -+ version.get_target_major_version() == "9" -+ or version.matches_target_version(">= 10.2") - ) - - if not (is_feature_enabled and is_net_naming_available): -diff --git a/repos/system_upgrade/common/actors/emit_net_naming_scheme/tests/test_emit_net_naming_scheme.py b/repos/system_upgrade/common/actors/emit_net_naming_scheme/tests/test_emit_net_naming_scheme.py -index 9c1f91bb..d8eef404 100644 ---- a/repos/system_upgrade/common/actors/emit_net_naming_scheme/tests/test_emit_net_naming_scheme.py -+++ b/repos/system_upgrade/common/actors/emit_net_naming_scheme/tests/test_emit_net_naming_scheme.py -@@ -95,13 +95,8 @@ def test_emit_msgs_to_use_net_naming_schemes( - pkg_name = emit_net_naming_lib.NET_NAMING_SYSATTRS_RPM_NAME[target_major] - produced_messages = api.produce.model_instances - -- if ( -- is_net_scheme_enabled -- # the package is available since 10.2 -- and target_ver != "10.0" -- # TODO not yet available in CS 10, remove this when it is -- and not (target_distro == "centos" and target_major == "10") -- ): -+ # the package is available since 10.2 -+ if is_net_scheme_enabled and target_ver != "10.0": - userspace_tasks = ensure_one_msg_of_type_produced(produced_messages, TargetUserSpaceUpgradeTasks) - assert userspace_tasks.install_rpms == [pkg_name] - --- -2.53.0 - diff --git a/0005-Leapp_resume.service-Silence-AVC-SELinux-warnings.patch b/0005-Leapp_resume.service-Silence-AVC-SELinux-warnings.patch deleted file mode 100644 index f84fac7..0000000 --- a/0005-Leapp_resume.service-Silence-AVC-SELinux-warnings.patch +++ /dev/null @@ -1,46 +0,0 @@ -From 26e4ca841b23907eda2de78288183285e6b54b1f Mon Sep 17 00:00:00 2001 -From: karolinku -Date: Tue, 3 Mar 2026 13:56:26 +0100 -Subject: [PATCH 05/44] Leapp_resume.service: Silence AVC SELinux warnings - -Wrap the ExecStart command in `/bin/sh -c` to avoid SELinux AVC denials. - -The leapp_resume.service unit is inherently "malformed" from SELinux's -perspective: it directly executes /root/tmp_leapp_py3/leapp3, which is -labeled admin_home_t. When the system boots in Permissive mode (as it -does during the upgrade), SELinux logs AVC denials for this access -rather than blocking it. These AVCs are expected but noisy and -confusing. - -By invoking the command through /bin/sh -c, the execution context is -evaluated against /bin/sh (which carries a proper shell_exec_t label), -avoiding the admin_home_t AVC altogether. - -It is also confirmed with SELinux team that this issue could be -solved with also following solution (in case of any future issues): -``` -ExecStartPre=chcon -t bin_t /root/tmp_leapp_py3/leapp3 -ExecStart=/root/tmp_leapp_py3/leapp3 upgrade --resume -```` - -Jira: RHEL-149141 ---- - .../actors/createresumeservice/files/leapp_resume.service | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/repos/system_upgrade/common/actors/createresumeservice/files/leapp_resume.service b/repos/system_upgrade/common/actors/createresumeservice/files/leapp_resume.service -index 39ac6112..859237a1 100644 ---- a/repos/system_upgrade/common/actors/createresumeservice/files/leapp_resume.service -+++ b/repos/system_upgrade/common/actors/createresumeservice/files/leapp_resume.service -@@ -9,7 +9,7 @@ Wants=network-online.target - [Service] - Type=oneshot - # FIXME: this is temporary workaround for Python3 --ExecStart=/root/tmp_leapp_py3/leapp3 upgrade --resume -+ExecStart=/bin/sh -c "/root/tmp_leapp_py3/leapp3 upgrade --resume" - StandardOutput=journal+console - # FIXME: this shouldn't be needed, but Satellite upgrade runs installer, and that's slow - TimeoutStartSec=infinity --- -2.53.0 - diff --git a/0006-Add-API-to-get-a-distro-report-name.patch b/0006-Add-API-to-get-a-distro-report-name.patch deleted file mode 100644 index df1c049..0000000 --- a/0006-Add-API-to-get-a-distro-report-name.patch +++ /dev/null @@ -1,138 +0,0 @@ -From 9827568a1e8caeb3e96c0c40a3d7a741997391c6 Mon Sep 17 00:00:00 2001 -From: Matej Matuska -Date: Tue, 17 Feb 2026 16:56:43 +0100 -Subject: [PATCH 06/44] Add API to get a distro "report" name - -Add a global variable DISTRO_REPORT_NAMES which can be directly be used -in format_map() or unpacked into format() calls. -There are also source and target fields which can be used in e.g. -fstrings. - -Jira: RHEL-130948 ---- - .../system_upgrade/common/libraries/distro.py | 72 ++++++++++++++++++- - .../common/libraries/tests/test_distro.py | 19 +++++ - 2 files changed, 90 insertions(+), 1 deletion(-) - -diff --git a/repos/system_upgrade/common/libraries/distro.py b/repos/system_upgrade/common/libraries/distro.py -index 34c48a57..1a5e3923 100644 ---- a/repos/system_upgrade/common/libraries/distro.py -+++ b/repos/system_upgrade/common/libraries/distro.py -@@ -1,9 +1,10 @@ - import json - import os -+from collections.abc import Mapping - - from leapp.exceptions import StopActorExecutionError - from leapp.libraries.common import efi, repofileutils, rhsm --from leapp.libraries.common.config import get_target_distro_id -+from leapp.libraries.common.config import get_source_distro_id, get_target_distro_id - from leapp.libraries.common.config.architecture import ARCH_ACCEPTED, ARCH_X86_64 - from leapp.libraries.common.config.version import get_target_major_version - from leapp.libraries.stdlib import api -@@ -234,6 +235,75 @@ def distro_id_to_pretty_name(distro_id): - }[distro_id] - - -+def _distro_id_to_report_name(distro_id): -+ """ -+ Get the display name for the given distro id. -+ -+ The display name should be used for user facing text, such as in reports. -+ For a real/full name see the :func:`distro_id_to_pretty_name` function. -+ """ -+ return { -+ "rhel": "RHEL", -+ "centos": "CentOS Stream", -+ "almalinux": "AlmaLinux" -+ }[distro_id] -+ -+ -+class _DistroReportNames(Mapping): -+ """ -+ A mapping of distro names to be used in reports. -+ -+ In addition to the properties :attr:`source` and :attr:`target`, this class -+ can be used in :func:`format_map()` or unpacked to :func:`format` where it -+ exposes them as 'source_distro' and 'target_distro'. -+ """ -+ -+ @property -+ def source(self) -> str: -+ """ -+ The source distro report name. -+ -+ :type: str -+ """ -+ return _distro_id_to_report_name(get_source_distro_id()) -+ -+ @property -+ def target(self) -> str: -+ """ -+ The target distro report name. -+ -+ :type: str -+ """ -+ return _distro_id_to_report_name(get_target_distro_id()) -+ -+ def __getitem__(self, key): -+ if key == 'source_distro': -+ return self.source -+ -+ if key == 'target_distro': -+ return self.target -+ -+ raise KeyError(f"Key '{key}' not found in {self.__class__.__name__}") -+ -+ def __len__(self): -+ return 2 -+ -+ def __iter__(self): -+ yield from ['source_distro', 'target_distro'] -+ -+ -+DISTRO_REPORT_NAMES = _DistroReportNames() -+""" -+A mapping of distro "display" names for use in reports. -+ -+Can be used as argument for format_map() or unpacked in format() calls. -+The format string placeholders 'source_distro' and 'target_distro' are then -+replaced by the respective distro names or abbreviations of them. -+ -+See :class:`_DistroReportNames`. -+""" -+ -+ - def get_distro_efidir_canon_path(distro_id): - """ - Get canonical path to the distro EFI directory in the EFI mountpoint. -diff --git a/repos/system_upgrade/common/libraries/tests/test_distro.py b/repos/system_upgrade/common/libraries/tests/test_distro.py -index ec7d8f77..d266a9c6 100644 ---- a/repos/system_upgrade/common/libraries/tests/test_distro.py -+++ b/repos/system_upgrade/common/libraries/tests/test_distro.py -@@ -235,3 +235,22 @@ def test_get_distro_repoids_invalid_repo(monkeypatch): - ) - def test__get_distro_efidir_canon_path(distro, expect): - assert expect == distrolib.get_distro_efidir_canon_path(distro) -+ -+ -+def test_distro_report_names(monkeypatch): -+ current_actor = CurrentActorMocked(src_distro="centos", dst_distro="rhel") -+ monkeypatch.setattr(api, "current_actor", current_actor) -+ -+ assert distrolib.DISTRO_REPORT_NAMES.source == "CentOS Stream" -+ assert distrolib.DISTRO_REPORT_NAMES.target == "RHEL" -+ -+ expect = "CentOS Stream is upstream for RHEL" -+ template = "{source_distro} is upstream for {target_distro}" -+ assert expect == template.format_map(distrolib.DISTRO_REPORT_NAMES) -+ assert expect == template.format(**distrolib.DISTRO_REPORT_NAMES) -+ -+ template = "{source_distro} is {what} for {target_distro}" -+ assert expect == template.format(what='upstream', **distrolib.DISTRO_REPORT_NAMES) -+ -+ template = "{source_distro} is {what} for RHEL" -+ assert expect == template.format(what='upstream', **distrolib.DISTRO_REPORT_NAMES) --- -2.53.0 - diff --git a/0007-livemode-copy-upgrade-kernel-s-hmac-into-boot.patch b/0007-livemode-copy-upgrade-kernel-s-hmac-into-boot.patch deleted file mode 100644 index b1f9403..0000000 --- a/0007-livemode-copy-upgrade-kernel-s-hmac-into-boot.patch +++ /dev/null @@ -1,190 +0,0 @@ -From d6e4afe5c51ac8809649ca395e2ffafc5ab76922 Mon Sep 17 00:00:00 2001 -From: Michal Hecko -Date: Wed, 4 Mar 2026 22:45:18 +0100 -Subject: [PATCH 07/44] livemode: copy upgrade kernel's hmac into /boot - -Copying kernel's HMAC is necessary in order to boot with FIPS mode -enabled. Standard (old) upgrade process copies the kernel HMAC file -already, livemode did not in order to keep the initial implementation -simple. This change adds copying of kernel HMAC also for livemode. - -Jira-ref: RHEL-129571 ---- - .../libraries/upgradeinitramfsgenerator.py | 50 +++++++---- - .../unit_test_upgradeinitramfsgenerator.py | 84 ++++++++++++++++++- - 2 files changed, 118 insertions(+), 16 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py -index 03447b7c..1a0dccd8 100644 ---- a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py -+++ b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py -@@ -480,6 +480,16 @@ def prepare_boot_files_for_livemode(context): - - copy_target_kernel_from_userspace_into_boot(context, target_kernel_ver, kernel_artifact_name) - -+ uspace_kernel_hmac_path = context.full_path('/lib/modules/{}/.vmlinuz.hmac'.format(target_kernel_ver)) -+ upgrade_kernel_hmac_dest = '/boot/.{}.hmac'.format(kernel_artifact_name) -+ create_upgrade_hmac_from_target_hmac(uspace_kernel_hmac_path, upgrade_kernel_hmac_dest, kernel_artifact_name) -+ api.current_logger().info( -+ 'Written hmac ({0}) for the upgrade kernel (based on {1})'.format( -+ upgrade_kernel_hmac_dest, -+ uspace_kernel_hmac_path -+ ) -+ ) -+ - USERSPACE_ARTIFACTS_PATH = '/artifacts' - context.makedirs(USERSPACE_ARTIFACTS_PATH, exists_ok=True) - userspace_initramfs_dest = os.path.join(USERSPACE_ARTIFACTS_PATH, initramfs_artifact_name) -@@ -493,25 +503,35 @@ def prepare_boot_files_for_livemode(context): - - return BootContent(kernel_path=host_kernel_dest, - initram_path=host_initramfs_dest, -- kernel_hmac_path='') -+ kernel_hmac_path=upgrade_kernel_hmac_dest) -+ -+ -+def _read_file(path): -+ with open(path) as in_file: -+ return in_file.read() -+ -+ -+def _write_file(path, data): -+ with open(path, 'w') as out_file: -+ out_file.write(data) - - --def create_upgrade_hmac_from_target_hmac(original_hmac_path, upgrade_hmac_path, upgrade_kernel): -+def create_upgrade_hmac_from_target_hmac(original_hmac_path: str, upgrade_hmac_path: str, upgrade_kernel: str): - # Rename the kernel name stored in the HMAC file as the upgrade kernel is named differently and the HMAC file - # refers to the real target kernel -- with open(original_hmac_path) as original_hmac_file: -- hmac_file_lines = [line for line in original_hmac_file.read().split('\n') if line] -- if len(hmac_file_lines) > 1: -- details = ('Expected the target kernel HMAC file to containing only one HMAC line, ' -- 'found {0}'.format(len(hmac_file_lines))) -- raise StopActorExecutionError('Failed to prepare HMAC file for upgrade kernel.', -- details={'details': details}) -- -- # Keep only non-empty strings after splitting on space -- hmac, dummy_target_kernel_name = [fragment for fragment in hmac_file_lines[0].split(' ') if fragment] -- -- with open(upgrade_hmac_path, 'w') as upgrade_kernel_hmac_file: -- upgrade_kernel_hmac_file.write('{hmac} {kernel}\n'.format(hmac=hmac, kernel=upgrade_kernel)) -+ original_hmac_file_content = _read_file(original_hmac_path) -+ hmac_file_lines = [line for line in original_hmac_file_content.split('\n') if line] -+ if len(hmac_file_lines) > 1: -+ details = ('Expected the target kernel HMAC file to containing only one HMAC line, ' -+ 'found {0}'.format(len(hmac_file_lines))) -+ raise StopActorExecutionError('Failed to prepare HMAC file for upgrade kernel.', -+ details={'details': details}) -+ -+ # Keep only non-empty strings after splitting on space -+ hmac, dummy_target_kernel_name = [fragment for fragment in hmac_file_lines[0].split(' ') if fragment] -+ -+ upgrade_hmac_content = '{hmac} {kernel}\n'.format(hmac=hmac, kernel=upgrade_kernel) -+ _write_file(upgrade_hmac_path, upgrade_hmac_content) - - - def copy_boot_files(context): -diff --git a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/tests/unit_test_upgradeinitramfsgenerator.py b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/tests/unit_test_upgradeinitramfsgenerator.py -index b96bf79f..165a4df0 100644 ---- a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/tests/unit_test_upgradeinitramfsgenerator.py -+++ b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/tests/unit_test_upgradeinitramfsgenerator.py -@@ -116,7 +116,7 @@ class MockedContext: - self.called_copy_to.append((src, dst)) - self.content.add(dst) - -- def makedirs(self, path): -+ def makedirs(self, path, exists_ok=True): - self.called_makedirs.append(path) - - def remove_tree(self, path): -@@ -402,3 +402,85 @@ def test_copy_modules_duplicate_skip(monkeypatch, kind): - assert context.content - assert len(context.called_copy_to) == 1 - assert debugmsg in upgradeinitramfsgenerator.api.current_logger.dbgmsg -+ -+ -+def test_create_upgrade_hmac_from_target_hmac(monkeypatch): -+ upgrade_hmac_written = False -+ -+ def _read_file_mock(path): -+ assert path == '/original-hmac' -+ return ('ff00a9674033eea61bec48d21a1d2c27eaac9bd6ed4997e31dd0d9307c7a4770eb81df7116c' -+ '4ace25d354a06dfdcd75e38f504f2ea7c1c4bdc95ea7083b701c0 vmlinuz-6.12.0-55.9.1.el10_0.x86_64') -+ -+ def _write_file_mock(path, content): -+ assert path == '/boot/.vmlinuz-upgrade.x86_64.hmac' -+ expected_content = ('ff00a9674033eea61bec48d21a1d2c27eaac9bd6ed4997e31dd0d9307c7a4770eb81df7116c' -+ '4ace25d354a06dfdcd75e38f504f2ea7c1c4bdc95ea7083b701c0 ' -+ 'vmlinuz-upgrade.x86_64\n') -+ assert content == expected_content -+ nonlocal upgrade_hmac_written -+ upgrade_hmac_written = True -+ -+ monkeypatch.setattr(upgradeinitramfsgenerator, '_read_file', _read_file_mock) -+ monkeypatch.setattr(upgradeinitramfsgenerator, '_write_file', _write_file_mock) -+ upgradeinitramfsgenerator.create_upgrade_hmac_from_target_hmac( -+ '/original-hmac', '/boot/.vmlinuz-upgrade.x86_64.hmac', 'vmlinuz-upgrade.x86_64') -+ -+ assert upgrade_hmac_written -+ -+ -+def test_prepare_boot_files_for_livemode(monkeypatch): -+ context_mock = MockedContext() -+ -+ monkeypatch.setattr(upgradeinitramfsgenerator, -+ '_get_target_kernel_version', -+ lambda ctx: '6.18.3-100.fc42.x86_64') -+ -+ monkeypatch.setattr(upgradeinitramfsgenerator, -+ 'get_boot_artifact_names', -+ lambda: ('vmlinuz-upgrade.x86_64', 'initramfs-upgrade.x86_64.img')) -+ -+ upgrade_kernel_present = False -+ initramfs_generated = False -+ upgrade_initramfs_present = False -+ upgrade_kernel_hmac_present = False -+ -+ def copy_target_kernel_mock(context, target_kernel_ver, kernel_artifact_name): -+ nonlocal upgrade_kernel_present -+ upgrade_kernel_present = True -+ -+ def _generate_initramfs_mock(context, userspace_initramfs_dest, target_kernel_ver): -+ nonlocal initramfs_generated -+ initramfs_generated = True -+ -+ def create_upgrade_hmac_from_target_hmac_mock(uspace_kernel_hmac_path, -+ upgrade_kernel_hmac_dest, -+ kernel_artifact_name): -+ assert upgrade_kernel_hmac_dest == '/boot/.vmlinuz-upgrade.x86_64.hmac' -+ nonlocal upgrade_kernel_hmac_present -+ upgrade_kernel_hmac_present = True -+ -+ monkeypatch.setattr(upgradeinitramfsgenerator, -+ 'copy_target_kernel_from_userspace_into_boot', -+ copy_target_kernel_mock) -+ -+ monkeypatch.setattr(upgradeinitramfsgenerator, -+ 'create_upgrade_hmac_from_target_hmac', -+ create_upgrade_hmac_from_target_hmac_mock) -+ -+ monkeypatch.setattr(upgradeinitramfsgenerator, -+ '_generate_livemode_initramfs', -+ _generate_initramfs_mock) -+ -+ boot_content = upgradeinitramfsgenerator.prepare_boot_files_for_livemode(context_mock) -+ -+ upgrade_initramfs_present = context_mock.called_copy_from[0][1] == '/boot/initramfs-upgrade.x86_64.img' -+ -+ assert upgrade_kernel_present -+ assert initramfs_generated -+ assert upgrade_initramfs_present -+ assert upgrade_kernel_hmac_present -+ -+ assert boot_content.kernel_path == '/boot/vmlinuz-upgrade.x86_64' -+ assert boot_content.initram_path == '/boot/initramfs-upgrade.x86_64.img' -+ assert boot_content.kernel_hmac_path == '/boot/.vmlinuz-upgrade.x86_64.hmac' --- -2.53.0 - diff --git a/0008-fix-livemode-make-live.dir-is-relative-to-device-roo.patch b/0008-fix-livemode-make-live.dir-is-relative-to-device-roo.patch deleted file mode 100644 index 3b69428..0000000 --- a/0008-fix-livemode-make-live.dir-is-relative-to-device-roo.patch +++ /dev/null @@ -1,131 +0,0 @@ -From 0fc2075683ffe30b77e83c7d793815e9301fa41d Mon Sep 17 00:00:00 2001 -From: Michal Hecko -Date: Fri, 13 Mar 2026 10:03:19 +0100 -Subject: [PATCH 08/44] fix(livemode): make live.dir is relative to device root - -Booting from an squashfs image (from a local block dev) requires to -parameters: -- identifier of the device where the squashfs image resides -- path to the squashfs image on the given device (aka live.dir) -Therefore, live.dir needs to be set up with a path that is relative to -the device where the image resides. This patch fixes the current -behavior where the path is being taken as it is, i.e., relative to the -root of the source system. - -Jira-ref: RHEL-104379 ---- - .../libraries/addupgradebootentry.py | 29 ++++++++++----- - .../tests/unit_test_addupgradebootentry.py | 36 +++++++++++-------- - 2 files changed, 43 insertions(+), 22 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py b/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py -index b28ec57c..5b635a83 100644 ---- a/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py -+++ b/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py -@@ -270,14 +270,26 @@ def local_os_stat(path): - return os.stat(path) - - --def _get_device_uuid(path): -+def _split_path_on_mount_point(path): -+ """ Split a given path into a prefix where a device is mounted and suffix relative to the device root. """ -+ mount_point = path -+ while not os.path.ismount(mount_point): -+ mount_point = os.path.dirname(mount_point) -+ -+ relative_suffix = path[len(mount_point):] -+ if len(relative_suffix) == 0 or relative_suffix[0] != '/': -+ # relative_suffix is '' if the squashfs dir is set to root (/) -+ # relative_suffix does not start with '/' if the dir is located in /, i.e., /mydir -+ relative_suffix = '/{}'.format(relative_suffix) -+ -+ return (mount_point, relative_suffix) -+ -+ -+def _get_device_uuid(mount_point): - """ -- Find the UUID of a device in which the given path is located. -+ Find the UUID of a device mounted at a given mount point. - """ -- while not os.path.ismount(path): -- path = os.path.dirname(path) -- -- needle_dev_id = local_os_stat(path).st_dev -+ needle_dev_id = local_os_stat(mount_point).st_dev - - for uuid in os.listdir('/dev/disk/by-uuid'): - uuid_fullpath = os.path.join('/dev/disk/by-uuid/', uuid) -@@ -333,8 +345,9 @@ def construct_cmdline_args_for_livemode(): - if livemode_config.url_to_load_squashfs_from: - args['root'] = 'live:{}'.format(livemode_config.url_to_load_squashfs_from) - else: -- args['root'] = 'live:UUID={}'.format(_get_device_uuid(dir_path_containing_liveimg)) -- args['rd.live.dir'] = dir_path_containing_liveimg -+ dev_mount_point, dir_location_on_dev = _split_path_on_mount_point(dir_path_containing_liveimg) -+ args['root'] = 'live:UUID={}'.format(_get_device_uuid(dev_mount_point)) -+ args['rd.live.dir'] = dir_location_on_dev - args['rd.live.squashimg'] = liveimg_filename - - if livemode_config.dracut_network: -diff --git a/repos/system_upgrade/common/actors/addupgradebootentry/tests/unit_test_addupgradebootentry.py b/repos/system_upgrade/common/actors/addupgradebootentry/tests/unit_test_addupgradebootentry.py -index 7341602b..79c05a5d 100644 ---- a/repos/system_upgrade/common/actors/addupgradebootentry/tests/unit_test_addupgradebootentry.py -+++ b/repos/system_upgrade/common/actors/addupgradebootentry/tests/unit_test_addupgradebootentry.py -@@ -279,23 +279,31 @@ def test_get_rdlvm_arg_values(monkeypatch): - assert args == ('A', 'B') - - -+@pytest.mark.parametrize( -+ ('path', 'mount_point', 'suffix'), -+ [ -+ ('/', '/', '/'), -+ ('/dir', '/', '/dir'), -+ ('/dir/squashfs', '/', '/dir/squashfs'), -+ ('/dir/squashfs', '/dir', '/squashfs'), -+ ] -+) -+def test_split_path_on_mount_point(monkeypatch, path, mount_point, suffix): -+ def is_mount_mock(_path): -+ return _path == mount_point -+ -+ monkeypatch.setattr(os.path, 'ismount', is_mount_mock) -+ -+ ret_mount_point, ret_suffix = addupgradebootentry._split_path_on_mount_point(path) -+ assert ret_mount_point == mount_point -+ assert ret_suffix == suffix -+ -+ - def test_get_device_uuid(monkeypatch): - """ - The file in question is /var/lib/file - Underlying partition /var is a device /dev/sda1 (dev_id=10) linked to from /dev/disk/by-uuid/MY_UUID1 - """ -- -- execution_stats = { -- 'is_mount_call_count': 0 -- } -- -- def is_mount_mock(path): -- execution_stats['is_mount_call_count'] += 1 -- assert execution_stats['is_mount_call_count'] <= 3 -- return path == '/var' -- -- monkeypatch.setattr(os.path, 'ismount', is_mount_mock) -- - StatResult = namedtuple('StatResult', ('st_dev', 'st_rdev')) - - def stat_mock(path): -@@ -325,8 +333,8 @@ def test_get_device_uuid(monkeypatch): - - monkeypatch.setattr(os, 'readlink', readlink_mock) - -- path = '/var/lib/file' -- uuid = addupgradebootentry._get_device_uuid(path) -+ mount_point = '/var' -+ uuid = addupgradebootentry._get_device_uuid(mount_point) - - assert uuid == 'MY_UUID1' - --- -2.53.0 - diff --git a/0009-fix-livemode-change-location-of-source-system-tree.patch b/0009-fix-livemode-change-location-of-source-system-tree.patch deleted file mode 100644 index 0767c87..0000000 --- a/0009-fix-livemode-change-location-of-source-system-tree.patch +++ /dev/null @@ -1,61 +0,0 @@ -From 3a9cb366b7487bb5cc57e7650447af327c343b0d Mon Sep 17 00:00:00 2001 -From: Michal Hecko -Date: Wed, 18 Mar 2026 09:58:13 +0100 -Subject: [PATCH 09/44] fix(livemode): change location of source system tree - -Before this patch, the generated squashfs image mounts the source system -tree at /run/initramfs/live, which is the location used by dmsquash-live -to mount the block device that holds the squashfs image. This device, -however, might be arbitrary, e.g., it could be hosting /var of the -source system. Therefore, the squashfs system would attempt to mount -multiple things at /run/initramfs/live (/ and /var of the source -system), creating problems. This patch changes the location of the -source system tree to '/run/upgrade'. - -Jira-ref: RHEL-104379 ---- - .../files/do-upgrade.sh | 4 ++-- - .../libraries/prepareliveimage.py | 12 ++++++++++-- - 2 files changed, 12 insertions(+), 4 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/livemode/modify_userspace_for_livemode/files/do-upgrade.sh b/repos/system_upgrade/common/actors/livemode/modify_userspace_for_livemode/files/do-upgrade.sh -index 4b2f9a1f..51ebddb5 100755 ---- a/repos/system_upgrade/common/actors/livemode/modify_userspace_for_livemode/files/do-upgrade.sh -+++ b/repos/system_upgrade/common/actors/livemode/modify_userspace_for_livemode/files/do-upgrade.sh -@@ -28,8 +28,8 @@ export LEAPPHOME=/root/tmp_leapp_py3 - export LEAPP3_BIN=$LEAPPHOME/leapp3 - - # this was initially a dracut script, hence $NEWROOT. --# the rootfs is mounted on /run/initramfs/live when booted with dmsquash-live --export NEWROOT=/run/initramfs/live -+# the rootfs is mounted on /run/upgrade when booted with dmsquash-live -+export NEWROOT=/run/upgrade - - NSPAWN_OPTS="--capability=all --bind=/dev --bind=/dev/pts --bind=/proc --bind=/run/udev --bind=/run/lock" - [ -d /dev/mapper ] && NSPAWN_OPTS="$NSPAWN_OPTS --bind=/dev/mapper" -diff --git a/repos/system_upgrade/common/actors/livemode/modify_userspace_for_livemode/libraries/prepareliveimage.py b/repos/system_upgrade/common/actors/livemode/modify_userspace_for_livemode/libraries/prepareliveimage.py -index 2587bf89..96dadadf 100644 ---- a/repos/system_upgrade/common/actors/livemode/modify_userspace_for_livemode/libraries/prepareliveimage.py -+++ b/repos/system_upgrade/common/actors/livemode/modify_userspace_for_livemode/libraries/prepareliveimage.py -@@ -18,8 +18,16 @@ LEAPP_CONSOLE_SERVICE_FILE = 'console.service' - LEAPP_STRACE_SERVICE_FILE = 'upgrade-strace.service' - """ Service that executes the upgrade while strace-ing the corresponding Leapp's process tree. """ - --SOURCE_ROOT_MOUNT_LOCATION = '/run/initramfs/live' --""" Controls where the source system's root will be mounted inside the upgrade image. """ -+SOURCE_ROOT_MOUNT_LOCATION = '/run/upgrade' -+""" -+Controls where the source system's root will be mounted inside the upgrade image. -+ -+Note: This cannot be set to /run/initramfs/live as the path is used by -+ dmsquash-live by default to mount the block device that holds the squashfs -+ image. As this device can be arbitrary (e.g., it can be mounted as /var on the -+ source system), using /run/initramfs/live would cause mounting problems - we -+ would attempt to mount / and also (for example) /var on the same location. -+""" - - - def create_fstab_mounting_current_root_elsewhere(context, host_fstab): --- -2.53.0 - diff --git a/0010-Respect-distro-names-in-reports.patch b/0010-Respect-distro-names-in-reports.patch deleted file mode 100644 index 08d5ee2..0000000 --- a/0010-Respect-distro-names-in-reports.patch +++ /dev/null @@ -1,1678 +0,0 @@ -From c5863b121a2ff9e653a0a26a2c769fc0239c1c12 Mon Sep 17 00:00:00 2001 -From: Matej Matuska -Date: Mon, 2 Mar 2026 17:16:31 +0100 -Subject: [PATCH 10/44] Respect distro names in reports - -Previously, all reports had the distro name (RHEL) hardcoded in titles -and summaries. The reports in the following actors now respect the -source distro and target distro names: -- peseventscanner -- checklvm -- checkluks -- xorgcheck -- motifcheck -- mariadbcheck -- check_default_initramfs -- checkkrb5conf -- libdbcheck -- postgresqlcheck -- checkoldxfs -- networkdeprecations -- checkmysql -- inhibitcgroupsv1 -- checkpamuserdb -- opensslenginescheck -- firewalldcheckallowzonedrifting -- mysqlcheck -- firewalldcheckservicetftpclient -- multipathconfcheck -- mariadbcheck -- opensshdropindirectorycheck -- postgresqlcheck -- nischeck -- opensshsubsystemsftp -- checkcustomnetworkscripts -- checkdeprecatedrpmsignature -- kernelcmdlineconfig -- checkopensslconf - -The remaining reports are handled in the following commits. - -Jira: RHEL-130950 - -Co-Authored-By: Peter Mocary ---- - .../actors/checkluks/libraries/checkluks.py | 11 ++-- - .../actors/checkluks/tests/test_checkluks.py | 4 ++ - .../actors/checklvm/libraries/checklvm.py | 7 ++- - .../libraries/kernelcmdlineconfig.py | 18 +++--- - .../libraries/checkopensslconf.py | 33 +++++----- - .../libraries/pes_events_scanner.py | 28 ++++++--- - .../libraries/customnetworkscripts.py | 7 ++- - .../tests/unit_test_customnetworkscripts.py | 2 + - .../libraries/checkdeprecatedrpmsignature.py | 4 +- - .../firewalldcheckallowzonedrifting/actor.py | 13 ++-- - ...nt_test_firewalldcheckallowzonedrifting.py | 5 +- - .../firewalldcheckservicetftpclient/actor.py | 11 +++- - ...nt_test_firewalldcollectusedobjectnames.py | 4 +- - .../mariadbcheck/libraries/mariadbcheck.py | 41 ++++++------- - .../libraries/multipathconfcheck.py | 53 +++++++++------- - .../tests/test_multipath_conf_check_8to9.py | 9 +++ - .../actors/mysqlcheck/libraries/mysqlcheck.py | 13 ++-- - .../actors/nischeck/libraries/nischeck.py | 31 +++++----- - .../opensshdropindirectorycheck/actor.py | 2 +- - .../libraries/opensshsubsystemsftp.py | 6 +- - .../tests/test_opensshsubsystemsftp.py | 5 +- - .../libraries/postgresqlcheck.py | 61 ++++++++++--------- - .../libraries/check_default_initramfs.py | 11 ++-- - .../checkoldxfs/libraries/checkoldxfs.py | 13 ++-- - .../libraries/inhibitcgroupsv1.py | 11 ++-- - .../checkkrb5conf/libraries/checkkrb5conf.py | 7 ++- - .../actors/libdbcheck/libraries/libdbcheck.py | 44 +++++++------ - .../mariadbcheck/libraries/mariadbcheck.py | 43 ++++++------- - .../actors/motifcheck/libraries/motifcheck.py | 38 ++++++------ - .../mysql/checkmysql/libraries/checkmysql.py | 21 ++++--- - .../mysql/checkmysql/tests/test_checkmysql.py | 4 +- - .../actors/networkdeprecations/actor.py | 27 +++++--- - .../unit_test_networkdeprecations_9to10.py | 7 +++ - .../libraries/opensslenginescheck.py | 14 +++-- - .../component_test_opensslenginescheck.py | 5 +- - .../libraries/checkpamuserdb.py | 10 ++- - .../tests/test_checkpamuserdb.py | 3 + - .../libraries/postgresqlcheck.py | 56 ++++++++--------- - .../actors/xorgcheck/libraries/xorgcheck.py | 32 +++++----- - 39 files changed, 409 insertions(+), 305 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/checkluks/libraries/checkluks.py b/repos/system_upgrade/common/actors/checkluks/libraries/checkluks.py -index 4626cf63..f3e45b47 100644 ---- a/repos/system_upgrade/common/actors/checkluks/libraries/checkluks.py -+++ b/repos/system_upgrade/common/actors/checkluks/libraries/checkluks.py -@@ -1,5 +1,6 @@ - from leapp import reporting - from leapp.libraries.common.config.version import get_source_major_version -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.stdlib import api - from leapp.models import ( - CephInfo, -@@ -50,18 +51,18 @@ def report_inhibitor(luks1_partitions, no_tpm2_partitions): - if luks1_partitions: - - summary += ( -- '\n\nSince RHEL 8 the default format for LUKS encryption is LUKS2.' -- ' Despite the old LUKS1 format is still supported on RHEL systems' -+ '\n\nSince {target_distro} 8 the default format for LUKS encryption is LUKS2.' -+ ' Despite the old LUKS1 format is still supported on {target_distro} systems' - ' it has some limitations in comparison to LUKS2.' - ' Only the LUKS2 format is supported for upgrades.' -- ' The following LUKS1 partitions have been discovered on your system:{}' -- .format(''.join(_formatted_list_output(luks1_partitions))) -+ ' The following LUKS1 partitions have been discovered on your system:{partitions}' -+ .format(**DISTRO_REPORT_NAMES, partitions=''.join(_formatted_list_output(luks1_partitions))) - ) - report_hints.append(reporting.Remediation( - hint=( - 'Convert your LUKS1 encrypted devices to LUKS2 and bind it to TPM2 using clevis.' - ' If this is not possible in your case consider clean installation' -- ' of the target RHEL system instead.' -+ ' of the target {target_distro} system instead.'.format_map(DISTRO_REPORT_NAMES) - ) - )) - report_hints.append(reporting.ExternalLink( -diff --git a/repos/system_upgrade/common/actors/checkluks/tests/test_checkluks.py b/repos/system_upgrade/common/actors/checkluks/tests/test_checkluks.py -index 13b8bc55..0147c4f8 100644 ---- a/repos/system_upgrade/common/actors/checkluks/tests/test_checkluks.py -+++ b/repos/system_upgrade/common/actors/checkluks/tests/test_checkluks.py -@@ -1,3 +1,4 @@ -+from leapp.libraries.common import distro - from leapp.libraries.common.config import version - from leapp.models import ( - CephInfo, -@@ -17,6 +18,9 @@ _REPORT_TITLE_UNSUITABLE = 'Detected LUKS devices unsuitable for in-place upgrad - - def test_actor_with_luks1_notpm(monkeypatch, current_actor_context): - monkeypatch.setattr(version, 'get_source_major_version', lambda: '8') -+ monkeypatch.setattr(distro, 'get_source_distro_id', lambda: 'rhel') -+ monkeypatch.setattr(distro, 'get_target_distro_id', lambda: 'rhel') -+ - luks_dump = LuksDump( - version=1, - uuid='dd09e6d4-b595-4f1c-80b8-fd47540e6464', -diff --git a/repos/system_upgrade/common/actors/checklvm/libraries/checklvm.py b/repos/system_upgrade/common/actors/checklvm/libraries/checklvm.py -index 073bfbf4..241461dd 100644 ---- a/repos/system_upgrade/common/actors/checklvm/libraries/checklvm.py -+++ b/repos/system_upgrade/common/actors/checklvm/libraries/checklvm.py -@@ -1,6 +1,7 @@ - import os - - from leapp import reporting -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.common.rpms import has_package - from leapp.libraries.stdlib import api - from leapp.models import ( -@@ -19,9 +20,9 @@ LVM_DEVICES_FILE_PATH_PREFIX = '/etc/lvm/devices' - def _report_filter_detection(): - title = 'LVM filter definition detected.' - summary = ( -- 'Beginning with RHEL 9, LVM devices file is used by default to select devices used by ' -- f'LVM. Since leapp detected the use of LVM filter in the {LVM_CONFIG_PATH} configuration ' -- 'file, the configuration won\'t be modified to use devices file during the upgrade and ' -+ f'Beginning with {DISTRO_REPORT_NAMES.target} 9, LVM devices file is used by default to ' -+ f'select devices used by LVM. Since leapp detected the use of LVM filter in the {LVM_CONFIG_PATH} ' -+ 'configuration file, the configuration won\'t be modified to use devices file during the upgrade and ' - 'the LVM filter will remain in use after the upgrade.' - ) - -diff --git a/repos/system_upgrade/common/actors/kernelcmdlineconfig/libraries/kernelcmdlineconfig.py b/repos/system_upgrade/common/actors/kernelcmdlineconfig/libraries/kernelcmdlineconfig.py -index 98b8b95b..137341b4 100644 ---- a/repos/system_upgrade/common/actors/kernelcmdlineconfig/libraries/kernelcmdlineconfig.py -+++ b/repos/system_upgrade/common/actors/kernelcmdlineconfig/libraries/kernelcmdlineconfig.py -@@ -5,6 +5,8 @@ from leapp import reporting - from leapp.exceptions import StopActorExecutionError - from leapp.libraries import stdlib - from leapp.libraries.common.config import architecture, version -+from leapp.libraries.common.config.version import get_target_major_version -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.stdlib import api - from leapp.models import ( - InstalledTargetKernelInfo, -@@ -120,13 +122,13 @@ def _extract_grubby_value(record): - def report_multple_entries_for_default_kernel(): - if use_cmdline_file(): - report_hint = ( -- 'After the system has been rebooted into the new version of RHEL,' -+ 'After the system has been rebooted into the {target_distro} {target_version},' - ' check that configured default kernel cmdline arguments in /etc/kernel/cmdline ' - ' are correct. In case that different arguments are expected, update the file as needed.' -- ) -+ ).format(**DISTRO_REPORT_NAMES, target_version=get_target_major_version()) - else: - report_hint = ( -- 'After the system has been rebooted into the new version of RHEL,' -+ 'After the system has been rebooted into the {target_distro} {target_version},' - ' check that configured default kernel cmdline arguments are set as expected, using' - ' the `grub2-editenv list` command. ' - ' If different default arguments are expected, update them using grub2-editenv.\n' -@@ -138,7 +140,7 @@ def report_multple_entries_for_default_kernel(): - ' then run the following grub2-editenv command:\n\n' - ' # grub2-editenv - set "kernelopts=root=/dev/mapper/rhel_ibm--root' - ' ro console=tty0 console=ttyS0,115200 rd_NO_PLYMOUTH"' -- ) -+ ).format(**DISTRO_REPORT_NAMES, target_version=get_target_major_version()) - - reporting.create_report([ - reporting.Title('Ensure that expected default kernel cmdline arguments are set'), -@@ -243,14 +245,14 @@ def entrypoint(configs=None): - - if use_cmdline_file(): - report_hint = ( -- 'After the system has been rebooted into the new version of RHEL, you' -+ 'After the system has been rebooted into the {target_distro} {target_version}, you' - ' should take the kernel cmdline arguments from /proc/cmdline (Everything' - ' except the BOOT_IMAGE entry and initrd entries) and copy them into' - ' /etc/kernel/cmdline before installing any new kernels.' -- ) -+ ).format(**DISTRO_REPORT_NAMES, target_version=get_target_major_version()) - else: - report_hint = ( -- 'After the system has been rebooted into the new version of RHEL, you' -+ 'After the system has been rebooted into the {target_distro} {target_version}, you' - ' should take the kernel cmdline arguments from /proc/cmdline (Everything' - ' except the BOOT_IMAGE entry and initrd entries) and then use the' - ' grub2-editenv command to make them the default kernel args. For example,' -@@ -261,7 +263,7 @@ def entrypoint(configs=None): - ' then run the following grub2-editenv command:\n\n' - ' # grub2-editenv - set "kernelopts=root=/dev/mapper/rhel_ibm--root' - ' ro console=tty0 console=ttyS0,115200 rd_NO_PLYMOUTH"' -- ) -+ ).format(**DISTRO_REPORT_NAMES, target_version=get_target_major_version()) - - reporting.create_report([ - reporting.Title('Could not set the kernel arguments for future kernels'), -diff --git a/repos/system_upgrade/common/actors/openssl/checkopensslconf/libraries/checkopensslconf.py b/repos/system_upgrade/common/actors/openssl/checkopensslconf/libraries/checkopensslconf.py -index d005e205..6e7267f5 100644 ---- a/repos/system_upgrade/common/actors/openssl/checkopensslconf/libraries/checkopensslconf.py -+++ b/repos/system_upgrade/common/actors/openssl/checkopensslconf/libraries/checkopensslconf.py -@@ -1,5 +1,6 @@ - from leapp import reporting - from leapp.libraries.common.config import architecture, version -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.common.rpms import has_package - from leapp.libraries.stdlib import api - from leapp.models import DistributionSignedRPM, TrackedFilesInfoSource -@@ -8,7 +9,7 @@ DEFAULT_OPENSSL_CONF = '/etc/pki/tls/openssl.cnf' - URL_CRYPTOPOLICIES = { - '8': 'https://red.ht/rhel-8-system-wide-crypto-policies', - '9': 'https://red.ht/rhel-9-system-wide-crypto-policies', -- '10': 'https://red.ht/rhel-10-system-wide-crypto-policies', # TODO actually make the url -+ '10': 'https://red.ht/rhel-10-system-wide-crypto-policies', - } - - -@@ -22,14 +23,14 @@ def check_ibmca(): - # is deprecated, so keep proper teminology to not confuse users. - summary = ( - 'The presence of openssl-ibmca package suggests that the system may be configured' -- ' to use the IBMCA OpenSSL engine.' -- ' Due to major changes in OpenSSL and libica between RHEL {source} and RHEL {target} it is not' -- ' possible to migrate OpenSSL configuration files automatically. Therefore,' -- ' it is necessary to enable IBMCA providers in the OpenSSL config file manually' -- ' after the system upgrade.' -- .format( -- source=version.get_source_major_version(), -- target=version.get_target_major_version(), -+ ' to use the IBMCA OpenSSL engine. Due to major changes in OpenSSL and libica' -+ ' between {source_distro} {source_version} and {target_distro} {target_version}' -+ ' it is not possible to migrate OpenSSL configuration files automatically.' -+ ' Therefore, it is necessary to enable IBMCA providers in the OpenSSL config file' -+ ' manually after the system upgrade.'.format( -+ source_version=version.get_source_major_version(), -+ target_version=version.get_target_major_version(), -+ **DISTRO_REPORT_NAMES - ) - ) - -@@ -79,7 +80,7 @@ def check_default_openssl(): - # current wording could be inaccurate. - summary = ( - 'The OpenSSL configuration file ({fpath}) has been' -- ' modified on the system. RHEL 8 (and newer) systems provide a crypto-policies' -+ ' modified on the system. {target_distro} 8 (and newer) systems provide a crypto-policies' - ' mechanism ensuring usage of system-wide secure cryptography algorithms.' - ' Also the target system uses newer version of OpenSSL that is not fully' - ' compatible with the current one.' -@@ -92,20 +93,22 @@ def check_default_openssl(): - ' the upgrade if it depends on the current OpenSSL configuration.' - ' Such a problem may be caused by using a particular OpenSSL engine, as' - ' OpenSSL engines built for the' -- ' RHEL {source} system are not compatible with RHEL {target}.' -+ ' {source_distro} {source_version} system are not compatible with' -+ ' {target_distro} {target_version}.' - .format( - fpath=DEFAULT_OPENSSL_CONF, -- source=version.get_source_major_version(), -- target=version.get_target_major_version() -+ source_version=version.get_source_major_version(), -+ target_version=version.get_target_major_version(), -+ **DISTRO_REPORT_NAMES - ) - ) - if version.get_target_major_version() == '9': - # NOTE(pstodulk): that a try to make things with engine/providers a - # little bit better (see my TODO note above) - summary += ( -- '\n\nNote the legacy ENGINE API is deprecated since RHEL 8 and' -+ '\n\nNote the legacy ENGINE API is deprecated since {target_distro} 8 and' - ' it is required to use the new OpenSSL providers API instead on' -- ' RHEL 9 systems.' -+ ' {target_distro} 9 systems.'.format_map(DISTRO_REPORT_NAMES) - ) - hint = ( - 'Check that your ability to login to the system does not depend on' -diff --git a/repos/system_upgrade/common/actors/peseventsscanner/libraries/pes_events_scanner.py b/repos/system_upgrade/common/actors/peseventsscanner/libraries/pes_events_scanner.py -index 7c11fd21..6ba16ced 100644 ---- a/repos/system_upgrade/common/actors/peseventsscanner/libraries/pes_events_scanner.py -+++ b/repos/system_upgrade/common/actors/peseventsscanner/libraries/pes_events_scanner.py -@@ -7,6 +7,7 @@ from leapp.libraries.actor import peseventsscanner_repomap - from leapp.libraries.actor.pes_event_parsing import Action, get_pes_events, Package - from leapp.libraries.common import rpms - from leapp.libraries.common.config import get_target_distro_id, version -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.stdlib import api - from leapp.libraries.stdlib.config import is_verbose - from leapp.models import ( -@@ -457,30 +458,37 @@ def replace_pesids_with_repoids_in_packages(packages, source_pkgs_repoids): - - required_target_pesids = {pkg.repository for pkg in packages_with_pesid} - -- pesid_to_repoid_map = get_pesid_to_repoid_map(required_target_pesids) -+ pesid_to_target_repoid_map = get_pesid_to_repoid_map(required_target_pesids) - -- packages_without_known_repoid = {pkg for pkg in packages_with_pesid if pkg.repository not in pesid_to_repoid_map} -+ packages_with_unknown_target_repoid = { -+ pkg -+ for pkg in packages_with_pesid -+ if pkg.repository not in pesid_to_target_repoid_map -+ } - -- if packages_without_known_repoid: -+ if packages_with_unknown_target_repoid: - report_skipped_packages( - title='Packages from unknown repositories may not be installed', - message='packages may not be installed or upgraded due to repositories unknown to leapp:', -- skipped_pkgs=packages_without_known_repoid, -+ skipped_pkgs=packages_with_unknown_target_repoid, - remediation=( -- 'In case the listed repositories are mirrors of official repositories for RHEL' -- ' (provided by Red Hat on CDN)' -- ' and their repositories IDs has been customized, you can change' -+ 'In case the listed repositories are mirrors of official repositories for {}' -+ ' and their repositories IDs have been customized, you can change' - ' the configuration to use the official IDs instead of fixing the problem.' - ' You can also review the projected DNF upgrade transaction result' - ' in the logs to see what is going to happen, as this does not necessarily mean' - ' that the listed packages will not be upgraded. You can also' -- ' install any missing packages after the in-place upgrade manually.' -+ ' install any missing packages after the in-place upgrade manually.'.format( -+ 'RHEL (provided by Red Hat on CDN)' -+ if get_target_distro_id() == 'rhel' -+ else DISTRO_REPORT_NAMES.target -+ ) - ), - ) - -- packages_with_known_repoid = packages_with_pesid.difference(packages_without_known_repoid) -+ packages_with_known_repoid = packages_with_pesid.difference(packages_with_unknown_target_repoid) - packages_with_repoid = { -- Package(p.name, pesid_to_repoid_map[p.repository], p.modulestream) for p in packages_with_known_repoid -+ Package(p.name, pesid_to_target_repoid_map[p.repository], p.modulestream) for p in packages_with_known_repoid - } - # Packages without pesid are those for which we do not have an event, keep them in target packages - return packages_with_repoid.union(packages_without_pesid) -diff --git a/repos/system_upgrade/el8toel9/actors/checkcustomnetworkscripts/libraries/customnetworkscripts.py b/repos/system_upgrade/el8toel9/actors/checkcustomnetworkscripts/libraries/customnetworkscripts.py -index 2947aa27..a4c67efc 100644 ---- a/repos/system_upgrade/el8toel9/actors/checkcustomnetworkscripts/libraries/customnetworkscripts.py -+++ b/repos/system_upgrade/el8toel9/actors/checkcustomnetworkscripts/libraries/customnetworkscripts.py -@@ -1,6 +1,7 @@ - import os - - from leapp import reporting -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - - CUSTOM_NETWORK_SCRIPTS = [ - "/sbin/ifup-local", -@@ -17,9 +18,9 @@ def generate_report(existing_custom_network_scripts): - # Show documentation url if custom network-scripts detected - title = "custom network-scripts detected" - summary = ( -- "RHEL 9 does not support the legacy network-scripts package that was" -- " deprecated in RHEL 8. Custom network-scripts have been detected." -- ) -+ "{target_distro} 9 does not support the legacy network-scripts package that was" -+ " deprecated in {source_distro} 8. Custom network-scripts have been detected." -+ ).format_map(DISTRO_REPORT_NAMES) - - reporting.create_report( - [ -diff --git a/repos/system_upgrade/el8toel9/actors/checkcustomnetworkscripts/tests/unit_test_customnetworkscripts.py b/repos/system_upgrade/el8toel9/actors/checkcustomnetworkscripts/tests/unit_test_customnetworkscripts.py -index 5f57f5ba..2f3f0469 100644 ---- a/repos/system_upgrade/el8toel9/actors/checkcustomnetworkscripts/tests/unit_test_customnetworkscripts.py -+++ b/repos/system_upgrade/el8toel9/actors/checkcustomnetworkscripts/tests/unit_test_customnetworkscripts.py -@@ -3,11 +3,13 @@ import os - from leapp import reporting - from leapp.libraries.actor import customnetworkscripts - from leapp.libraries.common import testutils -+from leapp.libraries.stdlib import api - - - def test_customnetworkscripts_exists(monkeypatch): - monkeypatch.setattr(os.path, "isfile", lambda dummy: True) - monkeypatch.setattr(reporting, "create_report", testutils.create_report_mocked()) -+ monkeypatch.setattr(api, "current_actor", testutils.CurrentActorMocked()) - customnetworkscripts.process() - assert reporting.create_report.called - -diff --git a/repos/system_upgrade/el8toel9/actors/checkdeprecatedrpmsignature/libraries/checkdeprecatedrpmsignature.py b/repos/system_upgrade/el8toel9/actors/checkdeprecatedrpmsignature/libraries/checkdeprecatedrpmsignature.py -index 0ab59495..3e8bdbe7 100644 ---- a/repos/system_upgrade/el8toel9/actors/checkdeprecatedrpmsignature/libraries/checkdeprecatedrpmsignature.py -+++ b/repos/system_upgrade/el8toel9/actors/checkdeprecatedrpmsignature/libraries/checkdeprecatedrpmsignature.py -@@ -1,4 +1,5 @@ - from leapp import reporting -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.stdlib import api - from leapp.models import CryptoPolicyInfo, InstalledRPM - -@@ -11,7 +12,7 @@ FMT_LIST_SEPARATOR = '\n - ' - # framework prints external links in the file as well. - SUMMARY_FMT = ( - 'Digital signatures using SHA-1 hash algorithm are no longer considered' -- ' secure and are not allowed to be used on RHEL 9 systems by default.' -+ ' secure and are not allowed to be used on {target_distro} 9 systems by default.' - ' This causes issues when using DNF/RPM to handle packages with RSA/SHA1' - ' signatures as the signature cannot be checked with the default' - ' cryptographic policy. Any such packages cannot be installed, removed,' -@@ -68,6 +69,7 @@ def process(): - report = [ - reporting.Title('Detected RPMs with RSA/SHA1 signature'), - reporting.Summary(SUMMARY_FMT.format( -+ target_distro=DISTRO_REPORT_NAMES.target, - major_changes_url=MAJOR_CHANGE_URL, - crypto_policies_url=CRYPTO_POLICIES_URL, - bad_pkgs=bad_rpms_str -diff --git a/repos/system_upgrade/el8toel9/actors/firewalldcheckallowzonedrifting/actor.py b/repos/system_upgrade/el8toel9/actors/firewalldcheckallowzonedrifting/actor.py -index 6f1c8f43..3b5f87a5 100644 ---- a/repos/system_upgrade/el8toel9/actors/firewalldcheckallowzonedrifting/actor.py -+++ b/repos/system_upgrade/el8toel9/actors/firewalldcheckallowzonedrifting/actor.py -@@ -1,5 +1,6 @@ - from leapp import reporting - from leapp.actors import Actor -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.models import FirewalldGlobalConfig, FirewallsFacts - from leapp.reporting import create_report, Report - from leapp.tags import ChecksPhaseTag, IPUWorkflowTag -@@ -34,10 +35,14 @@ class FirewalldCheckAllowZoneDrifting(Actor): - - create_report([ - reporting.Title('Firewalld Configuration AllowZoneDrifting Is Unsupported'), -- reporting.Summary('Firewalld has enabled configuration option ' -- '"{conf_key}" which has been removed in RHEL-9. ' -- 'New behavior is as if "{conf_key}" was set to "no".'.format( -- conf_key='AllowZoneDrifting')), -+ reporting.Summary( -+ 'Firewalld has enabled configuration option "{conf_key}" which' -+ ' has been removed in {target_distro} 9. New behavior is as if ' -+ '"{conf_key}" was set to "no".'.format( -+ target_distro=DISTRO_REPORT_NAMES.target, -+ conf_key='AllowZoneDrifting', -+ ) -+ ), - reporting.Severity(reporting.Severity.HIGH), - reporting.Groups([reporting.Groups.SANITY, reporting.Groups.FIREWALL]), - reporting.Groups([reporting.Groups.INHIBITOR]), -diff --git a/repos/system_upgrade/el8toel9/actors/firewalldcheckallowzonedrifting/tests/component_test_firewalldcheckallowzonedrifting.py b/repos/system_upgrade/el8toel9/actors/firewalldcheckallowzonedrifting/tests/component_test_firewalldcheckallowzonedrifting.py -index 9908fa29..eaeed253 100644 ---- a/repos/system_upgrade/el8toel9/actors/firewalldcheckallowzonedrifting/tests/component_test_firewalldcheckallowzonedrifting.py -+++ b/repos/system_upgrade/el8toel9/actors/firewalldcheckallowzonedrifting/tests/component_test_firewalldcheckallowzonedrifting.py -@@ -1,8 +1,11 @@ -+from leapp.libraries.common import distro - from leapp.models import FirewalldGlobalConfig, FirewallsFacts, FirewallStatus - from leapp.reporting import Report - - --def test_actor_firewalldcheckallowzonedrifting(current_actor_context): -+def test_actor_firewalldcheckallowzonedrifting(current_actor_context, monkeypatch): -+ monkeypatch.setattr(distro, 'get_target_distro_id', lambda: 'rhel') -+ - status = FirewallStatus(enabled=True, active=True) - current_actor_context.feed(FirewallsFacts(firewalld=status, - iptables=status, -diff --git a/repos/system_upgrade/el8toel9/actors/firewalldcheckservicetftpclient/actor.py b/repos/system_upgrade/el8toel9/actors/firewalldcheckservicetftpclient/actor.py -index f6aa7393..0719ee1e 100644 ---- a/repos/system_upgrade/el8toel9/actors/firewalldcheckservicetftpclient/actor.py -+++ b/repos/system_upgrade/el8toel9/actors/firewalldcheckservicetftpclient/actor.py -@@ -1,5 +1,6 @@ - from leapp import reporting - from leapp.actors import Actor -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.models import FirewalldUsedObjectNames - from leapp.reporting import create_report, Report - from leapp.tags import ChecksPhaseTag, IPUWorkflowTag -@@ -27,9 +28,13 @@ class FirewalldCheckServiceTftpClient(Actor): - if send_report: - create_report([ - reporting.Title('Firewalld Service tftp-client Is Unsupported'), -- reporting.Summary('Firewalld has service "{service}" enabled. ' -- 'Service "{service}" has been removed in RHEL-9.'.format( -- service=tftp_client_service)), -+ reporting.Summary( -+ 'Firewalld has service "{service}" enabled. ' -+ 'Service "{service}" has been removed in {target_distro} 9.'.format( -+ service=tftp_client_service, -+ target_distro=DISTRO_REPORT_NAMES.target, -+ ) -+ ), - reporting.Severity(reporting.Severity.HIGH), - reporting.Groups([reporting.Groups.SANITY, reporting.Groups.FIREWALL]), - reporting.Groups([reporting.Groups.INHIBITOR]), -diff --git a/repos/system_upgrade/el8toel9/actors/firewalldcheckservicetftpclient/tests/component_test_firewalldcollectusedobjectnames.py b/repos/system_upgrade/el8toel9/actors/firewalldcheckservicetftpclient/tests/component_test_firewalldcollectusedobjectnames.py -index ce964301..1ed33fb0 100644 ---- a/repos/system_upgrade/el8toel9/actors/firewalldcheckservicetftpclient/tests/component_test_firewalldcollectusedobjectnames.py -+++ b/repos/system_upgrade/el8toel9/actors/firewalldcheckservicetftpclient/tests/component_test_firewalldcollectusedobjectnames.py -@@ -1,11 +1,13 @@ -+from leapp.libraries.common import distro - from leapp.models import FirewalldUsedObjectNames - from leapp.reporting import Report - - --def test_actor_firewalldcheckservicetftpclient(current_actor_context): -+def test_actor_firewalldcheckservicetftpclient(current_actor_context, monkeypatch): - services = ['cockpit', 'tftp-client', 'ssh', 'https'] - policies = [] - zones = [] -+ monkeypatch.setattr(distro, 'get_target_distro_id', lambda: 'rhel') - - current_actor_context.feed(FirewalldUsedObjectNames(services=services, - policies=policies, -diff --git a/repos/system_upgrade/el8toel9/actors/mariadbcheck/libraries/mariadbcheck.py b/repos/system_upgrade/el8toel9/actors/mariadbcheck/libraries/mariadbcheck.py -index c56c6422..6fe7d669 100644 ---- a/repos/system_upgrade/el8toel9/actors/mariadbcheck/libraries/mariadbcheck.py -+++ b/repos/system_upgrade/el8toel9/actors/mariadbcheck/libraries/mariadbcheck.py -@@ -1,25 +1,9 @@ - from leapp import reporting -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.common.rpms import has_package - from leapp.libraries.stdlib import api - from leapp.models import DistributionSignedRPM - --# Summary for mariadb-server report --report_server_inst_summary = ( -- 'MariaDB server component will be upgraded. Since RHEL-9 includes' -- ' MariaDB server 10.5 by default, which is incompatible with 10.3' -- ' included in RHEL-8, it is necessary to proceed with additional steps' -- ' for the complete upgrade of the MariaDB data.' --) -- --report_server_inst_hint = ( -- 'Back up your data before proceeding with the upgrade' -- ' and follow steps in the documentation section "Migrating to a RHEL 9 version of MariaDB"' -- ' after the upgrade.' --) -- --# Link URL for mariadb-server report --report_server_inst_link_url = 'https://access.redhat.com/articles/6743671' -- - - def _report_server_installed(): - """ -@@ -29,15 +13,30 @@ def _report_server_installed(): - installation, warn them about necessary additional steps, and - redirect them to online documentation for the upgrade process. - """ -+ summary = ( -+ 'MariaDB server component will be upgraded. Since {target_distro} 9' -+ ' includes MariaDB server 10.5 by default, which is incompatible with' -+ ' 10.3 included in {source_distro} 8, it is necessary to proceed with' -+ ' additional steps for the complete upgrade of the MariaDB data.' -+ ).format_map(DISTRO_REPORT_NAMES) -+ -+ hint = ( -+ 'Back up your data before proceeding with the upgrade and follow steps' -+ ' in the documentation section "Migrating to a RHEL 9 version of MariaDB"' -+ ' after the upgrade.' -+ ) -+ - reporting.create_report([ - reporting.Title('MariaDB (mariadb-server) has been detected on your system'), -- reporting.Summary(report_server_inst_summary), -+ reporting.Summary(summary), - reporting.Severity(reporting.Severity.MEDIUM), - reporting.Groups([reporting.Groups.SERVICES]), -- reporting.ExternalLink(title='Migrating to a RHEL 9 version of MariaDB', -- url=report_server_inst_link_url), -+ reporting.ExternalLink( -+ title='Migrating to a RHEL 9 version of MariaDB', -+ url='https://access.redhat.com/articles/6743671' -+ ), - reporting.RelatedResource('package', 'mariadb-server'), -- reporting.Remediation(hint=report_server_inst_hint), -+ reporting.Remediation(hint=hint), - ]) - - -diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfcheck/libraries/multipathconfcheck.py b/repos/system_upgrade/el8toel9/actors/multipathconfcheck/libraries/multipathconfcheck.py -index 31b8bc40..083d3342 100644 ---- a/repos/system_upgrade/el8toel9/actors/multipathconfcheck/libraries/multipathconfcheck.py -+++ b/repos/system_upgrade/el8toel9/actors/multipathconfcheck/libraries/multipathconfcheck.py -@@ -1,4 +1,5 @@ - from leapp import reporting -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.reporting import create_report - - -@@ -8,15 +9,18 @@ def _report_foreign(): - 'device-mapper-multipath now defaults to ignoring foreign devices' - ), - reporting.Summary( -- 'In RHEL-9, the default value for the "enable_foreign" option has ' -- 'changed to "NONE". This means that multipath will no longer list ' -- 'devices that are not managed by device-mapper. In order to retain ' -- 'the default RHEL-8 behavior of listing foreign multipath devices, ' -- '\'enable_foreign ""\' will be added to the defaults section of ' -- '"/etc/multipath.conf". If you wish to change to the default ' -- 'RHEL-9 behavior, please remove this line. This option only ' -- 'effects the devices that multipath lists. It has no impact on ' -- 'what devices are managed.'), -+ 'In {target_distro} 9, the default value for the "enable_foreign" ' -+ 'option has changed to "NONE". This means that multipath will no ' -+ 'longer list devices that are not managed by device-mapper. In ' -+ 'order to retain the default {source_distro} 8 behavior of listing ' -+ 'foreign multipath devices, \'enable_foreign ""\' will be added to ' -+ 'the defaults section of "/etc/multipath.conf". If you wish to ' -+ 'change to the default {target_distro} 9 behavior, please remove ' -+ 'this line. This option only affects the devices that multipath ' -+ 'lists. It has no impact on what devices are managed.'.format_map( -+ DISTRO_REPORT_NAMES -+ ) -+ ), - reporting.Severity(reporting.Severity.INFO), - reporting.Groups([reporting.Groups.SERVICES]), - reporting.RelatedResource('package', 'device-mapper-multipath') -@@ -29,13 +33,15 @@ def _report_allow_usb(): - 'device-mapper-multipath now defaults to ignoring USB devices' - ), - reporting.Summary( -- 'In RHEL-9, the default multipath configuration has changed to ' -- 'ignore USB devices. A new config option, "allow_usb_devices" has ' -- 'been added to control this. In order to retain the RHEL-8 ' -- 'behavior of treating USB devices like other block devices. ' -- '"allow_usb_devices yes" will be added to the defaults section ' -- 'of "/etc/multipath.conf". If you wish to change to the default ' -- 'RHEL-9 behavior, please remove this line.'), -+ 'In {target_distro} 9, the default multipath configuration has ' -+ 'changed to ignore USB devices. A new config option, ' -+ '"allow_usb_devices" has been added to control this. In order to ' -+ 'retain the {source_distro} 8 behavior of treating USB devices ' -+ 'like other block devices. "allow_usb_devices yes" will be added ' -+ 'to the defaults section of "/etc/multipath.conf". If you wish to ' -+ 'change to the default {target_distro} 9 behavior, please remove ' -+ 'this line.'.format_map(DISTRO_REPORT_NAMES) -+ ), - reporting.Severity(reporting.Severity.INFO), - reporting.Groups([reporting.Groups.SERVICES]), - reporting.RelatedResource('package', 'device-mapper-multipath') -@@ -56,11 +62,16 @@ def _report_invalid_regexes(paths): - ), - reporting.Summary( - 'Some options in device-mapper-multipath configuration files ' -- 'have values that are regular expressions. In RHEL-8, if such an ' -- 'option had a value of "*", multipath would internally convert it ' -- 'to ".*". In RHEL-9, values of "*" are no longer accepted. ' -- 'These regular expression values have been found in {}. They ' -- 'will be converted to ".*"'.format(paths_str)), -+ ' have values that are regular expressions. In {source_distro} 8,' -+ ' if such an option had a value of "*", multipath would internally' -+ 'convert it to ".*". In {target_distro} 9, values of "*" are no ' -+ 'longer accepted. ' -+ 'These regular expression values have been found in {paths}. They ' -+ 'will be converted to ".*"'.format( -+ paths=paths_str, -+ **DISTRO_REPORT_NAMES -+ ) -+ ), - reporting.Severity(reporting.Severity.INFO), - reporting.Groups([reporting.Groups.SERVICES]), - reporting.RelatedResource('package', 'device-mapper-multipath') -diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfcheck/tests/test_multipath_conf_check_8to9.py b/repos/system_upgrade/el8toel9/actors/multipathconfcheck/tests/test_multipath_conf_check_8to9.py -index b91d414c..981b9455 100644 ---- a/repos/system_upgrade/el8toel9/actors/multipathconfcheck/tests/test_multipath_conf_check_8to9.py -+++ b/repos/system_upgrade/el8toel9/actors/multipathconfcheck/tests/test_multipath_conf_check_8to9.py -@@ -1,3 +1,6 @@ -+import pytest -+ -+from leapp.libraries.common import distro - from leapp.models import MultipathConfFacts8to9, MultipathConfig8to9 - from leapp.reporting import Report - -@@ -35,6 +38,12 @@ def _build_facts(confs): - return MultipathConfFacts8to9(configs=confs) - - -+@pytest.fixture(autouse=True) -+def mock_distro_names(monkeypatch): -+ monkeypatch.setattr(distro, 'get_target_distro_id', lambda: 'rhel') -+ monkeypatch.setattr(distro, 'get_source_distro_id', lambda: 'rhel') -+ -+ - def test_need_everything(current_actor_context): - config = _build_config('need_everything.conf', None, False, True, False) - facts = _build_facts([config]) -diff --git a/repos/system_upgrade/el8toel9/actors/mysqlcheck/libraries/mysqlcheck.py b/repos/system_upgrade/el8toel9/actors/mysqlcheck/libraries/mysqlcheck.py -index b446d9c4..c0865812 100644 ---- a/repos/system_upgrade/el8toel9/actors/mysqlcheck/libraries/mysqlcheck.py -+++ b/repos/system_upgrade/el8toel9/actors/mysqlcheck/libraries/mysqlcheck.py -@@ -1,4 +1,5 @@ - from leapp import reporting -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.common.rpms import has_package - from leapp.models import DistributionSignedRPM - -@@ -14,16 +15,18 @@ def _report_server_installed(): - reporting.create_report([ - reporting.Title('Further action to upgrade MySQL might be needed'), - reporting.Summary( -- 'The MySQL server component will be reinstalled during the upgrade with a RHEL 9' -- ' version. Since RHEL 9 includes the same MySQL version 8.0 by default, no action' -+ 'The MySQL server component will be reinstalled during the upgrade with a {target_distro} 9' -+ ' version. Since {target_distro} 9 includes the same MySQL version 8.0 by default, no action' - ' should be required and there should not be any compatibility issues. However,' - ' it is still advisable to follow the documentation on this topic for up to date' - ' recommendations.' -- ' Keep in mind that MySQL 8.0, which is the default in RHEL 9, will reach the end' -+ ' Keep in mind that MySQL 8.0, which is the default in {target_distro} 9, will reach the end' - ' of \'Extended Support\' in April 2026. As such it is advisable to upgrade to' - ' MySQL version 8.4, which is provided via a module. MySQL 8.4 is also the' -- ' default version for RHEL 10, therefore having MySQL 8.4 on the RHEL 9 system' -- ' will make a future upgrade process to RHEL 10 smoother.' -+ ' default version for {target_distro} 10, therefore having MySQL 8.4 on the {target_distro} 9 system' -+ ' will make a future upgrade process to {target_distro} 10 smoother.'.format_map( -+ DISTRO_REPORT_NAMES -+ ) - ), - reporting.Severity(reporting.Severity.MEDIUM), - reporting.Groups([reporting.Groups.SERVICES]), -diff --git a/repos/system_upgrade/el8toel9/actors/nischeck/libraries/nischeck.py b/repos/system_upgrade/el8toel9/actors/nischeck/libraries/nischeck.py -index c5d85977..9ddd9ca7 100644 ---- a/repos/system_upgrade/el8toel9/actors/nischeck/libraries/nischeck.py -+++ b/repos/system_upgrade/el8toel9/actors/nischeck/libraries/nischeck.py -@@ -1,22 +1,10 @@ - from leapp import reporting - from leapp.exceptions import StopActorExecutionError -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.common.rpms import has_package - from leapp.libraries.stdlib import api - from leapp.models import DistributionSignedRPM, NISConfig - --report_summary = ( -- 'The NIS components (ypserv, ypbind, and yp-tools) are no longer available in RHEL-9.' -- ' The technology behind those packages is based an outdated design patterns, which are' -- ' no longer considered as secure. There is no direct alternative with fully compatible' -- ' features.' --) -- --report_hint = ( -- 'The alternatives are LDAP and for some use cases Kerberos or migrating to IPA.' --) -- --report_link_url = 'https://access.redhat.com/solutions/5991271' -- - - def report_nis(): - """ -@@ -55,15 +43,26 @@ def report_nis(): - if not rpms_configured_installed: - return - -+ report_summary = ( -+ 'The NIS components (ypserv, ypbind, and yp-tools) are no longer available' -+ ' in {target_distro} 9. The technology behind those packages is based an ' -+ ' outdated design patterns, which are no longer considered as secure. There' -+ ' is no direct alternative with fully compatible features.' -+ ).format_map(DISTRO_REPORT_NAMES) -+ - # Create report - report_content = [ - reporting.Title('NIS component has been detected on your system'), - reporting.Summary(report_summary), - reporting.Severity(reporting.Severity.MEDIUM), - reporting.Groups([reporting.Groups.SERVICES]), -- reporting.ExternalLink(title='RHEL 9 (NIS) discontinuation', -- url=report_link_url), -- reporting.Remediation(hint=report_hint), -+ reporting.ExternalLink( -+ title='RHEL 9 (NIS) discontinuation', -+ url='https://access.redhat.com/solutions/5991271', -+ ), -+ reporting.Remediation( -+ hint='The alternatives are LDAP and for some use cases Kerberos or migrating to IPA.' -+ ), - ] - - related_resources = [reporting.RelatedResource('package', pkg) for pkg in rpms_configured_installed] -diff --git a/repos/system_upgrade/el8toel9/actors/opensshdropindirectorycheck/actor.py b/repos/system_upgrade/el8toel9/actors/opensshdropindirectorycheck/actor.py -index 5d52e3ca..4ec85fb0 100644 ---- a/repos/system_upgrade/el8toel9/actors/opensshdropindirectorycheck/actor.py -+++ b/repos/system_upgrade/el8toel9/actors/opensshdropindirectorycheck/actor.py -@@ -50,7 +50,7 @@ class OpenSshDropInDirectoryCheck(Actor): - reporting.Title('The upgrade will prepend the Include directive to OpenSSH sshd_config'), - reporting.Summary( - 'OpenSSH server configuration needs to be modified to contain Include directive ' -- 'for the RHEL9 to work properly and integrate with the other parts of the OS. ' -+ 'for the target system to work properly and integrate with the other parts of the OS. ' - 'The following snippet will be added to the /etc/ssh/sshd_config during the ' - 'ApplicationsPhase: `Include /etc/ssh/sshd_config.d/*.conf`' - ), -diff --git a/repos/system_upgrade/el8toel9/actors/opensshsubsystemsftp/libraries/opensshsubsystemsftp.py b/repos/system_upgrade/el8toel9/actors/opensshsubsystemsftp/libraries/opensshsubsystemsftp.py -index 3264a8de..306fef7e 100644 ---- a/repos/system_upgrade/el8toel9/actors/opensshsubsystemsftp/libraries/opensshsubsystemsftp.py -+++ b/repos/system_upgrade/el8toel9/actors/opensshsubsystemsftp/libraries/opensshsubsystemsftp.py -@@ -1,5 +1,6 @@ - from leapp import reporting - from leapp.exceptions import StopActorExecutionError -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.stdlib import api - - -@@ -32,9 +33,10 @@ def process(openssh_messages): - reporting.create_report([ - reporting.Title('OpenSSH configured without SFTP subsystem'), - reporting.Summary( -- 'The RHEL9 is changing the default SCP behaviour to use SFTP internally ' -+ 'The {target_distro} 9 is changing the default SCP behaviour to use SFTP internally ' - 'so not having SFTP server enabled can prevent interoperability and break existing ' -- 'scripts on other systems updated to RHEL9 to copy files to or from this machine.' -+ 'scripts on other systems updated to {target_distro} 9 to copy files to or from ' -+ 'this machine.'.format_map(DISTRO_REPORT_NAMES) - ), - reporting.Remediation( - hint='Add the following line to the /etc/ssh/sshd_config to enable SFTP server: ' -diff --git a/repos/system_upgrade/el8toel9/actors/opensshsubsystemsftp/tests/test_opensshsubsystemsftp.py b/repos/system_upgrade/el8toel9/actors/opensshsubsystemsftp/tests/test_opensshsubsystemsftp.py -index 4e3c2ace..4ffe1112 100644 ---- a/repos/system_upgrade/el8toel9/actors/opensshsubsystemsftp/tests/test_opensshsubsystemsftp.py -+++ b/repos/system_upgrade/el8toel9/actors/opensshsubsystemsftp/tests/test_opensshsubsystemsftp.py -@@ -2,6 +2,7 @@ import pytest - - from leapp.exceptions import StopActorExecutionError - from leapp.libraries.actor import opensshsubsystemsftp -+from leapp.libraries.common import distro - from leapp.models import OpenSshConfig, Report - - -@@ -17,7 +18,9 @@ def test_no_config(current_actor_context): - (True, 'internal-sftp', False), - (True, '/usr/libexec/openssh/sftp-server', False) - ]) --def test_subsystem(current_actor_context, modified, subsystem, expected_report): -+def test_subsystem(monkeypatch, current_actor_context, modified, subsystem, expected_report): -+ monkeypatch.setattr(distro, 'get_target_distro_id', lambda: 'rhel') -+ - conf = OpenSshConfig( - modified=modified, - permit_root_login=[], -diff --git a/repos/system_upgrade/el8toel9/actors/postgresqlcheck/libraries/postgresqlcheck.py b/repos/system_upgrade/el8toel9/actors/postgresqlcheck/libraries/postgresqlcheck.py -index 1cc5362d..0952e3b5 100644 ---- a/repos/system_upgrade/el8toel9/actors/postgresqlcheck/libraries/postgresqlcheck.py -+++ b/repos/system_upgrade/el8toel9/actors/postgresqlcheck/libraries/postgresqlcheck.py -@@ -1,27 +1,9 @@ - from leapp import reporting -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.common.rpms import has_package - from leapp.libraries.stdlib import api - from leapp.models import DistributionSignedRPM - --# Summary for postgresql-server report --report_server_inst_summary = ( -- 'PostgreSQL server component will be upgraded. Since RHEL-9 includes' -- ' PostgreSQL server 13 by default, which is incompatible with 9.6, 10 and 12' -- ' included in RHEL-8, in those cases, it is necessary to proceed with additional steps' -- ' for the complete upgrade of the PostgreSQL data.' -- 'If the database has already been upgraded, meaning the system is already using PostgreSQL 13,' -- ' then no further actions are required.' --) -- --report_server_inst_hint = ( -- 'Back up your data before proceeding with the upgrade' -- ' and follow steps in the documentation section "Migrating to a RHEL 9 version of PostgreSQL"' -- ' after the upgrade.' --) -- --# Link URL for postgresql-server report --report_server_inst_link_url = 'https://access.redhat.com/articles/6654721' -- - - def _report_server_installed(): - """ -@@ -31,16 +13,37 @@ def _report_server_installed(): - installation, warn them about necessary additional steps, and - redirect them to online documentation for the upgrade process. - """ -- reporting.create_report([ -- reporting.Title('PostgreSQL (postgresql-server) has been detected on your system'), -- reporting.Summary(report_server_inst_summary), -- reporting.Severity(reporting.Severity.MEDIUM), -- reporting.Groups([reporting.Groups.SERVICES]), -- reporting.ExternalLink(title='Migrating to a RHEL 9 version of PostgreSQL', -- url=report_server_inst_link_url), -- reporting.RelatedResource('package', 'postgresql-server'), -- reporting.Remediation(hint=report_server_inst_hint), -- ]) -+ summary = ( -+ 'PostgreSQL server component will be upgraded. Since {target_distro} 9' -+ ' includes PostgreSQL server 13 by default, which is incompatible with 9.6,' -+ ' 10 and 12 included in {source_distro} 8, in those cases, it is necessary' -+ ' to proceed with additional steps for the complete upgrade of the PostgreSQL' -+ ' data. If the database has already been upgraded, meaning the system is' -+ ' already using PostgreSQL 13, then no further actions are required.' -+ ).format_map(DISTRO_REPORT_NAMES) -+ -+ hint = ( -+ 'Back up your data before proceeding with the upgrade' -+ ' and follow steps in the documentation section "Migrating to a RHEL 9 version of PostgreSQL"' -+ ' after the upgrade.' -+ ) -+ -+ reporting.create_report( -+ [ -+ reporting.Title( -+ "PostgreSQL (postgresql-server) has been detected on your system" -+ ), -+ reporting.Summary(summary), -+ reporting.Severity(reporting.Severity.MEDIUM), -+ reporting.Groups([reporting.Groups.SERVICES]), -+ reporting.ExternalLink( -+ title="Migrating to a RHEL 9 version of PostgreSQL", -+ url='https://access.redhat.com/articles/6654721', -+ ), -+ reporting.RelatedResource("package", "postgresql-server"), -+ reporting.Remediation(hint=hint), -+ ] -+ ) - - - def report_installed_packages(_context=api): -diff --git a/repos/system_upgrade/el9toel10/actors/check_default_initramfs/libraries/check_default_initramfs.py b/repos/system_upgrade/el9toel10/actors/check_default_initramfs/libraries/check_default_initramfs.py -index 098b5fde..706eddc8 100644 ---- a/repos/system_upgrade/el9toel10/actors/check_default_initramfs/libraries/check_default_initramfs.py -+++ b/repos/system_upgrade/el9toel10/actors/check_default_initramfs/libraries/check_default_initramfs.py -@@ -2,6 +2,7 @@ import os - - from leapp import reporting - from leapp.exceptions import StopActorExecutionError -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.stdlib import api - from leapp.models import DefaultInitramfsInfo - -@@ -14,15 +15,17 @@ def check_default_initramfs(): - - if 'network-legacy' in default_initramfs_info.used_dracut_modules: - summary = ( -- f'Initramfs ({default_initramfs_info.path}) of the default boot entry uses dracut ' -+ 'Initramfs ({initramfs_path}) of the default boot entry uses dracut ' - 'modules that are missing on the target system. This could cause a fatal ' - 'failure during the upgrade, resulting in unbootable system as ' - 'the missing dracut module could prevent creation of the required target ' - 'initramfs.\n\n' - 'Namely, the legacy-network dracut module is used on this system, which ' -- 'could originate from older system installations. The problem is typical ' -- 'for RHEL 7 and early RHEL 8 systems that were in-place-upgraded to RHEL 9.' -- ) -+ 'could originate from older system installations. ' -+ 'The problem is typical for {source_distro} 7 and early {source_distro} 8 ' -+ 'systems that were in-place-upgraded to {source_distro} 9.' -+ ).format(**DISTRO_REPORT_NAMES, initramfs_path=default_initramfs_info.path) -+ - remediation_hint = ( - 'Remove the dracut config file which adds the `network-legacy` dracut module. ' - 'Then rebuild existing initramfs images to remove the dracut module from them.' -diff --git a/repos/system_upgrade/el9toel10/actors/checkoldxfs/libraries/checkoldxfs.py b/repos/system_upgrade/el9toel10/actors/checkoldxfs/libraries/checkoldxfs.py -index 0069ae7f..8b35744b 100644 ---- a/repos/system_upgrade/el9toel10/actors/checkoldxfs/libraries/checkoldxfs.py -+++ b/repos/system_upgrade/el9toel10/actors/checkoldxfs/libraries/checkoldxfs.py -@@ -1,10 +1,9 @@ - from leapp import reporting - from leapp.exceptions import StopActorExecutionError -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.stdlib import api - from leapp.models import XFSInfoFacts - --RHEL_9_TO_10_BACKUP_RESTORE_LINK = 'https://red.ht/rhel_9_to_10_backup_restore_xfs' -- - FMT_LIST_SEPARATOR = '\n - ' - - -@@ -71,14 +70,16 @@ def _inhibit_upgrade(invalid_bigtime, invalid_crc): - def _report_bigtime(invalid_bigtime): - title = 'Detected XFS filesystems without bigtime feature.' - summary = ( -- 'The XFS v5 filesystem format introduced the "bigtime" feature in RHEL 9,' -- ' to support timestamps beyond the year 2038. XFS filesystems that' -+ 'The XFS v5 filesystem format introduced the "bigtime" feature in' -+ ' {distro} 9, to support timestamps beyond the year 2038. XFS filesystems that' - ' do not have the "bigtime" feature enabled remain vulnerable to timestamp' - ' overflow issues. It is recommended to enable this feature on all' - ' XFS filesystems to ensure long-term compatibility and prevent potential' - ' failures.' -- ' Following XFS file systems have not enabled the "bigtime" feature:{}' -- .format(''.join(_formatted_list_output(invalid_bigtime))) -+ ' Following XFS file systems have not enabled the "bigtime" feature:{fs_list}'.format( -+ distro=DISTRO_REPORT_NAMES.target, -+ fs_list=''.join(_formatted_list_output(invalid_bigtime)) -+ ) - ) - - # NOTE(pstodulk): This will affect any system which upgraded from RHEL 8 so -diff --git a/repos/system_upgrade/el9toel10/actors/inhibitcgroupsv1/libraries/inhibitcgroupsv1.py b/repos/system_upgrade/el9toel10/actors/inhibitcgroupsv1/libraries/inhibitcgroupsv1.py -index 0a38ace3..6ae41be2 100644 ---- a/repos/system_upgrade/el9toel10/actors/inhibitcgroupsv1/libraries/inhibitcgroupsv1.py -+++ b/repos/system_upgrade/el9toel10/actors/inhibitcgroupsv1/libraries/inhibitcgroupsv1.py -@@ -1,5 +1,6 @@ - from leapp import reporting - from leapp.exceptions import StopActorExecutionError -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.stdlib import api - from leapp.models import KernelCmdline - -@@ -30,10 +31,12 @@ def process(): - remediation_cmd_args.append('systemd.legacy_systemd_cgroup_controller') - - summary = ( -- "Leapp detected cgroups-v1 is enabled on the system." -- " The support of cgroups-v1 was deprecated in RHEL 9 and is removed in RHEL 10." -- " Software requiring cgroups-v1 might not work correctly or at all on RHEL 10." -- ) -+ "Leapp detected cgroups-v1 is enabled on the system. The support of" -+ " cgroups-v1 was deprecated in {source_distro} 9 and is removed in" -+ " {target_distro} 10. Software requiring cgroups-v1 might not work" -+ " correctly or at all on {target_distro} 10." -+ ).format_map(DISTRO_REPORT_NAMES) -+ - reporting.create_report( - [ - reporting.Title("cgroups-v1 enabled on the system"), -diff --git a/repos/system_upgrade/el9toel10/actors/krb5conf/checkkrb5conf/libraries/checkkrb5conf.py b/repos/system_upgrade/el9toel10/actors/krb5conf/checkkrb5conf/libraries/checkkrb5conf.py -index 9feeb74e..406141cd 100644 ---- a/repos/system_upgrade/el9toel10/actors/krb5conf/checkkrb5conf/libraries/checkkrb5conf.py -+++ b/repos/system_upgrade/el9toel10/actors/krb5conf/checkkrb5conf/libraries/checkkrb5conf.py -@@ -1,5 +1,6 @@ - from leapp import reporting - from leapp.exceptions import StopActorExecutionError -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.stdlib import api - from leapp.models import OutdatedKrb5conf - -@@ -22,7 +23,8 @@ def process(): - reporting.Title('Unmanaged MIT krb5 configuration file(s) will be ' - 'updated to point to the new X.509 CA bundle file'), - reporting.Summary( -- 'On RHEL 10, the location of the reference X.509 CA bundle ' -+ f'On {DISTRO_REPORT_NAMES.target} 10, ' -+ 'the location of the reference X.509 CA bundle ' - 'file was modified. The following unmanaged MIT krb5 ' - 'configuration files have to be updated to point to the new ' - 'bundle file:' + __human_readable_list(msg.unmanaged_files)), -@@ -36,7 +38,8 @@ def process(): - reporting.Title('RPM-provided MIT krb5 configuration file(s) are ' - 'pointing to outdated X.509 CA bundle file'), - reporting.Summary( -- 'On RHEL 10, the location of the reference X.509 CA bundle ' -+ f'On {DISTRO_REPORT_NAMES.target} 10, ' -+ 'the location of the reference X.509 CA bundle ' - 'file was modified. Some MIT krb5 configuration files on this ' - 'system are pointing to the old bundle file, but are provided ' - 'by third-party RPMs. You must make sure these third-party ' -diff --git a/repos/system_upgrade/el9toel10/actors/libdbcheck/libraries/libdbcheck.py b/repos/system_upgrade/el9toel10/actors/libdbcheck/libraries/libdbcheck.py -index 84c03ef0..0eb773b0 100644 ---- a/repos/system_upgrade/el9toel10/actors/libdbcheck/libraries/libdbcheck.py -+++ b/repos/system_upgrade/el9toel10/actors/libdbcheck/libraries/libdbcheck.py -@@ -1,27 +1,9 @@ - from leapp import reporting -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.common.rpms import has_package - from leapp.libraries.stdlib import api - from leapp.models import DistributionSignedRPM - --# Summary for libdb report --report_libdb_inst_summary = ( -- 'Libdb was marked as deprecated in RHEL-9 and in RHEL-10 is not included anymore.' -- ' There are a couple of alternatives in RHEL-10; the applications that' -- ' depend on libdb will not work. Such applications must implement another' -- ' type of backend storage. And migrate existing data to the new database format.' --) -- --report_libdb_inst_hint = ( -- 'Back up your data before proceeding with the data upgrade/migration.' -- ' For the conversion, the tool db_converter from the libdb-utils' -- ' rpm could be used. This database format conversion must be performed' -- ' before the system upgrade. The db_converter is not available in RHEL 10' -- ' systems. For more information, see the provided article.' --) -- --# Link URL for libdb report --report_libdb_inst_link_url = 'https://access.redhat.com/articles/7099256' -- - - def _report_libdb_installed(): - """ -@@ -31,16 +13,32 @@ def _report_libdb_installed(): - installation, warn them about the lack of libdb support in RHEL 10, and - redirect them to online documentation for the migration process. - """ -+ summary = ( -+ 'Libdb was marked as deprecated in {source_distro} 9 and in' -+ ' {target_distro} 10 is not included anymore.' -+ ' There are a couple of alternatives in {target_distro} 10; the applications that' -+ ' depend on libdb will not work. Such applications must implement another' -+ ' type of backend storage. And migrate existing data to the new database format.' -+ ).format_map(DISTRO_REPORT_NAMES) -+ -+ hint_text = ( -+ 'Back up your data before proceeding with the data upgrade/migration.' -+ ' For the conversion, the tool db_converter from the libdb-utils' -+ ' rpm could be used. This database format conversion must be performed' -+ ' before the system upgrade. The db_converter is not available in' -+ ' {target_distro} 10 systems. For more information, see the provided article.' -+ ).format_map(DISTRO_REPORT_NAMES) -+ - reporting.create_report([ - reporting.Title('Berkeley DB (libdb) has been detected on your system'), -- reporting.Summary(report_libdb_inst_summary), -+ reporting.Summary(summary), - reporting.Severity(reporting.Severity.MEDIUM), - reporting.Groups([reporting.Groups.SERVICES]), - reporting.ExternalLink(title='Migrating to a RHEL 10 without libdb', -- url=report_libdb_inst_link_url), -+ url='https://access.redhat.com/articles/7099256'), - reporting.RelatedResource('package', 'libdb'), -- reporting.Remediation(hint=report_libdb_inst_hint), -- ]) -+ reporting.Remediation(hint=hint_text), -+ ]) - - - def report_installed_packages(_context=api): -diff --git a/repos/system_upgrade/el9toel10/actors/mariadbcheck/libraries/mariadbcheck.py b/repos/system_upgrade/el9toel10/actors/mariadbcheck/libraries/mariadbcheck.py -index f5092a73..ac14b13d 100644 ---- a/repos/system_upgrade/el9toel10/actors/mariadbcheck/libraries/mariadbcheck.py -+++ b/repos/system_upgrade/el9toel10/actors/mariadbcheck/libraries/mariadbcheck.py -@@ -1,27 +1,9 @@ - from leapp import reporting -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.common.rpms import has_package - from leapp.libraries.stdlib import api - from leapp.models import DistributionSignedRPM - --# Summary for mariadb-server report --report_server_inst_summary = ( -- 'MariaDB server component will be upgraded. Since RHEL-10 includes' -- ' MariaDB server 10.11 by default, which is incompatible with 10.5' -- ' included in RHEL-9 as default stream, it is necessary to proceed with' -- ' additional steps for the complete upgrade of the MariaDB data.' --) -- --report_server_inst_hint = ( -- 'Back up your data before proceeding with the upgrade' -- ' and follow steps in the documentation section "Migrating to a RHEL 10 version of MariaDB"' -- ' after the upgrade. If the database has already been upgraded,' -- ' meaning the system is already using MariaDB 10.11 then no further' -- ' actions are required.' --) -- --# Link URL for mariadb-server report --report_server_inst_link_url = 'https://access.redhat.com/articles/7097551' -- - - def _report_server_installed(): - """ -@@ -31,16 +13,31 @@ def _report_server_installed(): - installation, warn them about necessary additional steps, and - redirect them to online documentation for the upgrade process. - """ -+ summary = ( -+ 'MariaDB server component will be upgraded.' -+ ' Since {target_distro} 10 includes MariaDB server 10.11 by default,' -+ ' which is incompatible with 10.5 included in {source_distro} 9 as default stream,' -+ ' it is necessary to proceed with additional steps for the complete upgrade of the MariaDB data.' -+ ).format_map(DISTRO_REPORT_NAMES) -+ -+ hint = ( -+ 'Back up your data before proceeding with the upgrade and follow' -+ ' steps in the documentation section "Migrating to a RHEL 10 version of' -+ ' MariaDB" after the upgrade. If the database has already been' -+ ' upgraded, meaning the system is already using MariaDB 10.11 then no' -+ ' further actions are required.' -+ ) -+ - reporting.create_report([ - reporting.Title('MariaDB (mariadb-server) has been detected on your system'), -- reporting.Summary(report_server_inst_summary), -+ reporting.Summary(summary), - reporting.Severity(reporting.Severity.MEDIUM), - reporting.Groups([reporting.Groups.SERVICES]), - reporting.ExternalLink(title='Migrating to a RHEL 10 version of MariaDB', -- url=report_server_inst_link_url), -+ url='https://access.redhat.com/articles/7097551'), - reporting.RelatedResource('package', 'mariadb-server'), -- reporting.Remediation(hint=report_server_inst_hint), -- ]) -+ reporting.Remediation(hint=hint), -+ ]) - - - def report_installed_packages(_context=api): -diff --git a/repos/system_upgrade/el9toel10/actors/motifcheck/libraries/motifcheck.py b/repos/system_upgrade/el9toel10/actors/motifcheck/libraries/motifcheck.py -index ea69057e..ea00406f 100644 ---- a/repos/system_upgrade/el9toel10/actors/motifcheck/libraries/motifcheck.py -+++ b/repos/system_upgrade/el9toel10/actors/motifcheck/libraries/motifcheck.py -@@ -1,22 +1,8 @@ - from leapp import reporting -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.common.rpms import has_package - from leapp.models import DistributionSignedRPM - --# Summary for motif report --report_motif_inst_summary = ( -- 'The Motif package has been detected on your system. Motif is no longer available in RHEL 10.' -- ' Applications that depend on Motif will not work after the upgrade.' -- ' You will need to either migrate to an alternative GUI toolkit (such as GTK or Qt)' -- ' or maintain the Motif package through alternative means.' --) -- --report_motif_inst_hint = ( -- 'Consider migrating applications to a modern GUI toolkit before proceeding with the upgrade.' --) -- --# Link URL for motif report --report_motif_inst_link_url = 'https://red.ht/rhel-10-removed-features-graphics-infrastructures' -- - - def _report_motif_installed(): - """ -@@ -26,16 +12,28 @@ def _report_motif_installed(): - installation, warn them about the lack of motif support in RHEL 10, and - redirect them to online documentation for the migration process. - """ -+ summary = ( -+ 'The Motif package has been detected on your system. Motif is no longer' -+ ' available in {target_distro} 10. Applications that depend on Motif' -+ ' will not work after the upgrade. You will need to either migrate to' -+ ' an alternative GUI toolkit (such as GTK or Qt) or maintain the Motif' -+ ' package through alternative means.' -+ ).format_map(DISTRO_REPORT_NAMES) -+ - reporting.create_report([ - reporting.Title('Motif has been detected on your system'), -- reporting.Summary(report_motif_inst_summary), -+ reporting.Summary(summary), - reporting.Severity(reporting.Severity.HIGH), - reporting.Groups([reporting.Groups.SERVICES]), -- reporting.ExternalLink(title='RHEL 10 Removed Features - Graphics Infrastructures', -- url=report_motif_inst_link_url), -+ reporting.ExternalLink( -+ title='RHEL 10 Removed Features - Graphics Infrastructures', -+ url='https://red.ht/rhel-10-removed-features-graphics-infrastructures' -+ ), - reporting.RelatedResource('package', 'motif'), -- reporting.Remediation(hint=report_motif_inst_hint), -- ]) -+ reporting.Remediation( -+ hint='Consider migrating applications to a modern GUI toolkit before proceeding with the upgrade.' -+ ), -+ ]) - - - def report_installed_packages(): -diff --git a/repos/system_upgrade/el9toel10/actors/mysql/checkmysql/libraries/checkmysql.py b/repos/system_upgrade/el9toel10/actors/mysql/checkmysql/libraries/checkmysql.py -index 7ab07e8c..19a36354 100644 ---- a/repos/system_upgrade/el9toel10/actors/mysql/checkmysql/libraries/checkmysql.py -+++ b/repos/system_upgrade/el9toel10/actors/mysql/checkmysql/libraries/checkmysql.py -@@ -2,6 +2,7 @@ from typing import TYPE_CHECKING - - from leapp import reporting - from leapp.exceptions import StopActorExecutionError -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.stdlib import api - - if TYPE_CHECKING: -@@ -32,14 +33,16 @@ def _generate_mysql_present_report() -> None: - """ - reporting.create_report([ - reporting.Title('Manual migration of data from MySQL database might be needed'), -- reporting.Summary(( -- 'MySQL server component will be upgraded. ' -- 'Since RHEL-10 includes MySQL server 8.4 by default, ' -- 'it might be necessary to proceed with additional steps after ' -- 'RHEL upgrade is completed. In simple setups MySQL server should ' -- 'automatically upgrade all data on first start, but in more ' -- 'complicated setups manual intervention might be needed.' -- )), -+ reporting.Summary( -+ ( -+ 'MySQL server component will be upgraded. ' -+ 'Since {target_distro} 10 includes MySQL server 8.4 by default, ' -+ 'it might be necessary to proceed with additional steps after ' -+ 'the upgrade is completed. In simple setups MySQL server should ' -+ 'automatically upgrade all data on first start, but in more ' -+ 'complicated setups manual intervention might be needed.' -+ ).format_map(DISTRO_REPORT_NAMES) -+ ), - reporting.Severity(reporting.Severity.MEDIUM), - reporting.Groups([reporting.Groups.SERVICES]), - reporting.ExternalLink(title='Migrating MySQL databases from RHEL 9 to RHEL 10', -@@ -51,7 +54,7 @@ def _generate_mysql_present_report() -> None: - '"Migrating MySQL databases from RHEL 9 to RHEL 10" ' - 'with up to date recommended steps before and after the upgrade.' - )), -- ]) -+ ]) - - - def _generate_deprecated_config_report(found_options: list, -diff --git a/repos/system_upgrade/el9toel10/actors/mysql/checkmysql/tests/test_checkmysql.py b/repos/system_upgrade/el9toel10/actors/mysql/checkmysql/tests/test_checkmysql.py -index 84bcf859..b007f30a 100644 ---- a/repos/system_upgrade/el9toel10/actors/mysql/checkmysql/tests/test_checkmysql.py -+++ b/repos/system_upgrade/el9toel10/actors/mysql/checkmysql/tests/test_checkmysql.py -@@ -3,7 +3,7 @@ import pytest - from leapp import reporting - from leapp.exceptions import StopActorExecutionError - from leapp.libraries.actor import checkmysql --from leapp.libraries.common.testutils import create_report_mocked, logger_mocked -+from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked, logger_mocked - from leapp.libraries.stdlib import api - from leapp.models import MySQLConfiguration - -@@ -42,6 +42,7 @@ def test_process_no_deprecated(monkeypatch): - removed_options=[], - removed_arguments=[]) - -+ monkeypatch.setattr(api, "current_actor", CurrentActorMocked()) - monkeypatch.setattr(reporting, "create_report", create_report_mocked()) - monkeypatch.setattr(api, 'consume', consume_mocked) - -@@ -57,6 +58,7 @@ def test_process_deprecated(monkeypatch): - removed_options=['avoid_temporal_upgrade', '--old'], - removed_arguments=['--language']) - -+ monkeypatch.setattr(api, "current_actor", CurrentActorMocked()) - monkeypatch.setattr(reporting, "create_report", create_report_mocked()) - monkeypatch.setattr(api, 'consume', consume_mocked) - -diff --git a/repos/system_upgrade/el9toel10/actors/networkdeprecations/actor.py b/repos/system_upgrade/el9toel10/actors/networkdeprecations/actor.py -index ccafc1ee..337ea915 100644 ---- a/repos/system_upgrade/el9toel10/actors/networkdeprecations/actor.py -+++ b/repos/system_upgrade/el9toel10/actors/networkdeprecations/actor.py -@@ -2,6 +2,7 @@ import os - - from leapp import reporting - from leapp.actors import Actor -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.models import IfCfg, NetworkManagerConfig, Report - from leapp.tags import ChecksPhaseTag, IPUWorkflowTag - -@@ -30,9 +31,12 @@ class CheckNetworkDeprecations9to10(Actor): - @staticmethod - def report_dhclient(): - title = 'Deprecated DHCP plugin configured' -- summary = ('NetworkManager is configured to use the "dhclient" DHCP module.' -- ' In Red Hat Enterprise Linux 10, this setting will be ignored' -- ' along with any dhcp-client specific configuration.') -+ summary = ( -+ 'NetworkManager is configured to use the "dhclient" DHCP module.' -+ ' In {target_distro} 10, this setting will be ignored' -+ ' along with any dhcp-client specific configuration.' -+ ).format_map(DISTRO_REPORT_NAMES) -+ - remediation = ('Remove "dhcp=dhclient" line from "[main]" section from all' - ' configuration files in "/etc/NetworkManager". Review' - ' configuration in "/etc/dhcp", which will be ignored.') -@@ -53,12 +57,15 @@ class CheckNetworkDeprecations9to10(Actor): - reporting.Title('Legacy network configuration with policy routing rules found'), - reporting.Summary( - 'Network configuration files in "ifcfg" format are present accompanied' -- ' by legacy routing rules. In Red Hat Enterprise Linux 10, support' -+ ' by legacy routing rules. In {target_distro} 10, support' - ' for these files is no longer enabled and the configuration will be' - ' ignored. Legacy routing rules are not supported by NetworkManager' - ' natively and therefore can not be migrated automatically.' -- ' The following configuration files were found:{}' -- .format(''.join(_formatted_list_output(conn.values()))) -+ ' The following configuration files were found:{files}' -+ .format( -+ files=''.join(_formatted_list_output(conn.values())), -+ target_distro=DISTRO_REPORT_NAMES.target -+ ) - ), - reporting.Remediation(hint='Replace the routing rules with equivalent' - ' "ipv4.routing-rules" or "ipv6.routing-rules" properties,' -@@ -81,7 +88,6 @@ class CheckNetworkDeprecations9to10(Actor): - reporting.RelatedResource('package', 'NetworkManager'), - reporting.RelatedResource('package', 'NetworkManager-dispatcher-routing-rules'), - ] + [reporting.RelatedResource('file', file) for file in conn.values()]) -- pass - - @staticmethod - def report_ifcfg_leftover(conn): -@@ -110,9 +116,12 @@ class CheckNetworkDeprecations9to10(Actor): - reporting.Title('Legacy network configuration found'), - reporting.Summary( - 'Network configuration files in legacy "ifcfg" format are present.' -- 'In Red Hat Enterprise Linux 10, support for these files is no longer' -+ 'In {target_distro} 10, support for these files is no longer' - ' enabled and the configuration will be ignored. The following files' -- ' were found:{}'.format(''.join(_formatted_list_output(conn.values()))) -+ ' were found:{conns}'.format( -+ conns=''.join(_formatted_list_output(conn.values())), -+ target_distro=DISTRO_REPORT_NAMES.target, -+ ) - ), - reporting.Remediation( - hint='Convert the configuration into NetworkManager native "keyfile" format.', -diff --git a/repos/system_upgrade/el9toel10/actors/networkdeprecations/tests/unit_test_networkdeprecations_9to10.py b/repos/system_upgrade/el9toel10/actors/networkdeprecations/tests/unit_test_networkdeprecations_9to10.py -index e84b99ce..9005790b 100644 ---- a/repos/system_upgrade/el9toel10/actors/networkdeprecations/tests/unit_test_networkdeprecations_9to10.py -+++ b/repos/system_upgrade/el9toel10/actors/networkdeprecations/tests/unit_test_networkdeprecations_9to10.py -@@ -1,9 +1,16 @@ - import pytest - -+from leapp.libraries.common import distro - from leapp.models import IfCfg, NetworkManagerConfig, Report - from leapp.utils.report import is_inhibitor - - -+@pytest.fixture(autouse=True) -+def common_mocks(monkeypatch): -+ monkeypatch.setattr(distro, 'get_source_distro_id', lambda: 'rhel') -+ monkeypatch.setattr(distro, 'get_target_distro_id', lambda: 'rhel') -+ -+ - def test_dhcp_dhclient(current_actor_context): - current_actor_context.feed(NetworkManagerConfig(dhcp='dhclient')) - current_actor_context.run() -diff --git a/repos/system_upgrade/el9toel10/actors/opensslenginescheck/libraries/opensslenginescheck.py b/repos/system_upgrade/el9toel10/actors/opensslenginescheck/libraries/opensslenginescheck.py -index 00edc1da..06819162 100644 ---- a/repos/system_upgrade/el9toel10/actors/opensslenginescheck/libraries/opensslenginescheck.py -+++ b/repos/system_upgrade/el9toel10/actors/opensslenginescheck/libraries/opensslenginescheck.py -@@ -1,4 +1,5 @@ - from leapp import reporting -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.stdlib import api - - FMT_LIST_SEPARATOR = '\n - ' -@@ -110,11 +111,14 @@ def check_openssl_engines(config): - reporting.Summary( - 'OpenSSL engines are deprecated since OpenSSL version 3.0' - ' and they are no longer supported nor available on the target' -- ' RHEL 10 system. Any applications depending on OpenSSL engines' -- ' might not work correctly on the target system and should be configured' -- ' to use OpenSSL providers instead.' -- ' The following OpenSSL engines are configured inside the /etc/pki/tls/openssl.cnf file:{}' -- .format(''.join(_formatted_list_output(enabled_engines))) -+ ' {target} 10 system. Any applications depending on OpenSSL engines' -+ ' might not work correctly on the target system and should be' -+ ' configured to use OpenSSL providers instead.' -+ ' The following OpenSSL engines are configured inside the' -+ ' /etc/pki/tls/openssl.cnf file:{engines}'.format( -+ target=DISTRO_REPORT_NAMES.target, -+ engines=''.join(_formatted_list_output(enabled_engines)), -+ ) - ), - reporting.Remediation(hint=( - 'After the upgrade configure your system and applications' -diff --git a/repos/system_upgrade/el9toel10/actors/opensslenginescheck/tests/component_test_opensslenginescheck.py b/repos/system_upgrade/el9toel10/actors/opensslenginescheck/tests/component_test_opensslenginescheck.py -index e5ed7b25..afb1e5a2 100644 ---- a/repos/system_upgrade/el9toel10/actors/opensslenginescheck/tests/component_test_opensslenginescheck.py -+++ b/repos/system_upgrade/el9toel10/actors/opensslenginescheck/tests/component_test_opensslenginescheck.py -@@ -1,3 +1,4 @@ -+from leapp.libraries.common import distro - from leapp.models import OpenSslConfig, OpenSslConfigBlock, OpenSslConfigPair, Report - - -@@ -93,7 +94,9 @@ def test_actor_execution_default_modified(current_actor_context): - assert not current_actor_context.consume(Report) - - --def test_actor_execution_other_engine_modified(current_actor_context): -+def test_actor_execution_other_engine_modified(current_actor_context, monkeypatch): -+ monkeypatch.setattr(distro, 'get_source_distro_id', lambda: 'rhel') -+ monkeypatch.setattr(distro, 'get_target_distro_id', lambda: 'rhel') - # default, but removing contents unrelated for the checks - current_actor_context.feed( - OpenSslConfig( -diff --git a/repos/system_upgrade/el9toel10/actors/pamuserdb/checkpamuserdb/libraries/checkpamuserdb.py b/repos/system_upgrade/el9toel10/actors/pamuserdb/checkpamuserdb/libraries/checkpamuserdb.py -index 05cc71a9..58b47f58 100644 ---- a/repos/system_upgrade/el9toel10/actors/pamuserdb/checkpamuserdb/libraries/checkpamuserdb.py -+++ b/repos/system_upgrade/el9toel10/actors/pamuserdb/checkpamuserdb/libraries/checkpamuserdb.py -@@ -1,5 +1,6 @@ - from leapp import reporting - from leapp.exceptions import StopActorExecutionError -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.stdlib import api - from leapp.models import PamUserDbLocation - -@@ -15,10 +16,15 @@ def process(): - reporting.create_report([ - reporting.Title('pam_userdb databases will be converted to GDBM'), - reporting.Summary( -- 'On RHEL 10, GDMB is used by pam_userdb as it\'s backend database,' -+ 'On {target_distro} 10, GDMB is used by pam_userdb as it\'s backend database,' - ' replacing BerkeleyDB. Existing pam_userdb databases will be' - ' converted to GDBM. The following databases will be converted:' -- '{sep}{locations}'.format(sep=FMT_LIST_SEPARATOR, locations=FMT_LIST_SEPARATOR.join(msg.locations))), -+ '{sep}{locations}'.format( -+ sep=FMT_LIST_SEPARATOR, -+ locations=FMT_LIST_SEPARATOR.join(msg.locations), -+ target_distro=DISTRO_REPORT_NAMES.target, -+ ) -+ ), - reporting.Severity(reporting.Severity.INFO), - reporting.Groups([reporting.Groups.SECURITY, reporting.Groups.AUTHENTICATION]) - ]) -diff --git a/repos/system_upgrade/el9toel10/actors/pamuserdb/checkpamuserdb/tests/test_checkpamuserdb.py b/repos/system_upgrade/el9toel10/actors/pamuserdb/checkpamuserdb/tests/test_checkpamuserdb.py -index 2e11106b..1f81fb26 100644 ---- a/repos/system_upgrade/el9toel10/actors/pamuserdb/checkpamuserdb/tests/test_checkpamuserdb.py -+++ b/repos/system_upgrade/el9toel10/actors/pamuserdb/checkpamuserdb/tests/test_checkpamuserdb.py -@@ -3,6 +3,7 @@ import pytest - from leapp import reporting - from leapp.exceptions import StopActorExecutionError - from leapp.libraries.actor import checkpamuserdb -+from leapp.libraries.common import distro - from leapp.libraries.common.testutils import create_report_mocked, logger_mocked - from leapp.libraries.stdlib import api - from leapp.models import PamUserDbLocation -@@ -38,6 +39,8 @@ def test_process_locations(monkeypatch): - - monkeypatch.setattr(reporting, "create_report", create_report_mocked()) - monkeypatch.setattr(api, 'consume', consume_mocked) -+ monkeypatch.setattr(distro, 'get_source_distro_id', lambda: 'rhel') -+ monkeypatch.setattr(distro, 'get_target_distro_id', lambda: 'rhel') - - checkpamuserdb.process() - assert reporting.create_report.called == 1 -diff --git a/repos/system_upgrade/el9toel10/actors/postgresqlcheck/libraries/postgresqlcheck.py b/repos/system_upgrade/el9toel10/actors/postgresqlcheck/libraries/postgresqlcheck.py -index 7186b440..25689433 100644 ---- a/repos/system_upgrade/el9toel10/actors/postgresqlcheck/libraries/postgresqlcheck.py -+++ b/repos/system_upgrade/el9toel10/actors/postgresqlcheck/libraries/postgresqlcheck.py -@@ -1,27 +1,9 @@ - from leapp import reporting -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.common.rpms import has_package - from leapp.libraries.stdlib import api - from leapp.models import DistributionSignedRPM - --# Summary for postgresql-server report --report_server_inst_summary = ( -- 'PostgreSQL server component will be upgraded. Since RHEL-10 includes' -- ' PostgreSQL server 16 by default, which is incompatible with 13 and 15' -- ' included in RHEL-9, in those cases, it is necessary to proceed with additional steps' -- ' for the complete upgrade of the PostgreSQL data.' -- 'If the database has already been upgraded, meaning the system is already using PostgreSQL 16,' -- ' then no further actions are required.' --) -- --report_server_inst_hint = ( -- 'Back up your data before proceeding with the upgrade' -- ' and follow steps in the documentation section "Migrating to a RHEL 10 version of PostgreSQL"' -- ' after the upgrade.' --) -- --# Link URL for postgresql-server report --report_server_inst_link_url = 'https://access.redhat.com/articles/7097228' -- - - def _report_server_installed(): - """ -@@ -31,16 +13,34 @@ def _report_server_installed(): - installation, warn them about necessary additional steps, and - redirect them to online documentation for the upgrade process. - """ -+ -+ summary = ( -+ 'PostgreSQL server component will be upgraded. Since {target_distro} 10 includes' -+ ' PostgreSQL server 16 by default, which is incompatible with 13 and 15' -+ ' included in {source_distro} 9, in those cases, it is necessary to' -+ ' proceed with additional steps for the complete upgrade of the PostgreSQL data.' -+ ' If the database has already been upgraded, meaning the system is' -+ ' already using PostgreSQL 16, then no further actions are required.' -+ ).format_map(DISTRO_REPORT_NAMES) -+ -+ hint_text = ( -+ 'Back up your data before proceeding with the upgrade' -+ ' and follow steps in the documentation section "Migrating to a RHEL 10 version of PostgreSQL"' -+ ' after the upgrade.' -+ ) -+ - reporting.create_report([ -- reporting.Title('PostgreSQL (postgresql-server) has been detected on your system'), -- reporting.Summary(report_server_inst_summary), -- reporting.Severity(reporting.Severity.MEDIUM), -- reporting.Groups([reporting.Groups.SERVICES]), -- reporting.ExternalLink(title='Migrating to a RHEL 10 version of PostgreSQL', -- url=report_server_inst_link_url), -- reporting.RelatedResource('package', 'postgresql-server'), -- reporting.Remediation(hint=report_server_inst_hint), -- ]) -+ reporting.Title('PostgreSQL (postgresql-server) has been detected on your system'), -+ reporting.Summary(summary), -+ reporting.Severity(reporting.Severity.MEDIUM), -+ reporting.Groups([reporting.Groups.SERVICES]), -+ reporting.ExternalLink( -+ title='Migrating to a RHEL 10 version of PostgreSQL', -+ url='https://access.redhat.com/articles/7097228', -+ ), -+ reporting.RelatedResource('package', 'postgresql-server'), -+ reporting.Remediation(hint=hint_text), -+ ]) - - - def report_installed_packages(_context=api): -diff --git a/repos/system_upgrade/el9toel10/actors/xorgcheck/libraries/xorgcheck.py b/repos/system_upgrade/el9toel10/actors/xorgcheck/libraries/xorgcheck.py -index 13092957..e70b4d5c 100644 ---- a/repos/system_upgrade/el9toel10/actors/xorgcheck/libraries/xorgcheck.py -+++ b/repos/system_upgrade/el9toel10/actors/xorgcheck/libraries/xorgcheck.py -@@ -1,4 +1,5 @@ - from leapp import reporting -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.common.rpms import has_package - from leapp.models import DistributionSignedRPM - -@@ -16,21 +17,6 @@ _XORG_PACKAGES = [ - # Separator for list formatting in reports - FMT_LIST_SEPARATOR = '\n - ' - --# Summary template for Xorg report --_report_xorg_inst_summary_template = ( -- 'Xorg server packages have been detected on your system. The Xorg server is no longer available ' -- 'in RHEL 10. Applications and services that depend on Xorg server packages will not work ' -- 'after the upgrade. Migrate to Wayland or maintain the Xorg packages through ' -- 'alternative means. The following Xorg server packages have been detected and are not available in RHEL 10:{}{}' --) -- --_report_xorg_inst_hint = ( -- 'Consider migrating to Wayland before proceeding with the upgrade.' --) -- --# Link URL for Xorg report --_report_xorg_inst_link_url = 'https://red.ht/rhel-10-removed-features-graphics-infrastructures' -- - - def _report_xorg_installed(packages): - """ -@@ -43,15 +29,25 @@ def _report_xorg_installed(packages): - :param packages: List of installed Xorg package names - :type packages: list - """ -- summary = _report_xorg_inst_summary_template.format(FMT_LIST_SEPARATOR, FMT_LIST_SEPARATOR.join(packages)) -+ summary = ( -+ "Xorg server packages have been detected on your system. The Xorg server is no longer available " -+ "in {distro} 10. Applications and services that depend on Xorg server packages will " -+ "not work after the upgrade. Migrate to Wayland or maintain the Xorg packages through alternative means. " -+ "The following Xorg server packages have been detected and are not available in {distro} 10:{sep}{list}" -+ ).format( -+ distro=DISTRO_REPORT_NAMES.target, -+ sep=FMT_LIST_SEPARATOR, -+ list=FMT_LIST_SEPARATOR.join(packages), -+ ) -+ - reporting.create_report([ - reporting.Title('Xorg server packages have been detected on your system'), - reporting.Summary(summary), - reporting.Severity(reporting.Severity.HIGH), - reporting.Groups([reporting.Groups.SERVICES]), - reporting.ExternalLink(title='RHEL 10 Removed Features - Graphics Infrastructures', -- url=_report_xorg_inst_link_url), -- reporting.Remediation(hint=_report_xorg_inst_hint), -+ url='https://red.ht/rhel-10-removed-features-graphics-infrastructures'), -+ reporting.Remediation(hint='Consider migrating to Wayland before proceeding with the upgrade.'), - ] + [reporting.RelatedResource('package', pkg) for pkg in packages]) - - --- -2.53.0 - diff --git a/0011-checkblacklistca-Respect-distro-name-in-reports.patch b/0011-checkblacklistca-Respect-distro-name-in-reports.patch deleted file mode 100644 index 4067785..0000000 --- a/0011-checkblacklistca-Respect-distro-name-in-reports.patch +++ /dev/null @@ -1,86 +0,0 @@ -From f6838df00e3be7d8beec99bd9448ed47c7720853 Mon Sep 17 00:00:00 2001 -From: Matej Matuska -Date: Mon, 2 Mar 2026 18:25:09 +0100 -Subject: [PATCH 11/44] checkblacklistca: Respect distro name in reports - -Jira: RHEL-130950 ---- - .../libraries/checkblacklistca.py | 28 +++++++++++++------ - .../tests/component_test_checkblacklistca.py | 9 ++++++ - 2 files changed, 29 insertions(+), 8 deletions(-) - -diff --git a/repos/system_upgrade/el8toel9/actors/checkblacklistca/libraries/checkblacklistca.py b/repos/system_upgrade/el8toel9/actors/checkblacklistca/libraries/checkblacklistca.py -index 53b912b8..635b3640 100644 ---- a/repos/system_upgrade/el8toel9/actors/checkblacklistca/libraries/checkblacklistca.py -+++ b/repos/system_upgrade/el8toel9/actors/checkblacklistca/libraries/checkblacklistca.py -@@ -1,4 +1,5 @@ - from leapp import reporting -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.stdlib import api - from leapp.models import BlackListCA, BlackListError - -@@ -50,9 +51,14 @@ def process(): - reporting.Title('Distrusted CA certificates will be moved from blacklist to blocklist'), - reporting.Summary( - 'The directories which store user and administrator supplied ' -- 'distrusted certificates have change names from blacklist in ' -- 'RHEL8 to blocklist in RHEL9. As a result {} and ' -- '{} will be deleted.'.format(reportString, deleteString)), -+ 'distrusted certificates were renamed from blacklist in ' -+ '{source_distro} 8 to blocklist in {target_distro} 9. ' -+ 'As a result {report_string} and {delete_string} will be deleted.'.format( -+ report_string=reportString, -+ delete_string=deleteString, -+ **DISTRO_REPORT_NAMES, -+ ) -+ ), - reporting.Severity(reporting.Severity.INFO), - reporting.Groups([reporting.Groups.SECURITY]), - reporting.Groups([reporting.Groups.AUTHENTICATION]) -@@ -63,11 +69,17 @@ def process(): - reporting.Summary( - 'The directories which stores user and administrator supplied ' - 'distrusted certificates has change names from blacklist in ' -- 'RHEL8 to blocklist in RHEL9. But we are unable to access the ' -- 'RHEL8 directory {} because {}. You can clear this error by ' -- 'correcting the condition, or by moving the contents to {} ' -- 'and removing {} completely' -- '. '.format(ble.sourceDir, ble.error, ble.targetDir, ble.sourceDir)), -+ '{source_distro} 8 to blocklist in {target_distro} 9. ' -+ 'But we are unable to access the {source_distro} 8 directory ' -+ '{source_dir} because {error}. You can clear this error by ' -+ 'correcting the condition, or by moving the contents to ' -+ '{target_dir} and removing {source_dir} completely'.format( -+ source_dir=ble.sourceDir, -+ error=ble.error, -+ target_dir=ble.targetDir, -+ **DISTRO_REPORT_NAMES, -+ ) -+ ), - reporting.Severity(reporting.Severity.HIGH), - reporting.Groups([reporting.Groups.SECURITY]), - reporting.Groups([reporting.Groups.INHIBITOR]), -diff --git a/repos/system_upgrade/el8toel9/actors/checkblacklistca/tests/component_test_checkblacklistca.py b/repos/system_upgrade/el8toel9/actors/checkblacklistca/tests/component_test_checkblacklistca.py -index 2fc27501..af7e6305 100644 ---- a/repos/system_upgrade/el8toel9/actors/checkblacklistca/tests/component_test_checkblacklistca.py -+++ b/repos/system_upgrade/el8toel9/actors/checkblacklistca/tests/component_test_checkblacklistca.py -@@ -1,7 +1,16 @@ -+import pytest -+ -+from leapp.libraries.common import distro - from leapp.models import BlackListCA, BlackListError, Report - from leapp.utils.report import is_inhibitor - - -+@pytest.fixture(autouse=True) -+def common_mocks(monkeypatch): -+ monkeypatch.setattr(distro, 'get_source_distro_id', lambda: 'rhel') -+ monkeypatch.setattr(distro, 'get_target_distro_id', lambda: 'rhel') -+ -+ - def test_actor_execution_empty(current_actor_context): - current_actor_context.feed() - current_actor_context.run() --- -2.53.0 - diff --git a/0012-blsgrubcfgonppc64-Respect-distro-name-in-reports.patch b/0012-blsgrubcfgonppc64-Respect-distro-name-in-reports.patch deleted file mode 100644 index 8ddba38..0000000 --- a/0012-blsgrubcfgonppc64-Respect-distro-name-in-reports.patch +++ /dev/null @@ -1,49 +0,0 @@ -From a2a3dfb9b14f9582b4c895bda28a3ecb604ea737 Mon Sep 17 00:00:00 2001 -From: Matej Matuska -Date: Mon, 2 Mar 2026 18:34:34 +0100 -Subject: [PATCH 12/44] blsgrubcfgonppc64: Respect distro name in reports - -Jira: RHEL-130950 ---- - .../libraries/blsgrubcfgonppc64.py | 11 +++++++---- - 1 file changed, 7 insertions(+), 4 deletions(-) - -diff --git a/repos/system_upgrade/el8toel9/actors/checkblsgrubcfgonppc64/libraries/blsgrubcfgonppc64.py b/repos/system_upgrade/el8toel9/actors/checkblsgrubcfgonppc64/libraries/blsgrubcfgonppc64.py -index d723df65..58d15e25 100644 ---- a/repos/system_upgrade/el8toel9/actors/checkblsgrubcfgonppc64/libraries/blsgrubcfgonppc64.py -+++ b/repos/system_upgrade/el8toel9/actors/checkblsgrubcfgonppc64/libraries/blsgrubcfgonppc64.py -@@ -1,6 +1,7 @@ - from leapp import reporting - from leapp.libraries.common import grub - from leapp.libraries.common.config import architecture -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.stdlib import api - from leapp.models import DefaultGrubInfo, FirmwareFacts, GrubCfgBios - -@@ -31,8 +32,9 @@ def process(): - 'Leapp cannot continue with upgrade on "ppc64le" bare metal systems' - ), - reporting.Summary( -- 'In-place upgrade to RHEL 9 is not supported on POWER8 and POWER9 bare metal systems. ' -- 'For more information, refer to the following article: {}'.format(URL) -+ f'In-place upgrade to {DISTRO_REPORT_NAMES.target} 9 is not' -+ ' supported on POWER8 and POWER9 bare metal systems. For more' -+ ' information, refer to the attached article.' - ), - reporting.Severity(reporting.Severity.HIGH), - reporting.Groups(['inhibitor']), -@@ -54,8 +56,9 @@ def process(): - 'On "ppc64le" systems with BLS enabled, the GRUB configuration is not ' - 'properly converted after the upgrade and Leapp has to run "grub2-mkconfig" ' - '-o /boot/grub2/grub.cfg command in order to fix an issue with booting into ' -- 'the RHEL 8 kernel instead of RHEL 9.' -- -+ 'the {source_distro} 8 kernel instead of {target_distro} 9.'.format_map( -+ DISTRO_REPORT_NAMES -+ ) - ), - reporting.Severity(reporting.Severity.HIGH), - reporting.Groups([reporting.Groups.BOOT]), --- -2.53.0 - diff --git a/0013-checkselinux-Respect-distro-name-in-report.patch b/0013-checkselinux-Respect-distro-name-in-report.patch deleted file mode 100644 index b0bc46c..0000000 --- a/0013-checkselinux-Respect-distro-name-in-report.patch +++ /dev/null @@ -1,38 +0,0 @@ -From b43ed18a6e4cea273def17c83c0c8d1742ba5145 Mon Sep 17 00:00:00 2001 -From: Peter Mocary -Date: Mon, 2 Mar 2026 16:16:17 +0100 -Subject: [PATCH 13/44] checkselinux: Respect distro name in report - -Jira: RHEL-130950 ---- - .../common/actors/checkselinux/libraries/checkselinux.py | 7 ++++--- - 1 file changed, 4 insertions(+), 3 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/checkselinux/libraries/checkselinux.py b/repos/system_upgrade/common/actors/checkselinux/libraries/checkselinux.py -index 2ef914ac..dbd79adf 100644 ---- a/repos/system_upgrade/common/actors/checkselinux/libraries/checkselinux.py -+++ b/repos/system_upgrade/common/actors/checkselinux/libraries/checkselinux.py -@@ -1,5 +1,6 @@ - from leapp import reporting - from leapp.libraries.common.config.version import get_target_major_version -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.stdlib import api - from leapp.models import KernelCmdlineArg, SELinuxFacts, SelinuxPermissiveDecision, SelinuxRelabelDecision - -@@ -20,10 +21,10 @@ def process(): - reporting.create_report([ - reporting.Title('LEAPP detected SELinux disabled in "/etc/selinux/config"'), - reporting.Summary( -- 'On RHEL 9, disabling SELinux in "/etc/selinux/config" is no longer possible. ' -- 'This way, the system starts with SELinux enabled but with no policy loaded. LEAPP ' -+ 'On {target_distro} 9, disabling SELinux in "/etc/selinux/config" is no longer possible. ' -+ 'This way, the system starts with SELinux enabled but with no policy loaded. Leapp ' - 'will automatically disable SELinux using "SELINUX=0" kernel command line parameter. ' -- 'However, Red Hat strongly recommends to have SELinux enabled' -+ 'However, it is strongly recommended to have SELinux enabled'.format_map(DISTRO_REPORT_NAMES) - ), - reporting.Severity(reporting.Severity.INFO), - reporting.Groups([reporting.Groups.SELINUX]), --- -2.53.0 - diff --git a/0014-checkosrelease-Respect-distro-name-in-report.patch b/0014-checkosrelease-Respect-distro-name-in-report.patch deleted file mode 100644 index dbced68..0000000 --- a/0014-checkosrelease-Respect-distro-name-in-report.patch +++ /dev/null @@ -1,92 +0,0 @@ -From 60e54a82da37bb0d2f1bee1a35d1d7fa01cf3df7 Mon Sep 17 00:00:00 2001 -From: Peter Mocary -Date: Mon, 2 Mar 2026 16:44:04 +0100 -Subject: [PATCH 14/44] checkosrelease: Respect distro name in report - -Jira: RHEL-130950 ---- - .../common/actors/checkosrelease/actor.py | 2 +- - .../actors/checkosrelease/libraries/checkosrelease.py | 10 +++++++--- - .../actors/checkosrelease/tests/test_checkosrelease.py | 4 +++- - 3 files changed, 11 insertions(+), 5 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/checkosrelease/actor.py b/repos/system_upgrade/common/actors/checkosrelease/actor.py -index 7747eb9b..8c60d968 100644 ---- a/repos/system_upgrade/common/actors/checkosrelease/actor.py -+++ b/repos/system_upgrade/common/actors/checkosrelease/actor.py -@@ -6,7 +6,7 @@ from leapp.tags import ChecksPhaseTag, IPUWorkflowTag - - class CheckOSRelease(Actor): - """ -- Check if the current RHEL minor version is supported. If not, inhibit the upgrade process. -+ Check if the current distro version is supported. If not, inhibit the upgrade process. - - This check can be skipped by using the LEAPP_DEVEL_SKIP_CHECK_OS_RELEASE environment variable. - """ -diff --git a/repos/system_upgrade/common/actors/checkosrelease/libraries/checkosrelease.py b/repos/system_upgrade/common/actors/checkosrelease/libraries/checkosrelease.py -index 1ee6e6ab..1ab89a8d 100644 ---- a/repos/system_upgrade/common/actors/checkosrelease/libraries/checkosrelease.py -+++ b/repos/system_upgrade/common/actors/checkosrelease/libraries/checkosrelease.py -@@ -2,6 +2,7 @@ import os - - from leapp import reporting - from leapp.libraries.common.config import version -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - - COMMON_REPORT_TAGS = [reporting.Groups.SANITY] - FMT_LIST_SEPARATOR = '\n - ' -@@ -14,7 +15,9 @@ def skip_check(): - if os.getenv('LEAPP_DEVEL_SKIP_CHECK_OS_RELEASE'): - reporting.create_report([ - reporting.Title('Skipped OS release check'), -- reporting.Summary('Source RHEL release check skipped via LEAPP_DEVEL_SKIP_CHECK_OS_RELEASE env var.'), -+ reporting.Summary( -+ 'Source system release check skipped via LEAPP_DEVEL_SKIP_CHECK_OS_RELEASE env variable.' -+ ), - reporting.Severity(reporting.Severity.HIGH), - reporting.Groups(COMMON_REPORT_TAGS) - ] + related) -@@ -24,7 +27,7 @@ def skip_check(): - - - def check_os_version(): -- """ Check the RHEL minor version and inhibit the upgrade if it does not match the supported ones """ -+ """ Check the distro version and inhibit the upgrade if it does not match the supported ones """ - if not version.is_supported_version(): - supported_releases = [] - for rel in version.SUPPORTED_VERSIONS: -@@ -33,7 +36,8 @@ def check_os_version(): - current_release = ' '.join(version.current_version()).upper() - reporting.create_report([ - reporting.Title( -- 'The installed OS version is not supported for the in-place upgrade to the target RHEL version' -+ 'The installed OS version is not supported for the in-place upgrade' -+ ' to the target {target_distro} version'.format_map(DISTRO_REPORT_NAMES) - ), - reporting.Summary( - 'The supported OS releases for the upgrade process:' -diff --git a/repos/system_upgrade/common/actors/checkosrelease/tests/test_checkosrelease.py b/repos/system_upgrade/common/actors/checkosrelease/tests/test_checkosrelease.py -index 1ca8a1d7..c1c43065 100644 ---- a/repos/system_upgrade/common/actors/checkosrelease/tests/test_checkosrelease.py -+++ b/repos/system_upgrade/common/actors/checkosrelease/tests/test_checkosrelease.py -@@ -3,7 +3,8 @@ import os - from leapp import reporting - from leapp.libraries.actor import checkosrelease - from leapp.libraries.common.config import version --from leapp.libraries.common.testutils import create_report_mocked, produce_mocked -+from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked, produce_mocked -+from leapp.libraries.stdlib import api - from leapp.utils.report import is_inhibitor - - -@@ -26,6 +27,7 @@ def test_no_skip_check(monkeypatch): - - - def test_not_supported_release(monkeypatch): -+ monkeypatch.setattr(api, "current_actor", CurrentActorMocked()) - monkeypatch.setattr(version, "is_supported_version", lambda: False) - monkeypatch.setattr(version, "get_source_major_version", lambda: '8') - monkeypatch.setattr(version, "current_version", lambda: ('bad', '8')) --- -2.53.0 - diff --git a/0015-rocecheck-Remove-outadated-check-for-source-version.patch b/0015-rocecheck-Remove-outadated-check-for-source-version.patch deleted file mode 100644 index 38a9ee0..0000000 --- a/0015-rocecheck-Remove-outadated-check-for-source-version.patch +++ /dev/null @@ -1,113 +0,0 @@ -From ce379f96f128b60d93e02f95351e2f718940d2f2 Mon Sep 17 00:00:00 2001 -From: Matej Matuska -Date: Mon, 23 Feb 2026 12:45:59 +0100 -Subject: [PATCH 15/44] rocecheck: Remove outadated check for source version - -Jira: RHEL-130950 ---- - .../actors/rocecheck/libraries/rocecheck.py | 35 ++----------------- - .../rocecheck/tests/unit_test_rocecheck.py | 22 ------------ - 2 files changed, 3 insertions(+), 54 deletions(-) - -diff --git a/repos/system_upgrade/el8toel9/actors/rocecheck/libraries/rocecheck.py b/repos/system_upgrade/el8toel9/actors/rocecheck/libraries/rocecheck.py -index 7549feb8..0ed2046a 100644 ---- a/repos/system_upgrade/el8toel9/actors/rocecheck/libraries/rocecheck.py -+++ b/repos/system_upgrade/el8toel9/actors/rocecheck/libraries/rocecheck.py -@@ -1,6 +1,6 @@ - from leapp import reporting - from leapp.exceptions import StopActorExecutionError --from leapp.libraries.common.config import architecture, version -+from leapp.libraries.common.config import architecture - from leapp.libraries.stdlib import api - from leapp.models import KernelCmdline, RoceDetected - -@@ -37,36 +37,8 @@ def _fmt_list(items): - return ''.join([FMT_LIST_SEPARATOR.format(i) for i in items]) - - --def _report_old_version(roce): -+def _report_wrong_setup(roce): - roce_nics = roce.roce_nics_connected + roce.roce_nics_connecting -- reporting.create_report([ -- reporting.Title('A newer version of RHEL 8 is required for the upgrade with RoCE.'), -- reporting.Summary( -- 'The RHEL 9 system uses different network schemes for NIC names' -- ' than RHEL 8.' -- ' RHEL {version} does not provide functionality to be able' -- ' to set the system configuration in a way the network interface' -- ' names used by RoCE are persistent on both (RHEL 8 and RHEL 9)' -- ' systems.' -- ' The in-place upgrade from the current version of RHEL to RHEL 9' -- ' will break the RoCE network configuration.' -- '\n\nRoCE detected on following NICs:{nics}' -- .format( -- version=version.get_source_version(), -- nics=_fmt_list(roce_nics) -- ) -- ), -- reporting.Remediation(hint=( -- 'Update the system to RHEL 8.8 or newer using DNF and then reboot' -- ' the system prior the in-place upgrade to RHEL 9.' -- )), -- reporting.Severity(reporting.Severity.HIGH), -- reporting.Groups([ -- reporting.Groups.INHIBITOR, -- reporting.Groups.ACCESSIBILITY, -- reporting.Groups.SANITY, -- ]), -- ]) - - - def _report_wrong_setup(roce): -@@ -128,7 +100,6 @@ def process(): - # No used RoCE detected - nothing to do - api.current_logger().debug('Skipping RoCE checks: No RoCE card detected.') - return -- if version.matches_source_version('<= 8.6'): -- _report_old_version(roce) -+ - if not is_kernel_arg_set(): - _report_wrong_setup(roce) -diff --git a/repos/system_upgrade/el8toel9/actors/rocecheck/tests/unit_test_rocecheck.py b/repos/system_upgrade/el8toel9/actors/rocecheck/tests/unit_test_rocecheck.py -index b5511d17..70e4a7b3 100644 ---- a/repos/system_upgrade/el8toel9/actors/rocecheck/tests/unit_test_rocecheck.py -+++ b/repos/system_upgrade/el8toel9/actors/rocecheck/tests/unit_test_rocecheck.py -@@ -52,7 +52,6 @@ def test_roce_noibmz(monkeypatch, arch): - - monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(arch=arch)) - monkeypatch.setattr(reporting, "create_report", create_report_mocked()) -- monkeypatch.setattr(rocecheck, '_report_old_version', mocked_do_not_call_me) - monkeypatch.setattr(rocecheck, '_report_wrong_setup', mocked_do_not_call_me) - monkeypatch.setattr(rocecheck, 'is_kernel_arg_set', mocked_do_not_call_me) - monkeypatch.setattr(rocecheck.api, 'consume', mocked_do_not_call_me) -@@ -76,27 +75,6 @@ def test_roce_ok(monkeypatch, msgs, version): - assert not reporting.create_report.called - - --@pytest.mark.parametrize('msgs', ( -- [_kernel_cmdline(['net.naming-scheme=rhel-8.7']), _roce(['eno'], [])], -- [_kernel_cmdline(['net.naming-scheme=rhel-8.7']), _roce([], ['eno'])], -- [_kernel_cmdline(['net.naming-scheme=rhel-8.6']), _roce(['eno'], [])], -- [_kernel_cmdline(['net.naming-scheme=rhel-8.6']), _roce(['eno', 'eno1'], ['enp'])], -- [_kernel_cmdline(['foo=bar']), _roce(['eno'], [])], -- [_kernel_cmdline(), _roce(['eno'], [])], --)) --@pytest.mark.parametrize('version', ['8.0', '8.3', '8.6']) --def test_roce_old_rhel(monkeypatch, msgs, version): -- curr_actor_mocked = CurrentActorMocked(arch=architecture.ARCH_S390X, src_ver=version, msgs=msgs) -- monkeypatch.setattr(api, 'current_actor', curr_actor_mocked) -- monkeypatch.setattr(reporting, "create_report", create_report_mocked()) -- rocecheck.process() -- assert reporting.create_report.called -- assert any( -- 'version of RHEL' in report['title'] -- for report in reporting.create_report.reports -- ) -- -- - # NOTE: what about the situation when net.naming-scheme is configured multiple times??? - @pytest.mark.parametrize('msgs', ( - [_kernel_cmdline(['net.naming-scheme=rhel-8.6']), _roce(['eno'], [])], --- -2.53.0 - diff --git a/0016-rocecheck-Respect-distro-name-in-report.patch b/0016-rocecheck-Respect-distro-name-in-report.patch deleted file mode 100644 index 4efe4f2..0000000 --- a/0016-rocecheck-Respect-distro-name-in-report.patch +++ /dev/null @@ -1,114 +0,0 @@ -From 18fa991962cb1198173f0ad08a34de27467c32fe Mon Sep 17 00:00:00 2001 -From: Matej Matuska -Date: Mon, 23 Feb 2026 13:59:35 +0100 -Subject: [PATCH 16/44] rocecheck: Respect distro name in report - -Jira: RHEL-130950 ---- - .../actors/rocecheck/libraries/rocecheck.py | 81 ++++++++++--------- - 1 file changed, 44 insertions(+), 37 deletions(-) - -diff --git a/repos/system_upgrade/el8toel9/actors/rocecheck/libraries/rocecheck.py b/repos/system_upgrade/el8toel9/actors/rocecheck/libraries/rocecheck.py -index 0ed2046a..5014a8db 100644 ---- a/repos/system_upgrade/el8toel9/actors/rocecheck/libraries/rocecheck.py -+++ b/repos/system_upgrade/el8toel9/actors/rocecheck/libraries/rocecheck.py -@@ -1,6 +1,7 @@ - from leapp import reporting - from leapp.exceptions import StopActorExecutionError --from leapp.libraries.common.config import architecture -+from leapp.libraries.common.config import architecture, get_target_distro_id -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.stdlib import api - from leapp.models import KernelCmdline, RoceDetected - -@@ -40,45 +41,51 @@ def _fmt_list(items): - def _report_wrong_setup(roce): - roce_nics = roce.roce_nics_connected + roce.roce_nics_connecting - -+ summary = ( -+ 'The {target_distro} 9 system uses different network schemes for NIC names' -+ ' than {source_distro} 8.' -+ ' The below listed RoCE NICs need to be reconfigured to the new' -+ ' interface naming scheme in order to prevent loss of network' -+ ' access to your system via these interfaces after the upgrade.' -+ ' For more information, see: {url}' -+ '\n\nRoCE detected on the following NICs:{nics}' -+ ).format( -+ nics=_fmt_list(roce_nics), -+ url=DOC_URL, -+ **DISTRO_REPORT_NAMES, -+ ) -+ remmediation_hint = ( -+ 'Prerequisite for upgrading to {target_distro} {target_version}:' -+ 'In {source_distro} 8, all RoCE cards must be configured with the interface' -+ ' names they should have in {target_distro} {target_version}.\n' -+ 'For more information, see chapter 1.4 of the RHEL 8 Product' -+ ' Documentation (see the attached link) and follow these steps:\n' -+ '1.) determine the current interface device names of the RoCE' -+ ' cards that are in "connected to" or in "connecting" state\n' -+ '2.) determine if UID uniqueness is set for these cards\n' -+ '3.) compute new interface device names from the UID or the' -+ ' function ID, respectively\n' -+ '4.) change the network interface device names in ifcfg' -+ ' files\n' -+ '5.) set the kernel parameter net.naming-scheme=rhel-8.7 in the' -+ ' effective .conf file in /boot/loader/entries\n' -+ '6.) adjust other settings that rely on the interface device names' -+ ' (e.g. firewall) by changing the interface device names' -+ ' accordingly\n' -+ '7.) run `zipl -V` and reboot the system\n' -+ '8.) check your network connectivity\n' -+ '\n' -+ 'Caution: Creating an incorrect configuration might cause the loss' -+ ' of your network connection after reboot!' -+ ).format( -+ target_version="9" if get_target_distro_id() == "centos" else "9.x", -+ **DISTRO_REPORT_NAMES, -+ ) - --def _report_wrong_setup(roce): -- roce_nics = roce.roce_nics_connected + roce.roce_nics_connecting - reporting.create_report([ - reporting.Title('Invalid RoCE configuration for the in-place upgrade'), -- reporting.Summary( -- 'The RHEL 9 system uses different network schemes for NIC names' -- ' than RHEL 8.' -- ' The below listed RoCE NICs need to be reconfigured to the new' -- ' interface naming scheme in order to prevent loss of network' -- ' access to your system via these interfaces after the upgrade.' -- ' For more information, see: {url}' -- '\n\nRoCE detected on the following NICs:{nics}' -- .format(nics=_fmt_list(roce_nics), url=DOC_URL) -- ), -- reporting.Remediation(hint=( -- 'Prerequisite for upgrading to RHEL9.x:' -- 'In RHEL 8, all RoCE cards must be configured with the interface' -- ' names they should have in RHEL9.x.\n' -- 'For more information, see chapter 1.4 of the RHEL8 Product' -- ' Documentation (see the attached link) and follow these steps:\n' -- '1.) determine the current interface device names of the RoCE' -- ' cards that are in "connected to" or in "connecting" state\n' -- '2.) determine if UID uniqueness is set for these cards\n' -- '3.) compute new interface device names from the UID or the' -- ' function ID, respectively\n' -- '4.) change the network interface device names in ifcfg' -- ' files\n' -- '5.) set the kernel parameter net.naming-scheme=rhel-8.7 in the' -- ' effective .conf file in /boot/loader/entries\n' -- '6.) adjust other settings that rely on the interface device names' -- ' (e.g. firewall) by changing the interface device names' -- ' accordingly\n' -- '7.) run `zipl -V` and reboot the system\n' -- '8.) check your network connectivity\n' -- '\n' -- 'Caution: Creating an incorrect configuration might cause the loss' -- ' of your network connection after reboot!' -- )), -+ reporting.Summary(summary), -+ reporting.Remediation(hint=remmediation_hint), - reporting.ExternalLink( - title='Predictable network interface device names on the System z platform', - url=DOC_URL), --- -2.53.0 - diff --git a/0017-opensshpermitrootlogincheck-Remove-7to8-related-code.patch b/0017-opensshpermitrootlogincheck-Remove-7to8-related-code.patch deleted file mode 100644 index 58e12b4..0000000 --- a/0017-opensshpermitrootlogincheck-Remove-7to8-related-code.patch +++ /dev/null @@ -1,101 +0,0 @@ -From 71aa340ff3c2b5b9cd25e0aa731ace1b87e46d7f Mon Sep 17 00:00:00 2001 -From: Matej Matuska -Date: Mon, 23 Feb 2026 14:15:18 +0100 -Subject: [PATCH 17/44] opensshpermitrootlogincheck: Remove 7to8 related code - -Jira: RHEL-130950 ---- - .../opensshpermitrootlogincheck/actor.py | 64 +------------------ - 1 file changed, 2 insertions(+), 62 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/opensshpermitrootlogincheck/actor.py b/repos/system_upgrade/common/actors/opensshpermitrootlogincheck/actor.py -index 98d329ab..3aedfd5e 100644 ---- a/repos/system_upgrade/common/actors/opensshpermitrootlogincheck/actor.py -+++ b/repos/system_upgrade/common/actors/opensshpermitrootlogincheck/actor.py -@@ -1,7 +1,7 @@ - from leapp import reporting - from leapp.actors import Actor - from leapp.exceptions import StopActorExecutionError --from leapp.libraries.actor.opensshpermitrootlogincheck import global_value, semantics_changes -+from leapp.libraries.actor.opensshpermitrootlogincheck import global_value - from leapp.libraries.common.config.version import get_source_major_version - from leapp.libraries.stdlib import api - from leapp.models import OpenSshConfig, Report -@@ -46,73 +46,13 @@ class OpenSshPermitRootLoginCheck(Actor): - 'Could not check openssh configuration', details={'details': 'No OpenSshConfig facts found.'} - ) - -- if get_source_major_version() == '7': -- self.process7to8(config) -- elif get_source_major_version() == '8': -+ if get_source_major_version() == '8': - self.process8to9(config) - elif int(get_source_major_version()) >= 9: - pass - else: - api.current_logger().warning('Unknown source major version: {}'.format(get_source_major_version())) - -- @staticmethod -- def process7to8(config): -- # when the config was not modified, we can pass this check and let the -- # rpm handle the configuration file update -- if not config.modified: -- return -- -- # When the configuration does not contain *any* PermitRootLogin directive and -- # the configuration file was locally modified, it will not get updated by -- # RPM and the user might be locked away from the server with new default -- if not config.permit_root_login: -- create_report([ -- reporting.Title('Possible problems with remote login using root account'), -- reporting.Summary( -- 'OpenSSH configuration file does not explicitly state ' -- 'the option PermitRootLogin in sshd_config file, ' -- 'which will default in RHEL8 to "prohibit-password".' -- ), -- reporting.Severity(reporting.Severity.HIGH), -- reporting.Groups(COMMON_REPORT_TAGS), -- reporting.Remediation( -- hint='If you depend on remote root logins using passwords, consider ' -- 'setting up a different user for remote administration or adding ' -- '"PermitRootLogin yes" to sshd_config. ' -- 'If this change is ok for you, add explicit ' -- '"PermitRootLogin prohibit-password" to your sshd_config ' -- 'to ignore this inhibitor' -- ), -- reporting.Groups([reporting.Groups.INHIBITOR]) -- ] + COMMON_RESOURCES) -- return -- -- # Check if there is at least one PermitRootLogin other than "no" -- # in match blocks (other than Match All). -- # This usually means some more complicated setup depending on the -- # default value being globally "yes" and being overwritten by this -- # match block -- if semantics_changes(config): -- create_report([ -- reporting.Title('OpenSSH configured to allow root login'), -- reporting.Summary( -- 'OpenSSH is configured to deny root logins in match ' -- 'blocks, but not explicitly enabled in global or ' -- '"Match all" context. This update changes the ' -- 'default to disable root logins using passwords ' -- 'so your server might get inaccessible.' -- ), -- reporting.Severity(reporting.Severity.HIGH), -- reporting.Groups(COMMON_REPORT_TAGS), -- reporting.Remediation( -- hint='Consider using different user for administrative ' -- 'logins or make sure your configuration file ' -- 'contains the line "PermitRootLogin yes" ' -- 'in global context if desired.' -- ), -- reporting.Groups([reporting.Groups.INHIBITOR]) -- ] + COMMON_RESOURCES) -- - @staticmethod - def process8to9(config): - # RHEL8 default sshd configuration file is not modified: It will get replaced by rpm and --- -2.53.0 - diff --git a/0018-opensshpermitrootlogincheck-Respect-target-distro-na.patch b/0018-opensshpermitrootlogincheck-Respect-target-distro-na.patch deleted file mode 100644 index d43afca..0000000 --- a/0018-opensshpermitrootlogincheck-Respect-target-distro-na.patch +++ /dev/null @@ -1,60 +0,0 @@ -From d5edd261a729f0a83aa9642bf3655acf63f8808e Mon Sep 17 00:00:00 2001 -From: Matej Matuska -Date: Mon, 23 Feb 2026 14:15:38 +0100 -Subject: [PATCH 18/44] opensshpermitrootlogincheck: Respect target distro name - in report - -Jira: RHEL-130950 ---- - .../actors/opensshpermitrootlogincheck/actor.py | 17 ++++++++++------- - 1 file changed, 10 insertions(+), 7 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/opensshpermitrootlogincheck/actor.py b/repos/system_upgrade/common/actors/opensshpermitrootlogincheck/actor.py -index 3aedfd5e..93ee5021 100644 ---- a/repos/system_upgrade/common/actors/opensshpermitrootlogincheck/actor.py -+++ b/repos/system_upgrade/common/actors/opensshpermitrootlogincheck/actor.py -@@ -3,6 +3,7 @@ from leapp.actors import Actor - from leapp.exceptions import StopActorExecutionError - from leapp.libraries.actor.opensshpermitrootlogincheck import global_value - from leapp.libraries.common.config.version import get_source_major_version -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.stdlib import api - from leapp.models import OpenSshConfig, Report - from leapp.reporting import create_report -@@ -62,12 +63,12 @@ class OpenSshPermitRootLoginCheck(Actor): - create_report([ - reporting.Title('Possible problems with remote login using root account'), - reporting.Summary( -- 'OpenSSH configuration file will get updated to RHEL9 ' -+ 'OpenSSH configuration file will get updated to {target_distro} 9 ' - 'version, no longer allowing root login with password. ' - 'It is a good practice to use non-root administrative ' - 'user and non-password authentications, but if you rely ' - 'on the remote root login, this change can lock you out ' -- 'of this system.' -+ 'of this system.'.format_map(DISTRO_REPORT_NAMES) - ), - reporting.Severity(reporting.Severity.HIGH), - reporting.Groups(COMMON_REPORT_TAGS), -@@ -93,11 +94,13 @@ class OpenSshPermitRootLoginCheck(Actor): - create_report([ - reporting.Title('Remote root logins globally allowed using password'), - reporting.Summary( -- 'RHEL9 no longer allows remote root logins, but the ' -- 'server configuration explicitly overrides this default. ' -- 'The configuration file will not be updated and root is ' -- 'still going to be allowed to login with password. ' -- 'This is not recommended and considered as a security risk.' -+ '{target_distro} 9 no longer allows remote root logins, but ' -+ 'the server configuration explicitly overrides this default. ' -+ 'The configuration file will not be updated and root is still' -+ 'going to be allowed to login with password. This is not ' -+ 'recommended and considered as a security risk. '.format_map( -+ DISTRO_REPORT_NAMES -+ ) - ), - reporting.Severity(reporting.Severity.HIGH), - reporting.Groups(COMMON_REPORT_TAGS), --- -2.53.0 - diff --git a/0019-checkifcfg_ifcfg-Respect-distro-name-in-report.patch b/0019-checkifcfg_ifcfg-Respect-distro-name-in-report.patch deleted file mode 100644 index 46f75ab..0000000 --- a/0019-checkifcfg_ifcfg-Respect-distro-name-in-report.patch +++ /dev/null @@ -1,120 +0,0 @@ -From 61dc43540b15c71a8a2c2d5705c5952de059b6d8 Mon Sep 17 00:00:00 2001 -From: Matej Matuska -Date: Mon, 2 Mar 2026 18:05:37 +0100 -Subject: [PATCH 19/44] checkifcfg_ifcfg: Respect distro name in report - -Jira: RHEL-130950 ---- - .../checkifcfg/libraries/checkifcfg_ifcfg.py | 37 +++++++++++++------ - .../checkifcfg/tests/unit_test_ifcfg.py | 9 ++++- - 2 files changed, 32 insertions(+), 14 deletions(-) - -diff --git a/repos/system_upgrade/el8toel9/actors/checkifcfg/libraries/checkifcfg_ifcfg.py b/repos/system_upgrade/el8toel9/actors/checkifcfg/libraries/checkifcfg_ifcfg.py -index ed666350..79ede81e 100644 ---- a/repos/system_upgrade/el8toel9/actors/checkifcfg/libraries/checkifcfg_ifcfg.py -+++ b/repos/system_upgrade/el8toel9/actors/checkifcfg/libraries/checkifcfg_ifcfg.py -@@ -1,6 +1,7 @@ - import os - - from leapp import reporting -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.common.rpms import has_package - from leapp.libraries.stdlib import api - from leapp.models import IfCfg, InstalledRPM, RpmTransactionTasks -@@ -8,6 +9,12 @@ from leapp.models import IfCfg, InstalledRPM, RpmTransactionTasks - FMT_LIST_SEPARATOR = '\n - ' - - -+def _format_files_list(files): -+ return "".join( -+ ["{}{}".format(FMT_LIST_SEPARATOR, f) for f in files] -+ ) -+ -+ - def process(): - TRUE_VALUES = ['yes', 'true', '1'] - TYPE_MAP = { -@@ -74,12 +81,15 @@ def process(): - - if bad_type_files: - title = 'Network configuration for unsupported device types detected' -- summary = ('RHEL 9 does not support the legacy network-scripts' -- ' package that was deprecated in RHEL 8 in favor of' -- ' NetworkManager. Files for device types that are not' -- ' supported by NetworkManager are present in the system.' -- ' Files with the problematic configuration:{}').format( -- ''.join(['{}{}'.format(FMT_LIST_SEPARATOR, bfile) for bfile in bad_type_files]) -+ summary = ( -+ "{target_distro} 9 does not support the legacy network-scripts" -+ " package that was deprecated in {source_distro} 8 in favor of" -+ " NetworkManager. Files for device types that are not" -+ " supported by NetworkManager are present in the system." -+ " Files with the problematic configuration:{bad_files}".format( -+ bad_files=_format_files_list(bad_type_files), -+ **DISTRO_REPORT_NAMES, -+ ) - ) - remediation = ('Consult the nm-settings-ifcfg-rh(5) manual for' - ' valid types of ifcfg files. Remove configuration' -@@ -104,12 +114,15 @@ def process(): - - if not_controlled_files: - title = 'Network configuration with disabled NetworkManager support detected' -- summary = ('RHEL 9 does not support the legacy network-scripts' -- ' package that was deprecated in RHEL 8 in favor of' -- ' NetworkManager. Configuration present in the system' -- ' prohibit NetworkManager from loading it.' -- ' Files with the problematic configuration:{}').format( -- ''.join(['{}{}'.format(FMT_LIST_SEPARATOR, bfile) for bfile in not_controlled_files]) -+ summary = ( -+ '{target_distro} 9 does not support the legacy network-scripts' -+ ' package that was deprecated in {source_distro} 8 in favor of' -+ ' NetworkManager. Configuration present in the system' -+ ' prohibit NetworkManager from loading it.' -+ ' Files with the problematic configuration:{bad_files}' -+ ).format( -+ bad_files=_format_files_list(not_controlled_files), -+ **DISTRO_REPORT_NAMES, - ) - remediation = ('Ensure the ifcfg files comply with format described in' - ' nm-settings-ifcfg-rh(5) manual and remove the' -diff --git a/repos/system_upgrade/el8toel9/actors/checkifcfg/tests/unit_test_ifcfg.py b/repos/system_upgrade/el8toel9/actors/checkifcfg/tests/unit_test_ifcfg.py -index ddabedf2..02ffc65c 100644 ---- a/repos/system_upgrade/el8toel9/actors/checkifcfg/tests/unit_test_ifcfg.py -+++ b/repos/system_upgrade/el8toel9/actors/checkifcfg/tests/unit_test_ifcfg.py -@@ -1,3 +1,4 @@ -+from leapp.libraries.common import distro - from leapp.models import IfCfg, IfCfgProperty, InstalledRPM, RPM, RpmTransactionTasks - from leapp.reporting import Report - from leapp.utils.report import is_inhibitor -@@ -85,10 +86,12 @@ def test_ifcfg_good_type(current_actor_context): - assert rpm_transaction.to_install == ["NetworkManager"] - - --def test_ifcfg_not_controlled(current_actor_context): -+def test_ifcfg_not_controlled(monkeypatch, current_actor_context): - """ - Report if there's a NM_CONTROLLED=no file. - """ -+ monkeypatch.setattr(distro, 'get_source_distro_id', lambda: 'rhel') -+ monkeypatch.setattr(distro, 'get_target_distro_id', lambda: 'rhel') - - current_actor_context.feed(IfCfg( - filename="/NM/ifcfg-eth0", -@@ -105,10 +108,12 @@ def test_ifcfg_not_controlled(current_actor_context): - assert "disabled NetworkManager" in report_fields['title'] - - --def test_ifcfg_unknown_type(current_actor_context): -+def test_ifcfg_unknown_type(monkeypatch, current_actor_context): - """ - Report if there's configuration for a type we don't recognize. - """ -+ monkeypatch.setattr(distro, 'get_source_distro_id', lambda: 'rhel') -+ monkeypatch.setattr(distro, 'get_target_distro_id', lambda: 'rhel') - - current_actor_context.feed(IfCfg( - filename="/NM/ifcfg-pigeon0", --- -2.53.0 - diff --git a/0020-opensslproviders-Use-distro-name-in-the-leapp-commen.patch b/0020-opensslproviders-Use-distro-name-in-the-leapp-commen.patch deleted file mode 100644 index b14f6eb..0000000 --- a/0020-opensslproviders-Use-distro-name-in-the-leapp-commen.patch +++ /dev/null @@ -1,155 +0,0 @@ -From 747e4467aafe7c97b2a02b67527af70083a89e91 Mon Sep 17 00:00:00 2001 -From: Matej Matuska -Date: Mon, 2 Mar 2026 18:11:02 +0100 -Subject: [PATCH 20/44] opensslproviders: Use distro name in the leapp comment - -Jira: RHEL-130950 ---- - .../libraries/add_provider.py | 13 ++-- - .../tests/test_add_provider.py | 66 ++++++++++++------- - 2 files changed, 53 insertions(+), 26 deletions(-) - -diff --git a/repos/system_upgrade/el8toel9/actors/opensslproviders/libraries/add_provider.py b/repos/system_upgrade/el8toel9/actors/opensslproviders/libraries/add_provider.py -index 91462f18..3bf4cbf8 100644 ---- a/repos/system_upgrade/el8toel9/actors/opensslproviders/libraries/add_provider.py -+++ b/repos/system_upgrade/el8toel9/actors/opensslproviders/libraries/add_provider.py -@@ -2,13 +2,13 @@ import re - - from leapp import reporting - from leapp.exceptions import StopActorExecutionError -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.stdlib import api - - # The openssl configuration file - # TODO copied from opensslconfigscanner/libraries/readconf.py - CONFIG = '/etc/pki/tls/openssl.cnf' - --LEAPP_COMMENT = '# Modified by leapp during upgrade to RHEL 9\n' - APPEND_STRING = ( - '[provider_sect]\n' - 'default = default_sect\n' -@@ -22,6 +22,11 @@ APPEND_STRING = ( - ) - - -+def _get_leapp_comment(): -+ # cannot access DISTRO_REPORT_NAMES at top level -+ return f"# Modified by leapp during upgrade to {DISTRO_REPORT_NAMES.target} 9\n" -+ -+ - def _add_lines(lines, add): - """ - Add lines to the list of lines. Breaking possible newlines onto separate items -@@ -76,12 +81,12 @@ def _modify_file(f, fail_on_error=True): - lines = f.readlines() - lines = _replace(lines, r"openssl_conf\s*=\s*default_modules", - "openssl_conf = openssl_init", -- LEAPP_COMMENT, True, fail_on_error) -+ _get_leapp_comment(), True, fail_on_error) - lines = _replace(lines, r"\[\s*default_modules\s*\]", - "[openssl_init]\n" - "providers = provider_sect", -- LEAPP_COMMENT, True, fail_on_error) -- lines = _append(lines, APPEND_STRING, LEAPP_COMMENT) -+ _get_leapp_comment(), True, fail_on_error) -+ lines = _append(lines, APPEND_STRING, _get_leapp_comment()) - f.seek(0) - f.write(''.join(lines)) - -diff --git a/repos/system_upgrade/el8toel9/actors/opensslproviders/tests/test_add_provider.py b/repos/system_upgrade/el8toel9/actors/opensslproviders/tests/test_add_provider.py -index 78f2e9c6..c6e31c29 100644 ---- a/repos/system_upgrade/el8toel9/actors/opensslproviders/tests/test_add_provider.py -+++ b/repos/system_upgrade/el8toel9/actors/opensslproviders/tests/test_add_provider.py -@@ -3,12 +3,14 @@ import pytest - from leapp.libraries.actor.add_provider import ( - _add_lines, - _append, -+ _get_leapp_comment, - _modify_file, - _replace, - APPEND_STRING, -- LEAPP_COMMENT, - NotFoundException - ) -+from leapp.libraries.common.testutils import CurrentActorMocked -+from leapp.libraries.stdlib import api - - testdata = ( - ([], 'one', ['one\n']), -@@ -80,32 +82,52 @@ class MockFile: - - - testdata = ( -- ('', ''), -- ('openssl_conf=default_modules\n', -- '{}# openssl_conf=default_modules\nopenssl_conf = openssl_init\n'.format(LEAPP_COMMENT)), -- ('openssl_conf = default_modules\n', -- '{}# openssl_conf = default_modules\nopenssl_conf = openssl_init\n'.format(LEAPP_COMMENT)), -- ('openssl_conf = default_modules\n', -- '{}# openssl_conf = default_modules\nopenssl_conf = openssl_init\n'.format(LEAPP_COMMENT)), -- (' openssl_conf = default_modules \n', -- '{}# openssl_conf = default_modules \nopenssl_conf = openssl_init\n'.format(LEAPP_COMMENT)), -- ('[default_modules]\n', -- '{}# [default_modules]\n[openssl_init]\nproviders = provider_sect\n'.format(LEAPP_COMMENT)), -- ('[ default_modules ]\n', -- '{}# [ default_modules ]\n[openssl_init]\nproviders = provider_sect\n'.format(LEAPP_COMMENT)), -- (' [ default_modules ] \n', -- '{}# [ default_modules ] \n[openssl_init]\nproviders = provider_sect\n'.format(LEAPP_COMMENT)), -- ('openssl_conf=default_modules\n[default_modules]\n', -- '{c}# openssl_conf=default_modules\nopenssl_conf = openssl_init\n' -- '{c}# [default_modules]\n[openssl_init]\nproviders = provider_sect\n'.format(c=LEAPP_COMMENT)), -+ ("", ""), -+ ( -+ "openssl_conf=default_modules\n", -+ "{c}# openssl_conf=default_modules\nopenssl_conf = openssl_init\n", -+ ), -+ ( -+ "openssl_conf = default_modules\n", -+ "{c}# openssl_conf = default_modules\nopenssl_conf = openssl_init\n", -+ ), -+ ( -+ "openssl_conf = default_modules\n", -+ "{c}# openssl_conf = default_modules\nopenssl_conf = openssl_init\n", -+ ), -+ ( -+ " openssl_conf = default_modules \n", -+ "{c}# openssl_conf = default_modules \nopenssl_conf = openssl_init\n", -+ ), -+ ( -+ "[default_modules]\n", -+ "{c}# [default_modules]\n[openssl_init]\nproviders = provider_sect\n", -+ ), -+ ( -+ "[ default_modules ]\n", -+ "{c}# [ default_modules ]\n[openssl_init]\nproviders = provider_sect\n", -+ ), -+ ( -+ " [ default_modules ] \n", -+ "{c}# [ default_modules ] \n[openssl_init]\nproviders = provider_sect\n", -+ ), -+ ( -+ "openssl_conf=default_modules\n[default_modules]\n", -+ "{c}# openssl_conf=default_modules\nopenssl_conf = openssl_init\n" -+ "{c}# [default_modules]\n[openssl_init]\nproviders = provider_sect\n", -+ ), - ) - - - @pytest.mark.parametrize('file_content,expected', testdata) --def test_modify_file(file_content, expected): -- f = MockFile(file_content) -+def test_modify_file(monkeypatch, file_content, expected): -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) -+ -+ f = MockFile(file_content.format(c=_get_leapp_comment())) - - # Test separate replaces and do not fail if pattern is not found - _modify_file(f, False) - -- assert f.content == "{}{}{}\n".format(expected, LEAPP_COMMENT, APPEND_STRING) -+ assert f.content == "{}{}{}\n".format( -+ expected.format(c=_get_leapp_comment()), _get_leapp_comment(), APPEND_STRING -+ ) --- -2.53.0 - diff --git a/0021-opensslconfigcheck-Dont-make-the-recommendation-as-r.patch b/0021-opensslconfigcheck-Dont-make-the-recommendation-as-r.patch deleted file mode 100644 index d65b471..0000000 --- a/0021-opensslconfigcheck-Dont-make-the-recommendation-as-r.patch +++ /dev/null @@ -1,75 +0,0 @@ -From 35ea535848223e32bd01d726a838abd2ae84fa3e Mon Sep 17 00:00:00 2001 -From: Matej Matuska -Date: Mon, 2 Mar 2026 18:30:51 +0100 -Subject: [PATCH 21/44] opensslconfigcheck: Dont make the recommendation as - redhat if not rhel target - -Jira: RHEL-130950 ---- - .../opensslconfigcheck/libraries/opensslconfigcheck.py | 9 +++++++-- - .../tests/component_test_opensslconfigcheck.py | 7 +++++-- - 2 files changed, 12 insertions(+), 4 deletions(-) - -diff --git a/repos/system_upgrade/el8toel9/actors/opensslconfigcheck/libraries/opensslconfigcheck.py b/repos/system_upgrade/el8toel9/actors/opensslconfigcheck/libraries/opensslconfigcheck.py -index 07c1b22f..a686a7f2 100644 ---- a/repos/system_upgrade/el8toel9/actors/opensslconfigcheck/libraries/opensslconfigcheck.py -+++ b/repos/system_upgrade/el8toel9/actors/opensslconfigcheck/libraries/opensslconfigcheck.py -@@ -1,4 +1,5 @@ - from leapp import reporting -+from leapp.libraries.common.config import get_target_distro_id - from leapp.libraries.stdlib import api - - -@@ -228,14 +229,18 @@ def check_crypto_policies(config): - path=("default_modules", "ssl_conf", "ssl_module", - "system_default", "crypto_policy", ".include"), - value="/etc/crypto-policies/back-ends/opensslcnf.config"): -+ - reporting.create_report([ - reporting.Title('The OpenSSL configuration is missing the crypto policies integration'), -+ - reporting.Summary( - 'The OpenSSL configuration file `/etc/pki/tls/openssl.cnf` does not contain the ' - 'directive to include the system-wide crypto policies. This is not recommended ' -- 'by Red Hat and can lead to decreasing overall system security and inconsistent ' -+ '{} can lead to decreasing overall system security and inconsistent ' - 'behavior between applications. If you need to adjust the crypto policies to your ' -- 'needs, it is recommended to use custom crypto policies.' -+ 'needs, it is recommended to use custom crypto policies.'.format( -+ "by Red Hat and" if get_target_distro_id() == "rhel" else "as it" -+ ) - ), - reporting.Severity(reporting.Severity.MEDIUM), - reporting.Groups([ -diff --git a/repos/system_upgrade/el8toel9/actors/opensslconfigcheck/tests/component_test_opensslconfigcheck.py b/repos/system_upgrade/el8toel9/actors/opensslconfigcheck/tests/component_test_opensslconfigcheck.py -index d3363def..892cb7f1 100644 ---- a/repos/system_upgrade/el8toel9/actors/opensslconfigcheck/tests/component_test_opensslconfigcheck.py -+++ b/repos/system_upgrade/el8toel9/actors/opensslconfigcheck/tests/component_test_opensslconfigcheck.py -@@ -1,3 +1,4 @@ -+from leapp.libraries.actor import opensslconfigcheck - from leapp.models import OpenSslConfig, OpenSslConfigBlock, OpenSslConfigPair, Report - - -@@ -12,7 +13,8 @@ def test_actor_execution_empty(current_actor_context): - assert not current_actor_context.consume(Report) - - --def test_actor_execution_empty_modified(current_actor_context): -+def test_actor_execution_empty_modified(current_actor_context, monkeypatch): -+ monkeypatch.setattr(opensslconfigcheck, 'get_target_distro_id', lambda: 'rhel') - current_actor_context.feed( - OpenSslConfig( - blocks=[], -@@ -25,7 +27,8 @@ def test_actor_execution_empty_modified(current_actor_context): - assert 'missing the crypto policies integration' in r[0].report['title'] - - --def test_actor_execution_default_modified(current_actor_context): -+def test_actor_execution_default_modified(current_actor_context, monkeypatch): -+ monkeypatch.setattr(opensslconfigcheck, 'get_target_distro_id', lambda: 'rhel') - current_actor_context.feed( - OpenSslConfig( - openssl_conf='default_modules', --- -2.53.0 - diff --git a/0022-checkmicroarchitecture-Respect-distro-name-in-report.patch b/0022-checkmicroarchitecture-Respect-distro-name-in-report.patch deleted file mode 100644 index 9bda99a..0000000 --- a/0022-checkmicroarchitecture-Respect-distro-name-in-report.patch +++ /dev/null @@ -1,73 +0,0 @@ -From 367654a5eeaa1a13d857ff04acdc1e7890550fc1 Mon Sep 17 00:00:00 2001 -From: Peter Mocary -Date: Mon, 2 Mar 2026 17:35:53 +0100 -Subject: [PATCH 22/44] checkmicroarchitecture: Respect distro name in report - -Also add stable report key, the title used for key computation was using -target major ver == 8: -'Current x86-64 microarchitecture is unsupported in RHEL8' - -Jira: RHEL-130950 ---- - .../libraries/checkmicroarchitecture.py | 17 ++++++++++------- - 1 file changed, 10 insertions(+), 7 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/checkmicroarchitecture/libraries/checkmicroarchitecture.py b/repos/system_upgrade/common/actors/checkmicroarchitecture/libraries/checkmicroarchitecture.py -index a063b534..3011f906 100644 ---- a/repos/system_upgrade/common/actors/checkmicroarchitecture/libraries/checkmicroarchitecture.py -+++ b/repos/system_upgrade/common/actors/checkmicroarchitecture/libraries/checkmicroarchitecture.py -@@ -3,6 +3,7 @@ from collections import namedtuple - from leapp import reporting - from leapp.libraries.common.config.architecture import ARCH_X86_64, matches_architecture - from leapp.libraries.common.config.version import get_target_major_version -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.stdlib import api - from leapp.models import CPUInfo - -@@ -13,11 +14,11 @@ X86_64_V3_FLAGS = ['avx2', 'bmi1', 'bmi2', 'f16c', 'fma', 'abm', 'movbe', 'xsave - MicroarchInfo = namedtuple('MicroarchInfo', ('required_flags', 'extra_report_fields', 'microarch_ver')) - - --def _inhibit_upgrade(missing_flags, target_rhel, microarch_ver, extra_report_fields=None): -- title = 'Current x86-64 microarchitecture is unsupported in {0}'.format(target_rhel) -+def _inhibit_upgrade(missing_flags, target_distro, microarch_ver, extra_report_fields=None): -+ title = 'Current x86-64 microarchitecture is unsupported in the target OS' - summary = ('{0} has a higher CPU requirement than older versions, it now requires a CPU ' - 'compatible with {1} instruction set or higher.\n\n' -- 'Missings flags detected are: {2}\n').format(target_rhel, microarch_ver, ', '.join(missing_flags)) -+ 'Missings flags detected are: {2}\n').format(target_distro, microarch_ver, ', '.join(missing_flags)) - - report_fields = [ - reporting.Title(title), -@@ -28,7 +29,8 @@ def _inhibit_upgrade(missing_flags, target_rhel, microarch_ver, extra_report_fie - reporting.Remediation(hint=('If a case of using virtualization, virtualization platforms often allow ' - 'configuring a minimum denominator CPU model for compatibility when migrating ' - 'between different CPU models. Ensure that minimum requirements are not below ' -- 'that of {0}\n').format(target_rhel)), -+ 'that of {0}\n').format(target_distro)), -+ reporting.Key('0f48cdbe5aa2584e2ca7f4eb470b9b79da9e515d') - ] - - if extra_report_fields: -@@ -69,14 +71,15 @@ def process(): - - microarch_info = rhel_major_to_microarch_reqs.get(get_target_major_version()) - if not microarch_info: -- api.current_logger().info('No known microarchitecture requirements are known for target RHEL%s.', -- get_target_major_version()) -+ api.current_logger().info( -+ 'No known microarchitecture requirements are known' -+ ' for target {target_distro} {version}.'.format(**DISTRO_REPORT_NAMES, version=get_target_major_version())) - return - - missing_flags = [flag for flag in microarch_info.required_flags if flag not in cpuinfo.flags] - api.current_logger().debug('Required flags missing: %s', missing_flags) - if missing_flags: - _inhibit_upgrade(missing_flags, -- 'RHEL{0}'.format(get_target_major_version()), -+ '{target_distro} {version}'.format(**DISTRO_REPORT_NAMES, version=get_target_major_version()), - microarch_info.microarch_ver, - extra_report_fields=microarch_info.extra_report_fields) --- -2.53.0 - diff --git a/0023-checkmemory-Respect-distro-name-in-reports.patch b/0023-checkmemory-Respect-distro-name-in-reports.patch deleted file mode 100644 index 118f6f8..0000000 --- a/0023-checkmemory-Respect-distro-name-in-reports.patch +++ /dev/null @@ -1,88 +0,0 @@ -From 0e250cdc4672263d753209c420c15a33f9ae90eb Mon Sep 17 00:00:00 2001 -From: Peter Mocary -Date: Mon, 2 Mar 2026 17:45:16 +0100 -Subject: [PATCH 23/44] checkmemory: Respect distro name in reports - -Also add stable key to the report, the distro key was computed as if for -7->8 upgrade, the title: -'Minimum memory requirements for RHEL 8 are not met' - -as the reports for 8->9 and 9->10 are unified with the above report, the -following keys are no longer used: -Minimum memory requirements for RHEL 9 are not met -> e3066f6fae135718b3158597145554c4f22b9372 -Minimum memory requirements for RHEL 10 are not met -> ccf14c3ac899f3cdc3123dadac399cafe28ce2ef - -Jira: RHEL-130950 ---- - .../checkmemory/libraries/checkmemory.py | 33 ++++++++++--------- - .../checkmemory/tests/test_checkmemory.py | 2 +- - 2 files changed, 18 insertions(+), 17 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/checkmemory/libraries/checkmemory.py b/repos/system_upgrade/common/actors/checkmemory/libraries/checkmemory.py -index 040b404b..cdf8faa6 100644 ---- a/repos/system_upgrade/common/actors/checkmemory/libraries/checkmemory.py -+++ b/repos/system_upgrade/common/actors/checkmemory/libraries/checkmemory.py -@@ -1,6 +1,6 @@ - from leapp import reporting - from leapp.exceptions import StopActorExecutionError --from leapp.libraries.common.config import architecture, version -+from leapp.libraries.common.config import architecture - from leapp.libraries.stdlib import api - from leapp.models import MemoryInfo - -@@ -32,23 +32,24 @@ def process(): - minimum_req_error = _check_memory(memoryinfo) - - if minimum_req_error: -- title = 'Minimum memory requirements for RHEL {} are not met'.format(version.get_target_major_version()) -+ title = "Minimum memory requirements for the target OS are not met" - summary = 'Memory detected: {} MiB, required: {} MiB'.format( - int(minimum_req_error['detected'] / 1024), - int(minimum_req_error['minimal_req'] / 1024), - ) - reporting.create_report([ -- reporting.Title(title), -- reporting.Summary(summary), -- reporting.Severity(reporting.Severity.HIGH), -- reporting.Groups([reporting.Groups.SANITY, reporting.Groups.INHIBITOR]), -- reporting.ExternalLink( -- url='https://access.redhat.com/solutions/7014179', -- title='Leapp upgrade fail with error"Minimum memory requirements ' -- 'for RHEL 8 are not met"Upgrade cannot proceed' -- ), -- reporting.ExternalLink( -- url='https://access.redhat.com/articles/rhel-limits', -- title='Red Hat Enterprise Linux Technology Capabilities and Limits' -- ), -- ]) -+ reporting.Title(title), -+ reporting.Summary(summary), -+ reporting.Severity(reporting.Severity.HIGH), -+ reporting.Groups([reporting.Groups.SANITY, reporting.Groups.INHIBITOR]), -+ reporting.ExternalLink( -+ url='https://access.redhat.com/solutions/7014179', -+ title='Leapp upgrade fail with error "Minimum memory requirements ' -+ 'for RHEL 8 are not met"Upgrade cannot proceed' -+ ), -+ reporting.ExternalLink( -+ url='https://access.redhat.com/articles/rhel-limits', -+ title='Red Hat Enterprise Linux Technology Capabilities and Limits' -+ ), -+ reporting.Key('be50646b45beb8304c13daf5380d836a4be8e1cc'), -+ ]) -diff --git a/repos/system_upgrade/common/actors/checkmemory/tests/test_checkmemory.py b/repos/system_upgrade/common/actors/checkmemory/tests/test_checkmemory.py -index 79158dc6..38ddfa33 100644 ---- a/repos/system_upgrade/common/actors/checkmemory/tests/test_checkmemory.py -+++ b/repos/system_upgrade/common/actors/checkmemory/tests/test_checkmemory.py -@@ -21,7 +21,7 @@ def test_check_memory_high(monkeypatch): - - - def test_report(monkeypatch): -- title_msg = 'Minimum memory requirements for RHEL 9 are not met' -+ title_msg = 'Minimum memory requirements for the target OS are not met' - monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) - monkeypatch.setattr(api, 'consume', lambda x: iter([MemoryInfo(mem_total=129)])) - monkeypatch.setattr(reporting, "create_report", create_report_mocked()) --- -2.53.0 - diff --git a/0024-targetuserspacecreator-Respect-distro-name-in-report.patch b/0024-targetuserspacecreator-Respect-distro-name-in-report.patch deleted file mode 100644 index 8056dd9..0000000 --- a/0024-targetuserspacecreator-Respect-distro-name-in-report.patch +++ /dev/null @@ -1,126 +0,0 @@ -From 73b2a3f76e6f808c381a29253ef7e1cd5bfd4f69 Mon Sep 17 00:00:00 2001 -From: Peter Mocary -Date: Mon, 2 Mar 2026 18:11:43 +0100 -Subject: [PATCH 24/44] targetuserspacecreator: Respect distro name in report - -Also add stable report key for no base repos inhibitor. The key was -computed with 'Cannot find required basic RHEL target repositories.' as -the title. - -Jira: RHEL-130950 ---- - .../libraries/userspacegen.py | 34 ++++++++++--------- - .../tests/unit_test_targetuserspacecreator.py | 10 +++--- - 2 files changed, 23 insertions(+), 21 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py b/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py -index 7dbadae2..2475aad4 100644 ---- a/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py -+++ b/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py -@@ -844,9 +844,8 @@ def _inhibit_if_no_base_repos(distro_repoids): - no_baseos = all("baseos" not in ri for ri in distro_repoids) - no_appstream = all("appstream" not in ri for ri in distro_repoids) - if no_baseos or no_appstream: -- reporting.create_report([ -- # TODO: Make the report distro agnostic -- reporting.Title('Cannot find required basic RHEL target repositories.'), -+ report = [ -+ reporting.Title('Cannot find required basic target OS repositories.'), - reporting.Summary( - 'This can happen when a repository ID was entered incorrectly either while using the --enablerepo' - ' option of leapp or in a third party actor that produces a CustomTargetRepositoryMessage.' -@@ -854,17 +853,6 @@ def _inhibit_if_no_base_repos(distro_repoids): - reporting.Groups([reporting.Groups.REPOSITORY]), - reporting.Severity(reporting.Severity.HIGH), - reporting.Groups([reporting.Groups.INHIBITOR]), -- reporting.Remediation(hint=( -- 'It is required to have RHEL repositories on the system' -- ' provided by the subscription-manager unless the --no-rhsm' -- ' option is specified. You might be missing a valid SKU for' -- ' the target system or have a failed network connection.' -- ' Check whether your system is attached to a valid SKU that is' -- ' providing RHEL {} repositories.' -- ' If you are using Red Hat Satellite, read the upgrade documentation' -- ' to set up Satellite and the system properly.' -- -- ).format(target_major_version)), - reporting.ExternalLink( - url='https://access.redhat.com/solutions/5392811', - title='RHEL 7 to RHEL 8 LEAPP Upgrade Failing When Using Red Hat Satellite' -@@ -874,8 +862,22 @@ def _inhibit_if_no_base_repos(distro_repoids): - # https://red.ht/preparing-for-upgrade-to-rhel9 - # https://red.ht/preparing-for-upgrade-to-rhel10 - url='https://red.ht/preparing-for-upgrade-to-rhel{}'.format(target_major_version), -- title='Preparing for the upgrade') -- ]) -+ title='Preparing for the upgrade'), -+ reporting.Key('f5770a56e540f27d370da7b697cb4a2e81e2c30d'), -+ ] -+ if get_target_distro_id() == 'rhel': -+ report.append(reporting.Remediation(hint=( -+ 'It is required to have RHEL repositories on the system' -+ ' provided by the subscription-manager unless the --no-rhsm' -+ ' option is specified. You might be missing a valid SKU for' -+ ' the target system or have a failed network connection.' -+ ' Check whether your system is attached to a valid SKU that is' -+ ' providing RHEL {} repositories.' -+ ' If you are using Red Hat Satellite, read the upgrade documentation' -+ ' to set up Satellite and the system properly.' -+ .format(target_major_version))) -+ ) -+ reporting.create_report(report) - raise StopActorExecution() - - -diff --git a/repos/system_upgrade/common/actors/targetuserspacecreator/tests/unit_test_targetuserspacecreator.py b/repos/system_upgrade/common/actors/targetuserspacecreator/tests/unit_test_targetuserspacecreator.py -index e8853979..b49eff56 100644 ---- a/repos/system_upgrade/common/actors/targetuserspacecreator/tests/unit_test_targetuserspacecreator.py -+++ b/repos/system_upgrade/common/actors/targetuserspacecreator/tests/unit_test_targetuserspacecreator.py -@@ -1103,7 +1103,7 @@ def test_gather_target_repositories_none_available(monkeypatch): - reports = [m.report for m in mocked_produce.model_instances if isinstance(m, reporting.Report)] - inhibitors = [m for m in reports if 'INHIBITOR' in m.get('flags', ())] - assert len(inhibitors) == 1 -- assert inhibitors[0].get('title', '') == 'Cannot find required basic RHEL target repositories.' -+ assert inhibitors[0].get('title', '') == 'Cannot find required basic target OS repositories.' - - - @suppress_deprecation(models.RHELTargetRepository) -@@ -1166,7 +1166,7 @@ def test_gather_target_repositories_baseos_appstream_not_available(monkeypatch): - reports = [m.report for m in mocked_produce.model_instances if isinstance(m, reporting.Report)] - inhibitors = [m for m in reports if 'inhibitor' in m.get('groups', ())] - assert len(inhibitors) == 1 -- assert inhibitors[0].get('title', '') == 'Cannot find required basic RHEL target repositories.' -+ assert inhibitors[0].get('title', '') == 'Cannot find required basic target OS repositories.' - # Now test the case when either of AppStream and BaseOs is not available, upgrade should be inhibited - mocked_produce = produce_mocked() - monkeypatch.setattr(userspacegen.api, 'current_actor', CurrentActorMocked()) -@@ -1181,7 +1181,7 @@ def test_gather_target_repositories_baseos_appstream_not_available(monkeypatch): - reports = [m.report for m in mocked_produce.model_instances if isinstance(m, reporting.Report)] - inhibitors = [m for m in reports if 'inhibitor' in m.get('groups', ())] - assert len(inhibitors) == 1 -- assert inhibitors[0].get('title', '') == 'Cannot find required basic RHEL target repositories.' -+ assert inhibitors[0].get('title', '') == 'Cannot find required basic target OS repositories.' - mocked_produce = produce_mocked() - monkeypatch.setattr(userspacegen.api, 'current_actor', CurrentActorMocked()) - monkeypatch.setattr(userspacegen.api.current_actor(), 'produce', mocked_produce) -@@ -1195,7 +1195,7 @@ def test_gather_target_repositories_baseos_appstream_not_available(monkeypatch): - reports = [m.report for m in mocked_produce.model_instances if isinstance(m, reporting.Report)] - inhibitors = [m for m in reports if 'inhibitor' in m.get('groups', ())] - assert len(inhibitors) == 1 -- assert inhibitors[0].get('title', '') == 'Cannot find required basic RHEL target repositories.' -+ assert inhibitors[0].get('title', '') == 'Cannot find required basic target OS repositories.' - - - def test__get_distro_available_repoids_norhsm_norhui(monkeypatch): -@@ -1254,7 +1254,7 @@ def test__get_distro_available_repoids_nobaserepos_inhibit( - # TODO adjust the asserts when the report is made distro agnostic - assert reporting.create_report.called == 1 - report = reporting.create_report.reports[0] -- assert "Cannot find required basic RHEL target repositories" in report["title"] -+ assert "Cannot find required basic target OS repositories" in report["title"] - assert reporting.Groups.INHIBITOR in report["groups"] - - --- -2.53.0 - diff --git a/0025-checkdetecteddevicesanddirvers-Respect-distro-name-i.patch b/0025-checkdetecteddevicesanddirvers-Respect-distro-name-i.patch deleted file mode 100644 index 6ee6806..0000000 --- a/0025-checkdetecteddevicesanddirvers-Respect-distro-name-i.patch +++ /dev/null @@ -1,241 +0,0 @@ -From 8e0aad41201e080482fbc279fd2d58bc11c20e91 Mon Sep 17 00:00:00 2001 -From: Peter Mocary -Date: Mon, 2 Mar 2026 18:46:16 +0100 -Subject: [PATCH 25/44] checkdetecteddevicesanddirvers: Respect distro name in - reports - -Also add stable keys to all reports, the keys were generated as if on -7->8 upgrade (target major version == 8), the exact titles used for -computation: -- 'Leapp detected loaded kernel drivers which have been removed in RHEL 8. Upgrade cannot proceed.' -- 'Leapp detected devices which are no longer supported in RHEL 8. Upgrade cannot proceed.' -- 'Leapp detected a processor which is no longer supported in RHEL 8. Upgrade cannot proceed.' -- 'Leapp detected loaded kernel drivers which are no longer maintained in RHEL 8.' -- 'Leapp detected devices which are no longer maintained in RHEL 8' -- 'Leapp detected a processor which is no longer maintained in RHEL 8.' - -The following report titles and keys are no longer used as they've been -unified with the reports above and now also use the respective keys: - -Leapp detected loaded kernel drivers which have been removed in RHEL 9. Upgrade cannot proceed. - - 7ae2961239eec9905e2580fa6309a589b1dca953 -Leapp detected devices which are no longer supported in RHEL 9. Upgrade cannot proceed. - - 0e042eba4d14bd1f1ae691db6389621f778f21ad -Leapp detected a processor which is no longer supported in RHEL 9. Upgrade cannot proceed. - - f79672290945c09de12a14b28906b98d5c8ed68a -Leapp detected loaded kernel drivers which are no longer maintained in RHEL 9. - - b03c306f274b33b4cf3c7cd3764366c599681481 -Leapp detected devices which are no longer maintained in RHEL 9 - - 65ac6710649c928288162900e8df8316ac8e1bda -Leapp detected a processor which is no longer maintained in RHEL 9. - - 93f6cf039f01095a59ebaf581ced33316e034ef8 -Leapp detected loaded kernel drivers which have been removed in RHEL 10. Upgrade cannot proceed. - - 48e04852631245aa4afee9adcdc7c2375b8cffb8 -Leapp detected devices which are no longer supported in RHEL 10. Upgrade cannot proceed. - - fa9da5bf370c8477eb4f8366b68ffac2aaae6374 -Leapp detected a processor which is no longer supported in RHEL 10. Upgrade cannot proceed. - - f6199d8526ee41c3e89b4ed11b3f8ee90cfc6f9f -Leapp detected loaded kernel drivers which are no longer maintained in RHEL 10. - - fbb11ae5828d624c4e4c91e73d766c8e27b066d9 -Leapp detected devices which are no longer maintained in RHEL 10 - - 93f67baabd93c00625fce5ad47edbf19972e41a0 -Leapp detected a processor which is no longer maintained in RHEL 10. - - 9dde4bcd8b458bd6803462216785df3a1476c1f8 - -Jira: RHEL-130950 ---- - .../libraries/checkdddd.py | 70 +++++++++++-------- - 1 file changed, 41 insertions(+), 29 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/checkdetecteddevicesanddrivers/libraries/checkdddd.py b/repos/system_upgrade/common/actors/checkdetecteddevicesanddrivers/libraries/checkdddd.py -index 1f01adde..22a39c29 100644 ---- a/repos/system_upgrade/common/actors/checkdetecteddevicesanddrivers/libraries/checkdddd.py -+++ b/repos/system_upgrade/common/actors/checkdetecteddevicesanddrivers/libraries/checkdddd.py -@@ -3,6 +3,7 @@ from enum import IntEnum - - from leapp import reporting - from leapp.libraries.common.config.version import get_source_major_version, get_target_major_version -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.stdlib import api - from leapp.models import DetectedDeviceOrDriver - -@@ -22,17 +23,19 @@ def create_inhibitors(inhibiting_entries): - if drivers: - reporting.create_report([ - reporting.Title( -- 'Leapp detected loaded kernel drivers which have been removed ' -- 'in RHEL {}. Upgrade cannot proceed.'.format(get_target_major_version()) -+ 'Leapp detected loaded kernel drivers that have been removed in' -+ ' the target OS. Upgrade cannot proceed.' - ), - reporting.Summary( - ( -- 'Support for the following RHEL {source} device drivers has been removed in RHEL {target}:\n' -+ 'Support for the following {source_distro} {source_version} device drivers has' -+ ' been removed in {target_distro} {target_version}:\n' - ' - {drivers}\n' - ).format( - drivers='\n - '.join([entry.driver_name for entry in drivers]), -- target=get_target_major_version(), -- source=get_source_major_version(), -+ target_version=get_target_major_version(), -+ source_version=get_source_major_version(), -+ **DISTRO_REPORT_NAMES - ) - ), - reporting.ExternalLink( -@@ -52,52 +55,57 @@ def create_inhibitors(inhibiting_entries): - reporting.Audience('sysadmin'), - reporting.Groups([reporting.Groups.KERNEL, reporting.Groups.DRIVERS]), - reporting.Severity(reporting.Severity.HIGH), -- reporting.Groups([reporting.Groups.INHIBITOR]) -+ reporting.Groups([reporting.Groups.INHIBITOR]), -+ reporting.Key('f08a07da902958defa4f5c2699fae9ec2eb67c5b'), - ]) - - devices = inhibiting_entries.get(MessagingClass.DEVICES) - if devices: - reporting.create_report([ - reporting.Title( -- 'Leapp detected devices which are no longer supported in RHEL {}. Upgrade cannot proceed.'.format( -- get_target_major_version()) -+ 'Leapp detected devices no longer supported by the target OS.' -+ ' Upgrade cannot proceed.' - ), - reporting.Summary( - ( -- 'Support for the following devices has been removed in RHEL {target}:\n' -+ 'Support for the following devices has been removed in {target_distro} {version}:\n' - ' - {devices}\n' - ).format( - devices='\n - '.join(['{name} ({pci})'.format(name=entry.device_name, - pci=entry.device_id) for entry in devices]), -- target=get_target_major_version(), -+ version=get_target_major_version(), -+ **DISTRO_REPORT_NAMES - ) - ), - reporting.Audience('sysadmin'), - reporting.Groups([reporting.Groups.KERNEL]), - reporting.Severity(reporting.Severity.HIGH), -- reporting.Groups([reporting.Groups.INHIBITOR]) -+ reporting.Groups([reporting.Groups.INHIBITOR]), -+ reporting.Key('ccfc28592c82123649fc824c6c1c89cabfceae7c'), - ]) - - cpus = inhibiting_entries.get(MessagingClass.CPUS) - if cpus: - reporting.create_report([ - reporting.Title( -- 'Leapp detected a processor which is no longer supported in RHEL {}. Upgrade cannot proceed.'.format( -- get_target_major_version()) -+ 'Leapp detected a processor no longer supported by the target OS.' -+ ' Upgrade cannot proceed.' - ), - reporting.Summary( - ( -- 'Support for the following processors has been removed in RHEL {target}:\n' -+ 'Support for the following processors has been removed in {target_distro} {version}:\n' - ' - {processors}\n' - ).format( - processors='\n - '.join([entry.device_name for entry in cpus]), -- target=get_target_major_version(), -+ version=get_target_major_version(), -+ **DISTRO_REPORT_NAMES - ) - ), - reporting.Audience('sysadmin'), - reporting.Groups([reporting.Groups.KERNEL, reporting.Groups.BOOT]), - reporting.Severity(reporting.Severity.HIGH), -- reporting.Groups([reporting.Groups.INHIBITOR]) -+ reporting.Groups([reporting.Groups.INHIBITOR]), -+ reporting.Key('e3e9e4d2566733e2f843db9823c8568b9b6922f9'), - ]) - - -@@ -109,65 +117,69 @@ def create_warnings(unmaintained_entries): - if drivers: - reporting.create_report([ - reporting.Title( -- 'Leapp detected loaded kernel drivers which are no longer maintained in RHEL {}.'.format( -- get_target_major_version()) -+ 'Leapp detected loaded kernel drivers no longer maintained in the target OS' - ), - reporting.Summary( - ( -- 'The following RHEL {source} device drivers are no longer maintained RHEL {target}:\n' -+ 'The following {source_distro} {source_version} device drivers are no longer' -+ ' maintained {target_distro} {target_version}:\n' - ' - {drivers}\n' - ).format( - drivers='\n - '.join([entry.driver_name for entry in drivers]), -- target=get_target_major_version(), -- source=get_source_major_version(), -+ target_version=get_target_major_version(), -+ source_version=get_source_major_version(), -+ **DISTRO_REPORT_NAMES - ) - ), - reporting.Audience('sysadmin'), - reporting.Groups([reporting.Groups.KERNEL, reporting.Groups.DRIVERS]), - reporting.Severity(reporting.Severity.HIGH), -+ reporting.Key('0ff2413fd3cb0358736bf9be597f4dbdf58f2c4d'), - ]) - - devices = unmaintained_entries.get(MessagingClass.DEVICES) - if devices: - reporting.create_report([ - reporting.Title( -- 'Leapp detected devices which are no longer maintained in RHEL {}'.format( -- get_target_major_version()) -+ 'Leapp detected devices no longer maintained in the target OS' - ), - reporting.Summary( - ( -- 'The support for the following devices has been removed in RHEL {target} and ' -+ 'The support for the following devices has been removed in {target_distro} {version} and ' - 'are no longer maintained:\n - {devices}\n' - ).format( - devices='\n - '.join(['{name} ({pci})'.format(name=entry.device_name, - pci=entry.device_id) for entry in devices]), -- target=get_target_major_version(), -+ version=get_target_major_version(), -+ **DISTRO_REPORT_NAMES - ) - ), - reporting.Audience('sysadmin'), - reporting.Groups([reporting.Groups.KERNEL]), - reporting.Severity(reporting.Severity.HIGH), -+ reporting.Key('261e3e55a3a80346f2fcc2a1e59c64f7a4caa263'), - ]) - - cpus = unmaintained_entries.get(MessagingClass.CPUS) - if cpus: - reporting.create_report([ - reporting.Title( -- 'Leapp detected a processor which is no longer maintained in RHEL {}.'.format( -- get_target_major_version()) -+ 'Leapp detected a processor no longer maintained in the target OS' - ), - reporting.Summary( - ( -- 'The following processors are no longer maintained in RHEL {target}:\n' -+ 'The following processors are no longer maintained in {target_distro} {version}:\n' - ' - {processors}\n' - ).format( - processors='\n - '.join([entry.device_name for entry in cpus]), -- target=get_target_major_version(), -+ version=get_target_major_version(), -+ **DISTRO_REPORT_NAMES - ) - ), - reporting.Audience('sysadmin'), - reporting.Groups([reporting.Groups.KERNEL, reporting.Groups.BOOT]), - reporting.Severity(reporting.Severity.HIGH), -+ reporting.Key('61eb181bbc56328fbe03b5229d25a8ea5ebdc7a2'), - ]) - - --- -2.53.0 - diff --git a/0026-reportleftoverpackages-Respect-distro-name-in-report.patch b/0026-reportleftoverpackages-Respect-distro-name-in-report.patch deleted file mode 100644 index 956d22e..0000000 --- a/0026-reportleftoverpackages-Respect-distro-name-in-report.patch +++ /dev/null @@ -1,140 +0,0 @@ -From e9fa8aac4b32baaaf171c1cd6cd25f88a78d36a0 Mon Sep 17 00:00:00 2001 -From: Peter Mocary -Date: Mon, 2 Mar 2026 19:01:02 +0100 -Subject: [PATCH 26/44] reportleftoverpackages: Respect distro name in reports - -The actors is also moved from the reportleftoverpackages actor -subdirectory to the root actor directory. - -Also add stable report keys to the reports. The keys were computed using -titles: -- 'Leftover RHEL packages have been removed' -- 'Some RHEL packages have not been upgraded' - -Jira: RHEL-130950 ---- - .../{reportleftoverpackages => }/actor.py | 8 ++++---- - .../libraries/reportleftoverpackages.py | 17 +++++++++++------ - .../tests/test_reportleftoverpackages.py | 9 ++++++--- - 3 files changed, 21 insertions(+), 13 deletions(-) - rename repos/system_upgrade/common/actors/reportleftoverpackages/{reportleftoverpackages => }/actor.py (61%) - rename repos/system_upgrade/common/actors/reportleftoverpackages/{reportleftoverpackages => }/libraries/reportleftoverpackages.py (73%) - rename repos/system_upgrade/common/actors/reportleftoverpackages/{reportleftoverpackages => }/tests/test_reportleftoverpackages.py (86%) - -diff --git a/repos/system_upgrade/common/actors/reportleftoverpackages/reportleftoverpackages/actor.py b/repos/system_upgrade/common/actors/reportleftoverpackages/actor.py -similarity index 61% -rename from repos/system_upgrade/common/actors/reportleftoverpackages/reportleftoverpackages/actor.py -rename to repos/system_upgrade/common/actors/reportleftoverpackages/actor.py -index 58573451..d0640774 100644 ---- a/repos/system_upgrade/common/actors/reportleftoverpackages/reportleftoverpackages/actor.py -+++ b/repos/system_upgrade/common/actors/reportleftoverpackages/actor.py -@@ -7,11 +7,11 @@ from leapp.tags import IPUWorkflowTag, RPMUpgradePhaseTag - - class ReportLeftoverPackages(Actor): - """ -- Collect messages about leftover RHEL packages from older major versions and generate a report. -+ Generate a report about leftover distribution packages from older major versions. - -- Depending on execution of previous actors, -- generated report contains information that there are still old RHEL packages -- present on the system, which makes it unsupported or lists packages that have been removed. -+ Generated report informs about old distribution packages present on the system, -+ which makes it unsupported, and lists packages that have been removed if there -+ were any. - """ - - name = 'report_leftover_packages' -diff --git a/repos/system_upgrade/common/actors/reportleftoverpackages/reportleftoverpackages/libraries/reportleftoverpackages.py b/repos/system_upgrade/common/actors/reportleftoverpackages/libraries/reportleftoverpackages.py -similarity index 73% -rename from repos/system_upgrade/common/actors/reportleftoverpackages/reportleftoverpackages/libraries/reportleftoverpackages.py -rename to repos/system_upgrade/common/actors/reportleftoverpackages/libraries/reportleftoverpackages.py -index 51bda931..cd45ac87 100644 ---- a/repos/system_upgrade/common/actors/reportleftoverpackages/reportleftoverpackages/libraries/reportleftoverpackages.py -+++ b/repos/system_upgrade/common/actors/reportleftoverpackages/libraries/reportleftoverpackages.py -@@ -1,4 +1,5 @@ - from leapp import reporting -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.stdlib import api - from leapp.models import LeftoverPackages, RemovedPackages - -@@ -11,15 +12,16 @@ def process(): - leftover_pkgs_to_remove = ['-'.join([pkg.name, pkg.version, pkg.release]) for pkg in leftover_packages.items] - - if removed_packages and removed_packages.items: -- title = 'Leftover RHEL packages have been removed' -+ title = 'Leftover packages from the original OS have been removed' - removed = ['-'.join([pkg.name, pkg.version, pkg.release]) for pkg in removed_packages.items] - summary = ( -- 'Following packages have been removed:{sep}{list}\n' -+ 'Following {source_distro} packages have been removed:{sep}{list}\n' - 'Dependent packages may have been removed as well, please check that you are not missing ' - 'any packages.' - .format( - sep=FMT_LIST_SEPARATOR, -- list=FMT_LIST_SEPARATOR.join(removed) -+ list=FMT_LIST_SEPARATOR.join(removed), -+ **DISTRO_REPORT_NAMES - ) - ) - reporting.create_report([ -@@ -27,23 +29,26 @@ def process(): - reporting.Summary(summary), - reporting.Severity(reporting.Severity.HIGH), - reporting.Groups([reporting.Groups.SANITY]), -+ reporting.Key('5afbad560709afa4e40a160e40dfd44788ba9c3b'), - ] + [reporting.RelatedResource('package', pkg.name) for pkg in removed_packages.items]) - return - - if leftover_packages and leftover_packages.items: - summary = ( -- 'Following RHEL packages have not been upgraded:{sep}{list}\n' -+ 'Following {source_distro} packages have not been upgraded:{sep}{list}\n' - 'Please remove these packages to keep your system in supported state.' - .format( - sep=FMT_LIST_SEPARATOR, -- list=FMT_LIST_SEPARATOR.join(leftover_pkgs_to_remove) -+ list=FMT_LIST_SEPARATOR.join(leftover_pkgs_to_remove), -+ **DISTRO_REPORT_NAMES - ) - ) - reporting.create_report([ -- reporting.Title('Some RHEL packages have not been upgraded'), -+ reporting.Title('Some packages from the original OS have not been upgraded'), - reporting.Summary(summary), - reporting.Severity(reporting.Severity.HIGH), - reporting.Groups([reporting.Groups.SANITY]), -+ reporting.Key('d424c3132ed78a8632b5c73d919909e453107c06'), - ] + [reporting.RelatedResource('package', pkg.name) for pkg in leftover_packages.items]) - else: - api.current_logger().info('No leftover packages, skipping...') -diff --git a/repos/system_upgrade/common/actors/reportleftoverpackages/reportleftoverpackages/tests/test_reportleftoverpackages.py b/repos/system_upgrade/common/actors/reportleftoverpackages/tests/test_reportleftoverpackages.py -similarity index 86% -rename from repos/system_upgrade/common/actors/reportleftoverpackages/reportleftoverpackages/tests/test_reportleftoverpackages.py -rename to repos/system_upgrade/common/actors/reportleftoverpackages/tests/test_reportleftoverpackages.py -index ff493c57..9502bfd2 100644 ---- a/repos/system_upgrade/common/actors/reportleftoverpackages/reportleftoverpackages/tests/test_reportleftoverpackages.py -+++ b/repos/system_upgrade/common/actors/reportleftoverpackages/tests/test_reportleftoverpackages.py -@@ -27,7 +27,10 @@ def test_no_removed_packages_leftover_present(monkeypatch): - reportleftoverpackages.process() - - assert reporting.create_report.called == 1 -- assert 'Some RHEL packages have not been upgraded' in reporting.create_report.report_fields['title'] -+ assert ( -+ 'Some packages from the original OS have not been upgraded' -+ in reporting.create_report.report_fields['title'] -+ ) - assert 'Following RHEL packages have not been upgraded' in reporting.create_report.report_fields['summary'] - summary = 'Please remove these packages to keep your system in supported state.' - assert summary in reporting.create_report.report_fields['summary'] -@@ -44,6 +47,6 @@ def test_removed_packages(monkeypatch): - reportleftoverpackages.process() - - assert reporting.create_report.called == 1 -- assert 'Leftover RHEL packages have been removed' in reporting.create_report.report_fields['title'] -- assert 'Following packages have been removed' in reporting.create_report.report_fields['summary'] -+ assert 'Leftover packages from the original OS have been removed' in reporting.create_report.report_fields['title'] -+ assert 'Following RHEL packages have been removed' in reporting.create_report.report_fields['summary'] - assert 'rpm-1.0-1.el7' in reporting.create_report.report_fields['summary'] --- -2.53.0 - diff --git a/0027-fix-overlaygen-exclude-hugetlbfs-from-overlay-mounts.patch b/0027-fix-overlaygen-exclude-hugetlbfs-from-overlay-mounts.patch deleted file mode 100644 index 7570ad4..0000000 --- a/0027-fix-overlaygen-exclude-hugetlbfs-from-overlay-mounts.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 9f0cbbcb5a75f77b226cf385b6179f93fbdf9bc0 Mon Sep 17 00:00:00 2001 -From: Sergii Golovatiuk -Date: Wed, 18 Mar 2026 17:19:38 +0100 -Subject: [PATCH 27/44] fix(overlaygen): exclude hugetlbfs from overlay mounts - -hugetlbfs should not be mounted in the overlay environment during the -upgrade. It's not a regular mount point. This patch adds it to the -OVERLAY_DO_NOT_MOUNT exclusion list. - -Resolves: RHEL-156902 - -Signed-off-by: Sergii Golovatiuk ---- - repos/system_upgrade/common/libraries/overlaygen.py | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/repos/system_upgrade/common/libraries/overlaygen.py b/repos/system_upgrade/common/libraries/overlaygen.py -index 3091ab5b..9fdaadf9 100644 ---- a/repos/system_upgrade/common/libraries/overlaygen.py -+++ b/repos/system_upgrade/common/libraries/overlaygen.py -@@ -10,7 +10,7 @@ from leapp.libraries.common.config import get_env - from leapp.libraries.common.config.version import get_target_major_version - from leapp.libraries.stdlib import api, CalledProcessError, run - --OVERLAY_DO_NOT_MOUNT = ('tmpfs', 'devtmpfs', 'devpts', 'sysfs', 'proc', 'cramfs', 'sysv', 'vfat') -+OVERLAY_DO_NOT_MOUNT = ('tmpfs', 'devtmpfs', 'devpts', 'sysfs', 'proc', 'cramfs', 'sysv', 'vfat', 'hugetlbfs') - - # NOTE(pstodulk): what about using more closer values and than just multiply - # the final result by magical constant?... this number is most likely going to --- -2.53.0 - diff --git a/0028-biosdevname-Fix-check-of-naming-scheme-and-tests.patch b/0028-biosdevname-Fix-check-of-naming-scheme-and-tests.patch deleted file mode 100644 index d54a4e3..0000000 --- a/0028-biosdevname-Fix-check-of-naming-scheme-and-tests.patch +++ /dev/null @@ -1,121 +0,0 @@ -From 4c303cb3d30f891aa5d5d3ff4b3675deb41c6385 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Mon, 16 Mar 2026 23:10:49 +0100 -Subject: [PATCH 28/44] biosdevname: Fix check of naming scheme and tests - -The original check matched also NIC names with suffixes that do not -correspond to biosdevname naming scheme (e.g. "em0net"). Make the -check strict for the whole ^string$ and update tests to cover the -scenario. - -Also I made small refactorings in tests -* so in case of failure it's visible what input data caused the - failure -* minimize changes needed to do with planned update of Interface model ---- - .../biosdevname/libraries/biosdevname.py | 4 +- - .../biosdevname/tests/test_biosdevname.py | 73 ++++++++----------- - 2 files changed, 31 insertions(+), 46 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/biosdevname/libraries/biosdevname.py b/repos/system_upgrade/common/actors/biosdevname/libraries/biosdevname.py -index a6b4a242..e2f4b1d1 100644 ---- a/repos/system_upgrade/common/actors/biosdevname/libraries/biosdevname.py -+++ b/repos/system_upgrade/common/actors/biosdevname/libraries/biosdevname.py -@@ -27,8 +27,8 @@ def is_vendor_dell(): - - def all_interfaces_biosdevname(interfaces): - # Biosdevname supports two naming schemes -- emx = re.compile('em[0-9]+') -- pxpy = re.compile('p[0-9]+p[0-9]+') -+ emx = re.compile('^em[0-9]+$') -+ pxpy = re.compile('^p[0-9]+p[0-9]+$') - - for i in interfaces: - if emx.match(i.name) is None and pxpy.match(i.name) is None: -diff --git a/repos/system_upgrade/common/actors/biosdevname/tests/test_biosdevname.py b/repos/system_upgrade/common/actors/biosdevname/tests/test_biosdevname.py -index 427eea54..3aa8c614 100644 ---- a/repos/system_upgrade/common/actors/biosdevname/tests/test_biosdevname.py -+++ b/repos/system_upgrade/common/actors/biosdevname/tests/test_biosdevname.py -@@ -59,50 +59,35 @@ def test_is_vendor_is_not_dell(monkeypatch): - assert not biosdevname.is_vendor_dell() - - --def test_all_interfaces_biosdevname(monkeypatch): -- pci_info = PCIAddress(domain="domain", function="function", bus="bus", device="device") -- -- interfaces = [ -- Interface( -- name="eth0", mac="mac", vendor="dell", pci_info=pci_info, devpath="path", driver="drv" -- ) -- ] -- assert not biosdevname.all_interfaces_biosdevname(interfaces) -- interfaces = [ -- Interface( -- name="em0", mac="mac", vendor="dell", pci_info=pci_info, devpath="path", driver="drv" -- ) -- ] -- assert biosdevname.all_interfaces_biosdevname(interfaces) -- interfaces = [ -- Interface( -- name="p0p22", mac="mac", vendor="dell", pci_info=pci_info, devpath="path", driver="drv" -- ) -- ] -- assert biosdevname.all_interfaces_biosdevname(interfaces) -- -- interfaces = [ -- Interface( -- name="p1p2", mac="mac", vendor="dell", pci_info=pci_info, devpath="path", driver="drv" -- ), -- Interface( -- name="em2", mac="mac", vendor="dell", pci_info=pci_info, devpath="path", driver="drv" -- ), -- ] -- assert biosdevname.all_interfaces_biosdevname(interfaces) -- -- interfaces = [ -- Interface( -- name="p1p2", mac="mac", vendor="dell", pci_info=pci_info, devpath="path", driver="drv" -- ), -- Interface( -- name="em2", mac="mac", vendor="dell", pci_info=pci_info, devpath="path", driver="drv" -- ), -- Interface( -- name="eth0", mac="mac", vendor="dell", pci_info=pci_info, devpath="path", driver="drv" -- ), -- ] -- assert not biosdevname.all_interfaces_biosdevname(interfaces) -+def _gen_ifaces_by_names(names): -+ pci = PCIAddress(domain="0000", bus="3e", function="00", device="PCI bridge") -+ interfaces = [] -+ for nic_name in names: -+ interfaces.append(Interface( -+ name=nic_name, -+ devpath="path", -+ driver="drv", -+ mac="52:54:00:0b:4a:6d", -+ pci_info=pci, -+ vendor="dell", -+ )) -+ return interfaces -+ -+ -+@pytest.mark.parametrize(("interface_names", "expected_result"), ( -+ (["eth0"], False), -+ (["preem0"], False), -+ (["em0post"], False), -+ (["prep0p22"], False), -+ (["p0p22post"], False), -+ (["em0"], True), -+ (["p0p22"], True), -+ (["em2", "p1p22"], True), -+ (["p1p2", "em2", "eth0"], False) -+)) -+def test_all_interfaces_biosdevname(interface_names, expected_result): -+ interfaces = _gen_ifaces_by_names(interface_names) -+ assert biosdevname.all_interfaces_biosdevname(interfaces) == expected_result - - - def test_enable_biosdevname(monkeypatch): --- -2.53.0 - diff --git a/0029-persistentnetnamesdisable-refactoring-fix-ethX-detec.patch b/0029-persistentnetnamesdisable-refactoring-fix-ethX-detec.patch deleted file mode 100644 index 9615eff..0000000 --- a/0029-persistentnetnamesdisable-refactoring-fix-ethX-detec.patch +++ /dev/null @@ -1,340 +0,0 @@ -From 8d8774c34518a6269d56f100c2fc312ec0bbbde1 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Fri, 13 Mar 2026 16:15:20 +0100 -Subject: [PATCH 29/44] persistentnetnamesdisable: refactoring + fix ethX - detection - -The original check of ethX interfaces was buggy as it detected all -interfaces with the eth[0-9] substring: - * eth0 - * preeth0 - * eth0post - ... -The check has been corrected to search just for ethX kernel names, -skipping NIC names with additional prefixes or suffixes. Added test -for the ethX_count function. Tests have been slightly refactored to -minimize planned changes in the Interface model. - -Also the actor has been originally executed in incorrect phase. -Moving the actor to CheckPhase where it should be. Also I refactorred -the actor a little bit, moving the code to the private library to -follow current best practices. - -Jira: RHEL-3370 ---- - .../actors/persistentnetnamesdisable/actor.py | 63 +---------- - .../libraries/persistentnetnamesdisable.py | 75 +++++++++++++ - .../tests/test_persistentnetnamesdisable.py | 104 ++++++++++++++---- - 3 files changed, 163 insertions(+), 79 deletions(-) - create mode 100644 repos/system_upgrade/common/actors/persistentnetnamesdisable/libraries/persistentnetnamesdisable.py - -diff --git a/repos/system_upgrade/common/actors/persistentnetnamesdisable/actor.py b/repos/system_upgrade/common/actors/persistentnetnamesdisable/actor.py -index b0182982..15c43141 100644 ---- a/repos/system_upgrade/common/actors/persistentnetnamesdisable/actor.py -+++ b/repos/system_upgrade/common/actors/persistentnetnamesdisable/actor.py -@@ -1,11 +1,8 @@ --import re -- --from leapp import reporting - from leapp.actors import Actor --from leapp.libraries.common.config.version import get_target_major_version -+from leapp.libraries.actor import persistentnetnamesdisable - from leapp.models import KernelCmdlineArg, PersistentNetNamesFacts --from leapp.reporting import create_report, Report --from leapp.tags import FactsPhaseTag, IPUWorkflowTag -+from leapp.reporting import Report -+from leapp.tags import ChecksPhaseTag, IPUWorkflowTag - - - class PersistentNetNamesDisable(Actor): -@@ -16,57 +13,7 @@ class PersistentNetNamesDisable(Actor): - name = 'persistentnetnamesdisable' - consumes = (PersistentNetNamesFacts,) - produces = (KernelCmdlineArg, Report) -- tags = (FactsPhaseTag, IPUWorkflowTag) -- -- @staticmethod -- def ethX_count(interfaces): -- ethX = re.compile('eth[0-9]+') -- count = 0 -- -- for i in interfaces: -- if ethX.match(i.name): -- count = count + 1 -- return count -- -- @staticmethod -- def single_eth0(interfaces): -- return len(interfaces) == 1 and interfaces[0].name == 'eth0' -- -- def disable_persistent_naming(self): -- self.log.info("Single eth0 network interface detected. Appending 'net.ifnames=0' to RHEL-8 kernel commandline") -- self.produce(KernelCmdlineArg(**{'key': 'net.ifnames', 'value': '0'})) -+ tags = (ChecksPhaseTag, IPUWorkflowTag) - - def process(self): -- interfaces = next(self.consume(PersistentNetNamesFacts)).interfaces -- -- if self.single_eth0(interfaces): -- self.disable_persistent_naming() -- elif len(interfaces) > 1 and self.ethX_count(interfaces) > 0: -- report_entries = [ -- reporting.Title('Unsupported network configuration'), -- reporting.Summary( -- 'Detected multiple physical network interfaces where one or more use kernel naming (e.g. eth0). ' -- 'Upgrade process can not continue because stability of names can not be guaranteed. ' -- ), -- reporting.ExternalLink( -- title='How to Perform an In-Place Upgrade when Using Kernel-Assigned NIC Names', -- url='https://access.redhat.com/solutions/4067471' -- ), -- reporting.Remediation( -- hint='Rename all ethX network interfaces following the attached KB solution article.' -- ), -- reporting.Severity(reporting.Severity.HIGH), -- reporting.Groups([reporting.Groups.NETWORK]), -- reporting.Groups([reporting.Groups.INHIBITOR]) -- ] -- -- if get_target_major_version() == '9': -- report_entries.append( -- reporting.ExternalLink( -- title='RHEL 8 to RHEL 9: inplace upgrade fails at ' -- '"Network configuration for unsupported device types detected"', -- url='https://access.redhat.com/solutions/7009239' -- ) -- ) -- -- create_report(report_entries) -+ persistentnetnamesdisable.process() -diff --git a/repos/system_upgrade/common/actors/persistentnetnamesdisable/libraries/persistentnetnamesdisable.py b/repos/system_upgrade/common/actors/persistentnetnamesdisable/libraries/persistentnetnamesdisable.py -new file mode 100644 -index 00000000..0d1a90e2 ---- /dev/null -+++ b/repos/system_upgrade/common/actors/persistentnetnamesdisable/libraries/persistentnetnamesdisable.py -@@ -0,0 +1,75 @@ -+import re -+ -+from leapp import reporting -+from leapp.libraries.common.config.version import get_target_major_version -+from leapp.libraries.stdlib import api -+from leapp.models import KernelCmdlineArg, PersistentNetNamesFacts -+from leapp.reporting import create_report -+ -+ -+def ethX_count(interfaces): -+ """ -+ Count how many network interfaces with ethX naming is present. -+ """ -+ ethX = re.compile('^eth[0-9]+$') -+ count = 0 -+ -+ for i in interfaces: -+ if ethX.match(i.name): -+ count = count + 1 -+ return count -+ -+ -+def single_eth0(interfaces): -+ return len(interfaces) == 1 and interfaces[0].name == 'eth0' -+ -+ -+def disable_persistent_naming(): -+ api.current_logger().info( -+ "Single eth0 network interface detected." -+ " Appending 'net.ifnames=0' for the target system kernel commandline" -+ ) -+ api.produce(KernelCmdlineArg(**{'key': 'net.ifnames', 'value': '0'})) -+ -+ -+def report_ethX_ifaces(): -+ report_entries = [ -+ reporting.Title('Unsupported network configuration'), -+ reporting.Summary( -+ 'Detected multiple physical network interfaces where one or more' -+ ' use kernel naming (e.g. eth0). Upgrade process cannot continue' -+ ' because stability of names can not be guaranteed.' -+ ), -+ reporting.ExternalLink( -+ title='How to Perform an In-Place Upgrade when Using Kernel-Assigned NIC Names', -+ url='https://access.redhat.com/solutions/4067471' -+ ), -+ reporting.Remediation( -+ hint='Rename all ethX network interfaces following the attached KB solution article.' -+ ), -+ reporting.Severity(reporting.Severity.HIGH), -+ reporting.Groups([reporting.Groups.NETWORK]), -+ reporting.Groups([reporting.Groups.INHIBITOR]) -+ ] -+ -+ if get_target_major_version() == '9': -+ report_entries.append( -+ reporting.ExternalLink( -+ title='RHEL 8 to RHEL 9: inplace upgrade fails at ' -+ '"Network configuration for unsupported device types detected"', -+ url='https://access.redhat.com/solutions/7009239' -+ ) -+ ) -+ -+ create_report(report_entries) -+ -+ -+def process(): -+ interfaces = next(api.consume(PersistentNetNamesFacts)).interfaces -+ -+ if single_eth0(interfaces): -+ disable_persistent_naming() -+ return -+ -+ if len(interfaces) > 1 and ethX_count(interfaces): -+ report_ethX_ifaces() -diff --git a/repos/system_upgrade/common/actors/persistentnetnamesdisable/tests/test_persistentnetnamesdisable.py b/repos/system_upgrade/common/actors/persistentnetnamesdisable/tests/test_persistentnetnamesdisable.py -index 95b695c0..2369e80f 100644 ---- a/repos/system_upgrade/common/actors/persistentnetnamesdisable/tests/test_persistentnetnamesdisable.py -+++ b/repos/system_upgrade/common/actors/persistentnetnamesdisable/tests/test_persistentnetnamesdisable.py -@@ -1,17 +1,53 @@ - import pytest - --from leapp.libraries.common.config import version -+from leapp.libraries.actor import persistentnetnamesdisable - from leapp.models import Interface, KernelCmdlineArg, PCIAddress, PersistentNetNamesFacts - from leapp.reporting import Report - from leapp.snactor.fixture import current_actor_context - from leapp.utils.report import is_inhibitor - - -+def _gen_ifaces_by_names(names): -+ pci = PCIAddress(domain="0000", bus="3e", function="00", device="PCI bridge") -+ interfaces = [] -+ for nic_name in names: -+ interfaces.append(Interface( -+ name=nic_name, -+ devpath="/devices/platform/usb/cdc-wdm0", -+ driver="pcieport", -+ mac="52:54:00:0b:4a:6d", -+ pci_info=pci, -+ vendor="redhat", -+ )) -+ return interfaces -+ -+ -+@pytest.mark.parametrize(('interfaces', 'exp_result'), ( -+ (_gen_ifaces_by_names(['eno1', 'eno2', 'myfoo00', 'nicname']), 0), -+ (_gen_ifaces_by_names(['preeth0', 'eth2post', 'preeth0post']), 0), -+ (_gen_ifaces_by_names(['eth0']), 1), -+ (_gen_ifaces_by_names(['eth0', 'eth1', 'eth01', 'eth4980']), 4), -+ (_gen_ifaces_by_names(['myeth0', 'eth0', 'something']), 1), -+)) -+def test_ethX_count(interfaces, exp_result): -+ """ -+ Test the correct detection of ethX interfaces. -+ -+ It tests the bug causing https://issues.redhat.com/browse/RHEL-3370 -+ """ -+ assert persistentnetnamesdisable.ethX_count(interfaces) == exp_result -+ -+ - def test_actor_single_eth0(current_actor_context): - pci = PCIAddress(domain="0000", bus="3e", function="00", device="PCI bridge") -- interface = [Interface(name="eth0", mac="52:54:00:0b:4a:6d", vendor="redhat", -- driver="pcieport", pci_info=pci, -- devpath="/devices/platform/usb/cdc-wdm0")] -+ interface = [Interface( -+ name="eth0", -+ mac="52:54:00:0b:4a:6d", -+ vendor="redhat", -+ driver="pcieport", -+ pci_info=pci, -+ devpath="/devices/platform/usb/cdc-wdm0" -+ )] - current_actor_context.feed(PersistentNetNamesFacts(interfaces=interface)) - current_actor_context.run() - assert not current_actor_context.consume(Report) -@@ -21,15 +57,25 @@ def test_actor_single_eth0(current_actor_context): - 'target_version', ['9', '10'] - ) - def test_actor_more_ethX(monkeypatch, current_actor_context, target_version): -- monkeypatch.setattr(version, 'get_target_major_version', lambda: target_version) -+ monkeypatch.setattr(persistentnetnamesdisable, 'get_target_major_version', lambda: target_version) - pci1 = PCIAddress(domain="0000", bus="3e", function="00", device="PCI bridge") - pci2 = PCIAddress(domain="0000", bus="3d", function="00", device="Serial controller") -- interface = [Interface(name="eth0", mac="52:54:00:0b:4a:6d", vendor="redhat", -- driver="pcieport", pci_info=pci1, -- devpath="/devices/platform/usb/cdc-wdm0"), -- Interface(name="eth1", mac="52:54:00:0b:4a:6a", vendor="redhat", -- driver="serial", pci_info=pci2, -- devpath="/devices/hidraw/hidraw0")] -+ interface = [ -+ Interface( -+ name="eth0", -+ mac="52:54:00:0b:4a:6d", -+ vendor="redhat", -+ driver="pcieport", -+ pci_info=pci1, -+ devpath="/devices/platform/usb/cdc-wdm0"), -+ Interface( -+ name="eth1", -+ mac="52:54:00:0b:4a:6a", -+ vendor="redhat", -+ driver="serial", -+ pci_info=pci2, -+ devpath="/devices/hidraw/hidraw0") -+ ] - current_actor_context.feed(PersistentNetNamesFacts(interfaces=interface)) - current_actor_context.run() - -@@ -50,9 +96,15 @@ def test_actor_more_ethX(monkeypatch, current_actor_context, target_version): - - def test_actor_single_int_not_ethX(current_actor_context): - pci = PCIAddress(domain="0000", bus="3e", function="00", device="PCI bridge") -- interface = [Interface(name="tap0", mac="52:54:00:0b:4a:60", vendor="redhat", -- driver="pcieport", pci_info=pci, -- devpath="/devices/platform/usb/cdc-wdm0")] -+ interface = [ -+ Interface( -+ name="tap0", -+ mac="52:54:00:0b:4a:60", -+ vendor="redhat", -+ driver="pcieport", -+ pci_info=pci, -+ devpath="/devices/platform/usb/cdc-wdm0") -+ ] - current_actor_context.feed(PersistentNetNamesFacts(interfaces=interface)) - current_actor_context.run() - assert not current_actor_context.consume(Report) -@@ -62,15 +114,25 @@ def test_actor_single_int_not_ethX(current_actor_context): - 'target_version', ['9', '10'] - ) - def test_actor_ethX_and_not_ethX(monkeypatch, current_actor_context, target_version): -- monkeypatch.setattr(version, 'get_target_major_version', lambda: target_version) -+ monkeypatch.setattr(persistentnetnamesdisable, 'get_target_major_version', lambda: target_version) - pci1 = PCIAddress(domain="0000", bus="3e", function="00", device="PCI bridge") - pci2 = PCIAddress(domain="0000", bus="3d", function="00", device="Serial controller") -- interface = [Interface(name="virbr0", mac="52:54:00:0b:4a:6d", vendor="redhat", -- driver="pcieport", pci_info=pci1, -- devpath="/devices/platform/usb/cdc-wdm0"), -- Interface(name="eth0", mac="52:54:00:0b:4a:6a", vendor="redhat", -- driver="serial", pci_info=pci2, -- devpath="/devices/hidraw/hidraw0")] -+ interface = [ -+ Interface( -+ name="virbr0", -+ mac="52:54:00:0b:4a:6d", -+ vendor="redhat", -+ driver="pcieport", -+ pci_info=pci1, -+ devpath="/devices/platform/usb/cdc-wdm0"), -+ Interface( -+ name="eth0", -+ mac="52:54:00:0b:4a:6a", -+ vendor="redhat", -+ driver="serial", -+ pci_info=pci2, -+ devpath="/devices/hidraw/hidraw0") -+ ] - current_actor_context.feed(PersistentNetNamesFacts(interfaces=interface)) - current_actor_context.run() - assert current_actor_context.consume(Report) --- -2.53.0 - diff --git a/0030-Fix-scan-of-net-interfaces-non-PCI-conflicting-broke.patch b/0030-Fix-scan-of-net-interfaces-non-PCI-conflicting-broke.patch deleted file mode 100644 index 4f41a2f..0000000 --- a/0030-Fix-scan-of-net-interfaces-non-PCI-conflicting-broke.patch +++ /dev/null @@ -1,518 +0,0 @@ -From cdbb91156d1b87883523eed476c9a5d16b4b4e20 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Wed, 21 Feb 2024 20:55:54 +0100 -Subject: [PATCH 30/44] Fix scan of net interfaces: non-PCI, conflicting, - broken -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -Non-PCI network devices (present usually on IBM Z architecture) -does not have ID_VENDOR_ID attribute and also there is nothing -we could put into Interface.pci_info about them. -For such devices: - * set empty string for Interface.vendor field - * and None value for pci_info. -The Interface model has been updated, allowing None value for -pci_info field. - -Also some interfaces do not have to be "managed" by udev or can -provide incomplete data. This can happen e.g. in situations when -udev want to assign a NIC name that is already used by another -network interface. Such an interface stays with kernel name (ethX) -and udev DB contains only elementary information about it. This -led originally to a skip of the interface during system scanning -by leapp actors and such systems bypassed checks for presence of -ethX NIC names later. Usually such interfaces have been renamed after -the upgrade (either to a new non-ethX name, or they had a different -ethX value) which led to another issues. - -The new solution requires just bare-minimum details to be always -present (like NIC name and device path; if missing, the skip is used -again). For other values we set an empty strings if missing. - -Tests have been updated and extended to cover new changes, using -mainly real input data collected from affected systems. - -Jira: RHEL-22371, RHEL-72140 - -Co-authored-by: Michal Hečko ---- - .../tests/test_persistentnetnames.py | 5 + - .../tests/test_persistentnetnamesinitramfs.py | 6 + - .../common/libraries/persistentnetnames.py | 35 ++- - .../tests/test_persistentnetnames_library.py | 243 ++++++++++++++++-- - .../common/models/persistentnetnamesfacts.py | 57 +++- - 5 files changed, 314 insertions(+), 32 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/persistentnetnames/tests/test_persistentnetnames.py b/repos/system_upgrade/common/actors/persistentnetnames/tests/test_persistentnetnames.py -index ffea5983..a47eeabe 100644 ---- a/repos/system_upgrade/common/actors/persistentnetnames/tests/test_persistentnetnames.py -+++ b/repos/system_upgrade/common/actors/persistentnetnames/tests/test_persistentnetnames.py -@@ -32,6 +32,11 @@ class interfaces_mocked: - - @pytest.mark.parametrize('count', [0, 1, 8, 256]) - def test_run(monkeypatch, current_actor_context, count): -+ """ -+ Basic test of interface scanner actor -+ -+ Full testing of underlying function is covered in tests of common library. -+ """ - monkeypatch.setattr(persistentnetnames, 'interfaces', interfaces_mocked(count)) - current_actor_context.run() - assert len(current_actor_context.consume(PersistentNetNamesFacts)[0].interfaces) == count -diff --git a/repos/system_upgrade/common/actors/persistentnetnamesinitramfs/tests/test_persistentnetnamesinitramfs.py b/repos/system_upgrade/common/actors/persistentnetnamesinitramfs/tests/test_persistentnetnamesinitramfs.py -index f149502b..5605af4b 100644 ---- a/repos/system_upgrade/common/actors/persistentnetnamesinitramfs/tests/test_persistentnetnamesinitramfs.py -+++ b/repos/system_upgrade/common/actors/persistentnetnamesinitramfs/tests/test_persistentnetnamesinitramfs.py -@@ -32,6 +32,12 @@ class interfaces_mocked: - - @pytest.mark.parametrize('count', [0, 1, 8, 256]) - def test_run(monkeypatch, current_actor_context, count): -+ """ -+ Basic functionality test. -+ -+ The full testing of the underlying scanner function is covered by the common -+ library. -+ """ - monkeypatch.setattr(persistentnetnames, 'interfaces', interfaces_mocked(count)) - current_actor_context.run() - assert len(current_actor_context.consume(PersistentNetNamesFactsInitramfs)[0].interfaces) == count -diff --git a/repos/system_upgrade/common/libraries/persistentnetnames.py b/repos/system_upgrade/common/libraries/persistentnetnames.py -index 7fdf7eaa..48dee5f8 100644 ---- a/repos/system_upgrade/common/libraries/persistentnetnames.py -+++ b/repos/system_upgrade/common/libraries/persistentnetnames.py -@@ -41,16 +41,41 @@ def interfaces(): - try: - attrs['name'] = dev.sys_name - attrs['devpath'] = dev.device_path -- attrs['driver'] = dev['ID_NET_DRIVER'] -- attrs['vendor'] = dev['ID_VENDOR_ID'] -- attrs['pci_info'] = PCIAddress(**pci_info(dev['ID_PATH'])) -- attrs['mac'] = dev.attributes.get('address') -+ -+ # can be missing when the interface is not "managed" by udev -+ # TODO(pstodulk): check the MAC, I think that that one should be -+ # actually always in DB. -+ attrs['driver'] = dev.get('ID_NET_DRIVER', '') -+ attrs['mac'] = dev.attributes.get('address', '') - if isinstance(attrs['mac'], bytes): - attrs['mac'] = attrs['mac'].decode() -+ -+ # pci info is not provided for cards that do not use PCI bus, -+ # also it can be missing if the interface is not "managed" by udev. -+ # Also vendor can be provided only for cards on PCI bus. -+ attrs['vendor'] = dev.get('ID_VENDOR_ID', '') -+ attrs['pci_info'] = None # default for non-PCI card -+ if dev.get('ID_PATH', '').startswith('pci-'): -+ attrs['pci_info'] = PCIAddress(**pci_info(dev['ID_PATH'])) - except Exception as e: # pylint: disable=broad-except - # FIXME(msekleta): We should probably handle errors more granularly - # Maybe we should inhibit upgrade process at this point -- api.current_logger().warning('Failed to gather information about network interface: %s', e) -+ net_name = attrs.get('name', 'unknown-interface') -+ api.current_logger().warning( -+ 'Failed to gather information about network interface %s: "%s"', -+ net_name, str(e) -+ ) -+ # NOTE(pstodulk): skipping the interface as it's really broken -+ # or unknown from the code in leapp-repository pov and another -+ # processing would lead now to a broken upgrades. -+ # Other values that are usually expected but can be missing -+ # in some situations are covered and do not raise this exception. - continue - -+ if not all([attrs['driver'], attrs['mac']]): -+ api.current_logger().warning( -+ 'Information in udev about %s network interface is incomplete.', -+ attrs['name'] -+ ) -+ - yield Interface(**attrs) -diff --git a/repos/system_upgrade/common/libraries/tests/test_persistentnetnames_library.py b/repos/system_upgrade/common/libraries/tests/test_persistentnetnames_library.py -index 74aa08fa..b45863d9 100644 ---- a/repos/system_upgrade/common/libraries/tests/test_persistentnetnames_library.py -+++ b/repos/system_upgrade/common/libraries/tests/test_persistentnetnames_library.py -@@ -1,57 +1,252 @@ -+import pytest -+ - from leapp.libraries.common import persistentnetnames - from leapp.libraries.common.testutils import produce_mocked - from leapp.libraries.stdlib import api -+from leapp.models import PCIAddress - - --class AttributesTest: -- def __init__(self): -- self.attributes = { -- 'address': b'fa:16:3e:cd:26:5a' -- } -+class AttributesMocked: -+ def __init__(self, attributes): -+ self._attributes = attributes - -- def get(self, attribute): -- if attribute in self.attributes: -- return self.attributes[attribute] -- raise KeyError -+ def get(self, key, default=None): -+ return self._attributes.get(key, default) - - --class DeviceTest: -- def __init__(self): -- self.dict_data = { -- 'ID_NET_DRIVER': 'virtio_net', -- 'ID_VENDOR_ID': '0x1af4', -- 'ID_PATH': 'pci-0000:00:03.0', -- } -+class DeviceMocked: -+ def __init__(self, properties, attributes): -+ self.dict_data = properties -+ self._attributes = attributes - - def __getitem__(self, key): -+ return self.dict_data[key] -+ -+ def get(self, key, default=None): - if key in self.dict_data: - return self.dict_data[key] -- raise KeyError -+ return default - - @property - def sys_name(self): -- return 'eth' -+ return self.dict_data['INTERFACE'] - - @property - def device_path(self): -- return '/devices/pci0000:00/0000:00:03.0/virtio0/net/eth0' -+ return self.dict_data['DEVPATH'] - - @property - def attributes(self): -- return AttributesTest() -+ return AttributesMocked(self._attributes) -+ - -+@pytest.mark.parametrize('input_mac', ('fa:16:3e:cd:26:5a', b'fa:16:3e:cd:26:5a')) -+def test_getting_interfaces_complete_good(monkeypatch, input_mac): -+ """ -+ Detailed parsing of physical net interface with complete data -+ """ -+ def mocked_physical_interfaces(): -+ properties = { -+ 'CURRENT_TAGS': ':systemd:', -+ 'DEVPATH': '/devices/pci0000:17/0000:17:02.0/0000:19:00.0/net/eno3', -+ 'ID_BUS': 'pci', -+ 'ID_MM_CANDIDATE': '1', -+ 'ID_MODEL_FROM_DATABASE': 'NetXtreme BCM5720 Gigabit Ethernet PCIe', -+ 'ID_MODEL_ID': '0x165f', -+ 'ID_NET_DRIVER': 'tg3', -+ 'ID_NET_LABEL_ONBOARD': 'NIC3', -+ 'ID_NET_LINK_FILE': '/usr/lib/systemd/network/99-default.link', -+ 'ID_NET_NAME': 'eno3', -+ 'ID_NET_NAME_MAC': 'enx34735a9920fe', -+ 'ID_NET_NAME_ONBOARD': 'eno3', -+ 'ID_NET_NAME_PATH': 'enp25s0f0', -+ 'ID_NET_NAMING_SCHEME': 'rhel-9.0', -+ 'ID_OUI_FROM_DATABASE': 'Dell Inc.', -+ 'ID_PATH': 'pci-0000:19:00.0', -+ 'ID_PATH_TAG': 'pci-0000_19_00_0', -+ 'ID_PCI_CLASS_FROM_DATABASE': 'Network controller', -+ 'ID_PCI_SUBCLASS_FROM_DATABASE': 'Ethernet controller', -+ 'ID_VENDOR_FROM_DATABASE': 'Broadcom Inc. and subsidiaries', -+ 'ID_VENDOR_ID': '0x14e4', -+ 'IFINDEX': '4', -+ 'INTERFACE': 'eno3', -+ 'SUBSYSTEM': 'net', -+ 'SYSTEMD_ALIAS': '/sys/subsystem/net/devices/eno3', -+ 'TAGS': ':systemd:', -+ 'USEC_INITIALIZED': '16690226' -+ } -+ attributes = { -+ 'address': input_mac -+ } -+ return [DeviceMocked(properties, attributes)] -+ -+ monkeypatch.setattr(persistentnetnames, 'physical_interfaces', mocked_physical_interfaces) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ interface = next(persistentnetnames.interfaces()) - --def provide_test_interfaces(): -- return [DeviceTest()] -+ assert interface.name == 'eno3' -+ assert interface.devpath == '/devices/pci0000:17/0000:17:02.0/0000:19:00.0/net/eno3' -+ assert interface.driver == 'tg3' -+ assert interface.vendor == '0x14e4' -+ assert interface.mac == 'fa:16:3e:cd:26:5a' -+ assert interface.pci_info == PCIAddress( -+ domain='0000', -+ bus='19', -+ device='00', -+ function='0' -+ ) - - --def test_getting_interfaces(monkeypatch): -- monkeypatch.setattr(persistentnetnames, 'physical_interfaces', provide_test_interfaces) -+def test_getting_interfaces_complete_good_roce(monkeypatch): -+ """ -+ ROCE net interface parsing -+ """ -+ def mocked_physical_interfaces(): -+ properties = { -+ 'CURRENT_TAGS': ':systemd:', -+ 'DEVPATH': '/devices/pci0201:00/0201:00:00.0/net/eno513', -+ 'ID_BUS': 'pci', -+ 'ID_MODEL_FROM_DATABASE': 'ConnectX Family mlx5Gen Virtual Function', -+ 'ID_MODEL_ID': '0x101e', -+ 'ID_NET_DRIVER': 'mlx5_core', -+ 'ID_NET_LINK_FILE': '/etc/systemd/network/10-anaconda-ifname-eno513.link', -+ 'ID_NET_NAME': 'eno513', -+ 'ID_NET_NAME_MAC': 'enx2219aef66069', -+ 'ID_NET_NAME_ONBOARD': 'eno513', -+ 'ID_NET_NAME_PATH': 'enP513p0s0', -+ 'ID_NET_NAME_SLOT': 'ens5912', -+ 'ID_NET_NAMING_SCHEME': 'rhel-9.0', -+ 'ID_PATH': 'pci-0201:00:00.0', -+ 'ID_PATH_TAG': 'pci-0201_00_00_0', -+ 'ID_PCI_CLASS_FROM_DATABASE': 'Network controller', -+ 'ID_PCI_SUBCLASS_FROM_DATABASE': 'Ethernet controller', -+ 'ID_VENDOR_FROM_DATABASE': 'Mellanox Technologies', -+ 'ID_VENDOR_ID': '0x15b3', -+ 'IFINDEX': '2', -+ 'INTERFACE': 'eno513', -+ 'SUBSYSTEM': 'net', -+ 'SYSTEMD_ALIAS': '/sys/subsystem/net/devices/eno513', -+ 'TAGS': ':systemd:', -+ 'USEC_INITIALIZED': '26747014' -+ } -+ attributes = { -+ 'address': b'22:19:ae:f6:60:69' -+ } -+ -+ return [DeviceMocked(properties, attributes)] -+ -+ monkeypatch.setattr(persistentnetnames, 'physical_interfaces', mocked_physical_interfaces) - monkeypatch.setattr(api, 'produce', produce_mocked()) -+ - interface = next(persistentnetnames.interfaces()) -+ - assert interface.name - assert interface.devpath - assert interface.driver - assert interface.vendor -+ assert interface.mac - assert interface.pci_info -+ -+ -+@pytest.mark.parametrize(('properties', 'attributes'), ( -+ ( -+ { -+ # artificial data -+ 'CURRENT_TAGS': ':systemd:', -+ 'DEVPATH': '/devices/whatever/net/eno3', -+ 'ID_MM_CANDIDATE': '1', -+ 'ID_MODEL_FROM_DATABASE': 'Foo', -+ 'ID_MODEL_ID': '0x0000', -+ 'ID_NET_DRIVER': 'tg3', -+ 'ID_NET_LABEL_ONBOARD': 'NIC3', -+ 'ID_NET_LINK_FILE': '/usr/lib/systemd/network/99-default.link', -+ 'ID_NET_NAME': 'eno3', -+ 'ID_NET_NAME_MAC': 'enx34735a9920fe', -+ 'ID_NET_NAME_ONBOARD': 'eno3', -+ 'ID_NET_NAME_PATH': 'enp25s0f0', -+ 'ID_NET_NAMING_SCHEME': 'rhel-9.0', -+ 'ID_OUI_FROM_DATABASE': 'Dell Inc.', -+ 'IFINDEX': '4', -+ 'INTERFACE': 'eno3', -+ 'SUBSYSTEM': 'net', -+ 'SYSTEMD_ALIAS': '/sys/subsystem/net/devices/eno3', -+ 'TAGS': ':systemd:', -+ 'USEC_INITIALIZED': '16690226' -+ }, { -+ 'address': b'fa:16:3e:cd:26:5a' -+ } -+ ), ( -+ { -+ 'CURRENT_TAGS': ':systemd:', -+ 'DEVPATH': '/devices/css0/0.0.0001/0.0.0001/virtio1/net/enc1', -+ 'ID_NET_DRIVER': 'virtio_net', -+ 'ID_NET_LINK_FILE': '/usr/lib/systemd/network/99-default.link', -+ 'ID_NET_NAME': 'enc1', -+ 'ID_NET_NAME_MAC': 'enx001738010124', -+ 'ID_NET_NAME_PATH': 'enc1', -+ 'ID_NET_NAMING_SCHEME': 'rhel-9.0', -+ 'ID_OUI_FROM_DATABASE': 'International Business Machines', -+ 'ID_PATH': 'ccw-0.0.0001', -+ 'ID_PATH_TAG': 'ccw-0_0_0001', -+ 'IFINDEX': '2', -+ 'INTERFACE': 'enc1', -+ 'SUBSYSTEM': 'net', -+ 'SYSTEMD_ALIAS': '/sys/subsystem/net/devices/enc1', -+ 'TAGS': ':systemd:', -+ 'USEC_INITIALIZED': '3423981' -+ }, { -+ 'address': b'00:17:38:01:01:24' -+ } -+ ) -+)) -+def test_getting_interfaces_nonpci_good(monkeypatch, properties, attributes): -+ """ -+ Processing of net interface with incomplete data -+ """ -+ def mocked_physical_interfaces(): -+ return [DeviceMocked(properties, attributes)] -+ -+ monkeypatch.setattr(persistentnetnames, 'physical_interfaces', mocked_physical_interfaces) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ interface = next(persistentnetnames.interfaces()) -+ -+ assert interface.name -+ assert interface.devpath -+ assert interface.driver -+ assert not interface.vendor -+ assert interface.mac -+ assert not interface.pci_info -+ -+ -+def test_getting_interfaces_incomplete_udev_conflict(monkeypatch): -+ """ -+ Test parsing of conflicting interface. -+ -+ Such interface is not managed by udev and the information we could get -+ about it is limited. -+ """ -+ def mocked_physical_interfaces(): -+ properties = { -+ 'DEVPATH': '/devices/pci0000:ae/0000:ae:00.0/0000:af:00.0/0000:b0:04.0/0000:b2:00.1/net/eth3', -+ 'IFINDEX': '9', -+ 'INTERFACE': 'eth3', -+ 'SUBSYSTEM': 'net' -+ } -+ attributes = { -+ 'address': b'fa:16:3e:cd:26:5a' -+ } -+ return [DeviceMocked(properties, attributes)] -+ -+ monkeypatch.setattr(persistentnetnames, 'physical_interfaces', mocked_physical_interfaces) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ interface = next(persistentnetnames.interfaces()) -+ -+ assert interface.name -+ assert interface.devpath -+ assert not interface.driver -+ assert not interface.vendor - assert interface.mac -+ assert not interface.pci_info -diff --git a/repos/system_upgrade/common/models/persistentnetnamesfacts.py b/repos/system_upgrade/common/models/persistentnetnamesfacts.py -index 395b26f0..3c71cdcd 100644 ---- a/repos/system_upgrade/common/models/persistentnetnamesfacts.py -+++ b/repos/system_upgrade/common/models/persistentnetnamesfacts.py -@@ -4,7 +4,10 @@ from leapp.topics import SystemInfoTopic - - class PCIAddress(Model): - """ -- TODO: tbd -+ Network Interface PCI address. -+ -+ This model should not be produced nor consumed by actors directly. -+ It's part of the Interface model. - """ - topic = SystemInfoTopic - -@@ -16,16 +19,49 @@ class PCIAddress(Model): - - class Interface(Model): - """ -- TODO: tbd - Interface or NetworkInterface? -+ Physical network interface -+ -+ Contains information about a network interface collected from udev. -+ Data can be incomplete in case of issues or when the interface is not -+ managed by udev. -+ -+ This model should not be produced or consumed by actors directly. -+ See PersistentNetNamesFacts or PersistentNetNamesFactsInitramfs. - """ - topic = SystemInfoTopic - - name = fields.String() -+ """ -+ Name of the interface. -+ """ -+ - devpath = fields.String() -+ """ -+ Path to the device. -+ """ -+ - driver = fields.String() -+ """ -+ Network interface driver identifier. -+ """ -+ - vendor = fields.String() -- pci_info = fields.Model(PCIAddress) -+ """ -+ Numeric identifier of the hardware vendor on PCI bus. -+ """ -+ -+ pci_info = fields.Nullable(fields.Model(PCIAddress)) -+ """ -+ Parsed PCI address of the network interface. -+ -+ The value is None if the network interface is not connected via PCI or it is not managed -+ by udev. -+ """ -+ - mac = fields.String() -+ """ -+ MAC address of the network interface. -+ """ - - - class PersistentNetNamesFacts(Model): -@@ -34,6 +70,9 @@ class PersistentNetNamesFacts(Model): - """ - topic = SystemInfoTopic - interfaces = fields.List(fields.Model(Interface)) -+ """ -+ List of network interfaces with information collected from udev. -+ """ - - - class PersistentNetNamesFactsInitramfs(PersistentNetNamesFacts): -@@ -49,8 +88,17 @@ class RenamedInterface(Model): - """ - topic = SystemInfoTopic - -+ # TODO(pstodulk) deprecate these fields and replace them by new ones -+ # or deprecate the model completely. - rhel7_name = fields.String() -+ """ -+ Original interface name. -+ """ -+ - rhel8_name = fields.String() -+ """ -+ New interface name. -+ """ - - - class RenamedInterfaces(Model): -@@ -63,3 +111,6 @@ class RenamedInterfaces(Model): - topic = SystemInfoTopic - - renamed = fields.List(fields.Model(RenamedInterface)) -+ """ -+ The list of renamed interfaces. -+ """ --- -2.53.0 - diff --git a/0031-persistentnetnamesconfig-Deprecate-legacy-functional.patch b/0031-persistentnetnamesconfig-Deprecate-legacy-functional.patch deleted file mode 100644 index 68ff880..0000000 --- a/0031-persistentnetnamesconfig-Deprecate-legacy-functional.patch +++ /dev/null @@ -1,398 +0,0 @@ -From c60f657713f130d3accee7ceb303d7286b6180d5 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Wed, 18 Mar 2026 17:57:56 +0100 -Subject: [PATCH 31/44] persistentnetnamesconfig: Deprecate legacy - functionality - -The legacy solution to make NIC names persistent during the upgrade -is buggy and usually it's not used nowadays. Creation of link files -in the legacy solution breaks bonding configuration and also does not -handle interfaces in case of InfiniBand and RDMA. - -At this point, we plan to use of net naming scheme mandatory during -the upgrade (or at least the only solution for persistent NIC names). -Mark following symbols as deprecated: - * LEAPP_DISABLE_NET_NAMING_SCHEMES (envar) - * LEAPP_NO_NETWORK_RENAMING (envar) - * RenamedInterfaces (model) - * RenamedInterface (model) - -Also the RenamedInterface model had fields `rhel7_name` & `rhel8_name`, -which has not actually correspond to the real state. So the fields -have been renamed instantly to `original_name` & `new_name`. - -Also dropping the produce of InitrdInclude message in the actor as -the model is deprecated for a long time. - -We could possibly still think about reporting changed interface names, -but such a report should happen in the last (FirstBoot) phase when -the networking should be already fully initialized. Such a report -would be just a check whether net naming scheme works as expected. -But it's not considered at this very moment. ---- - docs/source/configuring-ipu/envars.md | 20 +++++++- - .../libraries-and-api/deprecations-list.md | 6 ++- - .../actors/persistentnetnamesconfig/actor.py | 19 ++++--- - .../libraries/persistentnetnamesconfig.py | 27 +++++----- - .../tests/test_persistentnetnamesconfig.py | 50 ++++++++++--------- - .../common/models/persistentnetnamesfacts.py | 21 ++++++-- - 6 files changed, 93 insertions(+), 50 deletions(-) - -diff --git a/docs/source/configuring-ipu/envars.md b/docs/source/configuring-ipu/envars.md -index 72d00634..9fa37f4f 100644 ---- a/docs/source/configuring-ipu/envars.md -+++ b/docs/source/configuring-ipu/envars.md -@@ -6,11 +6,21 @@ Below is a list of the general and development variables available. - ### General variables - - #### LEAPP_DISABLE_NET_NAMING_SCHEMES --On RHEL 8 to 9 upgrades, by default, net.naming-scheme is used to make network interface names immutable during the upgrade. In this case an extra RPM named `rhel-net-naming-sysattrs` is installed to the target system and target userspace container, providing the definitions of the "profiles" for net.naming-scheme. -+The `net.naming-scheme` kernel command line option is used by default to make -+network interface names immutable during the upgrade. -+In this case an extra RPM named `rhel-net-naming-sysattrs` (or `net-naming-sysattrs`) -+is installed to the target system and target userspace container, providing -+the definitions of the "profiles" for `net.naming-scheme`. - --If set to `0`, the "legacy" mechanism is used where leapp writes .link files to prevent interfaces being renamed -+If set to `0`, the legacy mechanism is used where leapp writes .link files to prevent interfaces being renamed - after booting to post-upgrade system. - -+```{warning} -+The variable is deprecated as it is a part of the legacy solution for handling -+NIC names during the upgrade. Current supported solution allows only using -+the `net.naming-scheme`. -+``` -+ - #### LEAPP_ENABLE_REPOS - Specify repositories (repoids) split by comma, that should be used during the in-place upgrade to the target system. It‘s overwritten automatically in case the --enablerepo option is used. It‘s recommended to use the --enablerepo option instead of the envar. - -@@ -26,6 +36,12 @@ If set to `1`, Leapp does not register the system into Red Hat Lightspeed automa - #### LEAPP_NO_NETWORK_RENAMING - If set to `1`, the actor responsible to handle NICs names ends without doing anything. The actor usually creates UDEV rules to preserve original NICs in case they are changed. However, in some cases it‘s not wanted and it leads in malfunction network configuration (e.g. in case the bonding is configured on the system). It‘s expected that NICs have to be handled manually if needed. - -+```{warning} -+The variable is deprecated as it is a part of the legacy solution for -+handling NIC names during the upgrade. Current supported solution allows only -+using of the `net.naming-scheme`. -+``` -+ - ##### LEAPP_NO_RHSM - If set to `1`, Leapp does not use Red Hat Subscription Management for the upgrade. It‘s equivalent to the --no-rhsm leapp option. - -diff --git a/docs/source/libraries-and-api/deprecations-list.md b/docs/source/libraries-and-api/deprecations-list.md -index 679bf489..a074ebc1 100644 ---- a/docs/source/libraries-and-api/deprecations-list.md -+++ b/docs/source/libraries-and-api/deprecations-list.md -@@ -13,7 +13,11 @@ framework, see {ref}`deprecation:list of the deprecated functionality in leapp`. - Only the versions in which a deprecation has been made are listed. - - ## Next release (till TODO date) --- Note: nothing new deprecated yet -+- Environment variables -+ - **`LEAPP_DISABLE_NET_NAMING_SCHEMES`** - The `net.naming-scheme` kernel argument provides much better alternative to the legacy solution enabled using this variable. Hence the legacy solution is deprecated together with this environment variable. -+ - **`LEAPP_NO_NETWORK_RENAMING`** - It becomes obsoleted by the solution based on `net.naming-scheme` which replaces the legacy solution based on created udev link files correcting NIC names during the upgrade. -+- Models: -+ - **`RenamedInterfaces`** - Information provided in this message is not always complete and it's not used since the `net.naming-scheme` kernel command line argument is set during the upgrade. - - ## v0.24.0 (till September 2026) - - Shared libraries -diff --git a/repos/system_upgrade/common/actors/persistentnetnamesconfig/actor.py b/repos/system_upgrade/common/actors/persistentnetnamesconfig/actor.py -index 2689d837..7a21b43a 100644 ---- a/repos/system_upgrade/common/actors/persistentnetnamesconfig/actor.py -+++ b/repos/system_upgrade/common/actors/persistentnetnamesconfig/actor.py -@@ -1,7 +1,6 @@ - from leapp.actors import Actor - from leapp.libraries.actor import persistentnetnamesconfig - from leapp.models import ( -- InitrdIncludes, - PersistentNetNamesFacts, - PersistentNetNamesFactsInitramfs, - RenamedInterfaces, -@@ -11,21 +10,27 @@ from leapp.tags import ApplicationsPhaseTag, IPUWorkflowTag - from leapp.utils.deprecation import suppress_deprecation - - --@suppress_deprecation(InitrdIncludes) -+@suppress_deprecation(RenamedInterfaces) - class PersistentNetNamesConfig(Actor): - """ - Generate udev persistent network naming configuration - -- This actor generates systemd-udevd link files for each physical ethernet interface present on RHEL-7 -- in case we notice that interface name differs on RHEL-8. Link file configuration will assign RHEL-7 version of -- a name. Actors produces list of interfaces which changed name between RHEL-7 and RHEL-8. -+ NOTE: This actor is deprecated and currently performs described actions -+ only if LEAPP_NO_NETWORK_RENAMING != 1 and LEAPP_DISABLE_NET_NAMING_SCHEMES == 1. -+ -+ This actor generates systemd-udevd link files for each physical network -+ interface present on the original system if the interface name differs -+ on the target OS. Link file configuration will assign original name that has -+ been detected on the source OS. -+ -+ Also produce list of interfaces which changed names during the upgrade -+ process. - """ - - name = 'persistentnetnamesconfig' - consumes = (PersistentNetNamesFacts, PersistentNetNamesFactsInitramfs) -- produces = (RenamedInterfaces, InitrdIncludes, TargetInitramfsTasks) -+ produces = (RenamedInterfaces, TargetInitramfsTasks) - tags = (ApplicationsPhaseTag, IPUWorkflowTag) -- initrd_files = [] - - def process(self): - persistentnetnamesconfig.process() -diff --git a/repos/system_upgrade/common/actors/persistentnetnamesconfig/libraries/persistentnetnamesconfig.py b/repos/system_upgrade/common/actors/persistentnetnamesconfig/libraries/persistentnetnamesconfig.py -index 189cd4d0..0618f090 100644 ---- a/repos/system_upgrade/common/actors/persistentnetnamesconfig/libraries/persistentnetnamesconfig.py -+++ b/repos/system_upgrade/common/actors/persistentnetnamesconfig/libraries/persistentnetnamesconfig.py -@@ -2,10 +2,9 @@ import errno - import os - import re - --from leapp.libraries.common.config import get_env, version -+from leapp.libraries.common.config import get_env - from leapp.libraries.stdlib import api - from leapp.models import ( -- InitrdIncludes, - PersistentNetNamesFacts, - PersistentNetNamesFactsInitramfs, - RenamedInterface, -@@ -37,18 +36,21 @@ def generate_link_file(interface): - return link_file - - --@suppress_deprecation(InitrdIncludes) -+@suppress_deprecation(RenamedInterfaces, RenamedInterface) - def process(): - are_net_schemes_enabled = get_env('LEAPP_DISABLE_NET_NAMING_SCHEMES', '0') != '1' -- is_upgrade_8to9 = version.get_target_major_version() == '9' - -- if are_net_schemes_enabled and is_upgrade_8to9: -- # For 8>9 we are using net.naming_scheme kernel arg by default - do not generate link files -+ if are_net_schemes_enabled: - msg = ('Skipping generation of .link files renaming NICs as net.naming-scheme ' -- '{LEAPP_DISABLE_NET_NAMING_SCHEMES != 1} is enabled and upgrade is 8>9') -- api.current_logger().info(msg) -+ '{LEAPP_DISABLE_NET_NAMING_SCHEMES != 1} is enabled.') -+ api.current_logger().debug(msg) - return - -+ api.current_logger().warning( -+ 'LEAPP_DISABLE_NET_NAMING_SCHEMES=1 - Using deprecated handling' -+ ' of network interface names by creating link files.' -+ ) -+ - if get_env('LEAPP_NO_NETWORK_RENAMING', '0') == '1': - api.current_logger().info( - 'Skipping handling of possibly renamed network interfaces: leapp executed with LEAPP_NO_NETWORK_RENAMING=1' -@@ -80,13 +82,13 @@ def process(): - ) - continue - -- if source_name != target_name and get_env('LEAPP_NO_NETWORK_RENAMING', '0') != '1': -+ if source_name != target_name: - api.current_logger().warning('Detected interface rename {} -> {}.'.format(source_name, target_name)) - -- if re.search('eth[0-9]+', iface.name) is not None: -+ if re.search('^eth[0-9]+$', iface.name) is not None: - api.current_logger().warning('Interface named using eth prefix, refusing to generate link file') -- renamed_interfaces.append(RenamedInterface(**{'rhel7_name': source_name, -- 'rhel8_name': target_name})) -+ renamed_interfaces.append(RenamedInterface(**{'original_name': source_name, -+ 'new_name': target_name})) - continue - - initrd_files.append(generate_link_file(iface)) -@@ -108,7 +110,6 @@ def process(): - api.current_logger().warning(msg) - - api.produce(RenamedInterfaces(renamed=renamed_interfaces)) -- api.produce(InitrdIncludes(files=initrd_files)) - # TODO: cover actor by tests in future. I am skipping writing of tests - # now as some refactoring and bugfixing related to this actor - # is planned already. -diff --git a/repos/system_upgrade/common/actors/persistentnetnamesconfig/tests/test_persistentnetnamesconfig.py b/repos/system_upgrade/common/actors/persistentnetnamesconfig/tests/test_persistentnetnamesconfig.py -index c584c7ea..b107612b 100644 ---- a/repos/system_upgrade/common/actors/persistentnetnamesconfig/tests/test_persistentnetnamesconfig.py -+++ b/repos/system_upgrade/common/actors/persistentnetnamesconfig/tests/test_persistentnetnamesconfig.py -@@ -7,7 +7,8 @@ from leapp.libraries.actor import persistentnetnamesconfig - from leapp.libraries.common.config import mock_configs - from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked, produce_mocked - from leapp.models import ( -- InitrdIncludes, -+ EnvVar, -+ IPUConfig, - Interface, - PCIAddress, - PersistentNetNamesFacts, -@@ -19,6 +20,19 @@ from leapp.models import ( - TEST_DIR = os.path.dirname(os.path.abspath(__file__)) - CUR_DIR = "" - -+CONFIG_DISABLED_NAMING_SCHEMES = IPUConfig( -+ leapp_env_vars=[ -+ EnvVar(name='LEAPP_DEVEL', value='0'), -+ EnvVar(name='LEAPP_DISABLE_NET_NAMING_SCHEMES', value='1'), -+ ], -+ os_release=mock_configs.CONFIG.os_release, -+ version=mock_configs.CONFIG.version, -+ architecture=mock_configs.CONFIG.architecture, -+ kernel=mock_configs.CONFIG.kernel, -+ supported_upgrade_paths=mock_configs.CONFIG.supported_upgrade_paths, -+ distro=mock_configs.CONFIG.distro -+) -+ - - @pytest.fixture - def adjust_cwd(): -@@ -56,12 +70,10 @@ def test_identical(current_actor_context): - interfaces = generate_interfaces(4) - current_actor_context.feed(PersistentNetNamesFacts(interfaces=interfaces)) - current_actor_context.feed(PersistentNetNamesFactsInitramfs(interfaces=interfaces)) -- current_actor_context.run(config_model=mock_configs.CONFIG) -+ current_actor_context.run(config_model=CONFIG_DISABLED_NAMING_SCHEMES) - - renamed_interfaces = current_actor_context.consume(RenamedInterfaces)[0] -- initrd_files = current_actor_context.consume(InitrdIncludes)[0] - t_initrafms_tasks = current_actor_context.consume(TargetInitramfsTasks)[0] -- assert initrd_files.files == t_initrafms_tasks.include_files - assert not renamed_interfaces.renamed - assert not t_initrafms_tasks.include_files - -@@ -73,12 +85,10 @@ def test_renamed_single_noneth(monkeypatch, current_actor_context): - current_actor_context.feed(PersistentNetNamesFacts(interfaces=interfaces)) - interfaces[0].name = 'n4' - current_actor_context.feed(PersistentNetNamesFactsInitramfs(interfaces=interfaces)) -- current_actor_context.run(config_model=mock_configs.CONFIG) -+ current_actor_context.run(config_model=CONFIG_DISABLED_NAMING_SCHEMES) - - renamed_interfaces = current_actor_context.consume(RenamedInterfaces)[0] -- initrd_files = current_actor_context.consume(InitrdIncludes)[0] - t_initrafms_tasks = current_actor_context.consume(TargetInitramfsTasks)[0] -- assert initrd_files.files == t_initrafms_tasks.include_files - assert not renamed_interfaces.renamed - assert len(t_initrafms_tasks.include_files) == 1 - assert '/etc/systemd/network/10-leapp-n0.link' in t_initrafms_tasks.include_files -@@ -92,12 +102,10 @@ def test_renamed_swap_noneth(monkeypatch, current_actor_context): - interfaces[0].name = 'n3' - interfaces[3].name = 'n0' - current_actor_context.feed(PersistentNetNamesFactsInitramfs(interfaces=interfaces)) -- current_actor_context.run(config_model=mock_configs.CONFIG) -+ current_actor_context.run(config_model=CONFIG_DISABLED_NAMING_SCHEMES) - - renamed_interfaces = current_actor_context.consume(RenamedInterfaces)[0] -- initrd_files = current_actor_context.consume(InitrdIncludes)[0] - t_initrafms_tasks = current_actor_context.consume(TargetInitramfsTasks)[0] -- assert initrd_files.files == t_initrafms_tasks.include_files - assert not renamed_interfaces.renamed - assert len(t_initrafms_tasks.include_files) == 2 - assert '/etc/systemd/network/10-leapp-n0.link' in t_initrafms_tasks.include_files -@@ -113,15 +121,13 @@ def test_renamed_single_eth(monkeypatch, current_actor_context): - current_actor_context.feed(PersistentNetNamesFacts(interfaces=interfaces)) - interfaces[0].name = 'eth4' - current_actor_context.feed(PersistentNetNamesFactsInitramfs(interfaces=interfaces)) -- current_actor_context.run(config_model=mock_configs.CONFIG) -+ current_actor_context.run(config_model=CONFIG_DISABLED_NAMING_SCHEMES) - - renamed_interfaces = current_actor_context.consume(RenamedInterfaces)[0] -- initrd_files = current_actor_context.consume(InitrdIncludes)[0] - t_initrafms_tasks = current_actor_context.consume(TargetInitramfsTasks)[0] -- assert initrd_files.files == t_initrafms_tasks.include_files - assert len(renamed_interfaces.renamed) == 1 -- assert renamed_interfaces.renamed[0].rhel7_name == 'eth0' -- assert renamed_interfaces.renamed[0].rhel8_name == 'eth4' -+ assert renamed_interfaces.renamed[0].original_name == 'eth0' -+ assert renamed_interfaces.renamed[0].new_name == 'eth4' - assert not t_initrafms_tasks.include_files - - -@@ -135,18 +141,16 @@ def test_renamed_swap_eth(monkeypatch, current_actor_context): - interfaces[0].name = 'eth3' - interfaces[3].name = 'eth0' - current_actor_context.feed(PersistentNetNamesFactsInitramfs(interfaces=interfaces)) -- current_actor_context.run(config_model=mock_configs.CONFIG) -+ current_actor_context.run(config_model=CONFIG_DISABLED_NAMING_SCHEMES) - - renamed_interfaces = current_actor_context.consume(RenamedInterfaces)[0] -- initrd_files = current_actor_context.consume(InitrdIncludes)[0] - t_initrafms_tasks = current_actor_context.consume(TargetInitramfsTasks)[0] -- assert initrd_files.files == t_initrafms_tasks.include_files - assert len(renamed_interfaces.renamed) == 2 - for interface in renamed_interfaces.renamed: -- if interface.rhel7_name == 'eth0': -- assert interface.rhel8_name == 'eth3' -- elif interface.rhel7_name == 'eth3': -- assert interface.rhel8_name == 'eth0' -+ if interface.original_name == 'eth0': -+ assert interface.new_name == 'eth3' -+ elif interface.original_name == 'eth3': -+ assert interface.new_name == 'eth0' - assert not t_initrafms_tasks.include_files - - -@@ -179,7 +183,7 @@ def test_bz_1899455_crash_iface(monkeypatch, adjust_cwd): - monkeypatch.setattr(persistentnetnamesconfig.api, 'produce', produce_mocked()) - persistentnetnamesconfig.process() - -- for prod_models in [RenamedInterfaces, InitrdIncludes, TargetInitramfsTasks]: -+ for prod_models in [RenamedInterfaces, TargetInitramfsTasks]: - any(isinstance(i, prod_models) for i in persistentnetnamesconfig.api.produce.model_instances) - assert any('Some network devices' in x for x in persistentnetnamesconfig.api.current_logger.warnmsg) - -diff --git a/repos/system_upgrade/common/models/persistentnetnamesfacts.py b/repos/system_upgrade/common/models/persistentnetnamesfacts.py -index 3c71cdcd..e5cf979b 100644 ---- a/repos/system_upgrade/common/models/persistentnetnamesfacts.py -+++ b/repos/system_upgrade/common/models/persistentnetnamesfacts.py -@@ -1,5 +1,6 @@ - from leapp.models import fields, Model - from leapp.topics import SystemInfoTopic -+from leapp.utils.deprecation import deprecated - - - class PCIAddress(Model): -@@ -82,25 +83,37 @@ class PersistentNetNamesFactsInitramfs(PersistentNetNamesFacts): - pass - - -+@deprecated( -+ since="2026-03-18", -+ message=( -+ "Information provided in this message is not always complete and it's" -+ " not used nowadays when net naming scheme is set during the upgrade." -+ ) -+) - class RenamedInterface(Model): - """ - Provide original and new name of the network interface when renamed - """ - topic = SystemInfoTopic - -- # TODO(pstodulk) deprecate these fields and replace them by new ones -- # or deprecate the model completely. -- rhel7_name = fields.String() -+ original_name = fields.String() - """ - Original interface name. - """ - -- rhel8_name = fields.String() -+ new_name = fields.String() - """ - New interface name. - """ - - -+@deprecated( -+ since="2026-03-18", -+ message=( -+ "Information provided in this message is not always complete and it's" -+ " not used nowadays when net naming scheme is set during the upgrade." -+ ) -+) - class RenamedInterfaces(Model): - """ - Provide list of renamed network interfaces --- -2.53.0 - diff --git a/0032-persistentnetnamesdisable-Disable-net.ifnames-correc.patch b/0032-persistentnetnamesdisable-Disable-net.ifnames-correc.patch deleted file mode 100644 index a857396..0000000 --- a/0032-persistentnetnamesdisable-Disable-net.ifnames-correc.patch +++ /dev/null @@ -1,124 +0,0 @@ -From 0872576049715c7a909379d5de5cc53bab3a408a Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Fri, 20 Mar 2026 17:19:20 +0100 -Subject: [PATCH 32/44] persistentnetnamesdisable: Disable net.ifnames - correctly for single eth - -Original solution disabled net.ifnames only for the target kernel -bootloader entry. But the upgrade initramfs stayed unhandled. -So it could happen that a "false positive" detection of changed -network interface names could been detected during the upgrade process -even when the target system booted again with "eth0" net interface -(because of net.ifnames=0). Set the parameter also for the upgrade -environment to behave same as on the target system. - -Also, actor originally produced just the KernelCmdlineArg msg, -which is nowadays considered kind of obsoleted for this purpose -- still valid, but obsoleted. So produce UpgradeKernelCmdlineArgTasks -and TargetKernelCmdlineArgTasks msgs instead. ---- - .../tests/test_persistentnetnamesconfig.py | 2 +- - .../common/actors/persistentnetnamesdisable/actor.py | 8 ++++++-- - .../libraries/persistentnetnamesdisable.py | 11 +++++++++-- - .../tests/test_persistentnetnamesdisable.py | 10 +++++++++- - 4 files changed, 25 insertions(+), 6 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/persistentnetnamesconfig/tests/test_persistentnetnamesconfig.py b/repos/system_upgrade/common/actors/persistentnetnamesconfig/tests/test_persistentnetnamesconfig.py -index b107612b..f2519d77 100644 ---- a/repos/system_upgrade/common/actors/persistentnetnamesconfig/tests/test_persistentnetnamesconfig.py -+++ b/repos/system_upgrade/common/actors/persistentnetnamesconfig/tests/test_persistentnetnamesconfig.py -@@ -8,8 +8,8 @@ from leapp.libraries.common.config import mock_configs - from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked, produce_mocked - from leapp.models import ( - EnvVar, -- IPUConfig, - Interface, -+ IPUConfig, - PCIAddress, - PersistentNetNamesFacts, - PersistentNetNamesFactsInitramfs, -diff --git a/repos/system_upgrade/common/actors/persistentnetnamesdisable/actor.py b/repos/system_upgrade/common/actors/persistentnetnamesdisable/actor.py -index 15c43141..741e2c86 100644 ---- a/repos/system_upgrade/common/actors/persistentnetnamesdisable/actor.py -+++ b/repos/system_upgrade/common/actors/persistentnetnamesdisable/actor.py -@@ -1,6 +1,10 @@ - from leapp.actors import Actor - from leapp.libraries.actor import persistentnetnamesdisable --from leapp.models import KernelCmdlineArg, PersistentNetNamesFacts -+from leapp.models import ( -+ PersistentNetNamesFacts, -+ TargetKernelCmdlineArgTasks, -+ UpgradeKernelCmdlineArgTasks -+) - from leapp.reporting import Report - from leapp.tags import ChecksPhaseTag, IPUWorkflowTag - -@@ -12,7 +16,7 @@ class PersistentNetNamesDisable(Actor): - - name = 'persistentnetnamesdisable' - consumes = (PersistentNetNamesFacts,) -- produces = (KernelCmdlineArg, Report) -+ produces = (Report, TargetKernelCmdlineArgTasks, UpgradeKernelCmdlineArgTasks) - tags = (ChecksPhaseTag, IPUWorkflowTag) - - def process(self): -diff --git a/repos/system_upgrade/common/actors/persistentnetnamesdisable/libraries/persistentnetnamesdisable.py b/repos/system_upgrade/common/actors/persistentnetnamesdisable/libraries/persistentnetnamesdisable.py -index 0d1a90e2..38eef133 100644 ---- a/repos/system_upgrade/common/actors/persistentnetnamesdisable/libraries/persistentnetnamesdisable.py -+++ b/repos/system_upgrade/common/actors/persistentnetnamesdisable/libraries/persistentnetnamesdisable.py -@@ -3,7 +3,12 @@ import re - from leapp import reporting - from leapp.libraries.common.config.version import get_target_major_version - from leapp.libraries.stdlib import api --from leapp.models import KernelCmdlineArg, PersistentNetNamesFacts -+from leapp.models import ( -+ KernelCmdlineArg, -+ PersistentNetNamesFacts, -+ TargetKernelCmdlineArgTasks, -+ UpgradeKernelCmdlineArgTasks -+) - from leapp.reporting import create_report - - -@@ -29,7 +34,9 @@ def disable_persistent_naming(): - "Single eth0 network interface detected." - " Appending 'net.ifnames=0' for the target system kernel commandline" - ) -- api.produce(KernelCmdlineArg(**{'key': 'net.ifnames', 'value': '0'})) -+ k_arg = KernelCmdlineArg(key='net.ifnames', value='0') -+ api.produce(UpgradeKernelCmdlineArgTasks(to_add=[k_arg])) -+ api.produce(TargetKernelCmdlineArgTasks(to_add=[k_arg])) - - - def report_ethX_ifaces(): -diff --git a/repos/system_upgrade/common/actors/persistentnetnamesdisable/tests/test_persistentnetnamesdisable.py b/repos/system_upgrade/common/actors/persistentnetnamesdisable/tests/test_persistentnetnamesdisable.py -index 2369e80f..81b5a28c 100644 ---- a/repos/system_upgrade/common/actors/persistentnetnamesdisable/tests/test_persistentnetnamesdisable.py -+++ b/repos/system_upgrade/common/actors/persistentnetnamesdisable/tests/test_persistentnetnamesdisable.py -@@ -1,7 +1,13 @@ - import pytest - - from leapp.libraries.actor import persistentnetnamesdisable --from leapp.models import Interface, KernelCmdlineArg, PCIAddress, PersistentNetNamesFacts -+from leapp.models import ( -+ Interface, -+ PCIAddress, -+ PersistentNetNamesFacts, -+ TargetKernelCmdlineArgTasks, -+ UpgradeKernelCmdlineArgTasks -+) - from leapp.reporting import Report - from leapp.snactor.fixture import current_actor_context - from leapp.utils.report import is_inhibitor -@@ -51,6 +57,8 @@ def test_actor_single_eth0(current_actor_context): - current_actor_context.feed(PersistentNetNamesFacts(interfaces=interface)) - current_actor_context.run() - assert not current_actor_context.consume(Report) -+ assert current_actor_context.consume(UpgradeKernelCmdlineArgTasks) -+ assert current_actor_context.consume(TargetKernelCmdlineArgTasks) - - - @pytest.mark.parametrize( --- -2.53.0 - diff --git a/0033-persistentnetnamesdisable-Mention-net.naming-scheme-.patch b/0033-persistentnetnamesdisable-Mention-net.naming-scheme-.patch deleted file mode 100644 index 792a1fe..0000000 --- a/0033-persistentnetnamesdisable-Mention-net.naming-scheme-.patch +++ /dev/null @@ -1,303 +0,0 @@ -From 806e65d67cac9e16c0341f366133ae3c14844fcd Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Wed, 25 Mar 2026 15:57:37 +0100 -Subject: [PATCH 33/44] persistentnetnamesdisable: Mention net.naming-scheme in - report - -Since RHEL 8 it's possible to specifcy net.naming-scheme to have -a consistent NIC names across RHEL major releases. When a specific -net.naming-scheme is not specified in kernel cmdline, suggest people -this option as well. ---- - .../actors/persistentnetnamesdisable/actor.py | 15 +++- - .../libraries/persistentnetnamesdisable.py | 86 +++++++++++++++---- - .../tests/test_persistentnetnamesdisable.py | 78 ++++++++++++++++- - 3 files changed, 158 insertions(+), 21 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/persistentnetnamesdisable/actor.py b/repos/system_upgrade/common/actors/persistentnetnamesdisable/actor.py -index 741e2c86..0bcc3827 100644 ---- a/repos/system_upgrade/common/actors/persistentnetnamesdisable/actor.py -+++ b/repos/system_upgrade/common/actors/persistentnetnamesdisable/actor.py -@@ -1,6 +1,7 @@ - from leapp.actors import Actor - from leapp.libraries.actor import persistentnetnamesdisable - from leapp.models import ( -+ KernelCmdline, - PersistentNetNamesFacts, - TargetKernelCmdlineArgTasks, - UpgradeKernelCmdlineArgTasks -@@ -11,11 +12,21 @@ from leapp.tags import ChecksPhaseTag, IPUWorkflowTag - - class PersistentNetNamesDisable(Actor): - """ -- Disable systemd-udevd persistent network naming on machine with single eth0 NIC -+ Check whether the system has any (physical) NICs with kernel naming (ethX) -+ -+ The kernel naming is in general unstable - there is no guarantee of persistent -+ NIC names between reboots, so eth0 can becaome eth3 and vice versa. If the -+ system has more than one physical network interface, the kernel naming must -+ not be used otherwise the upgrade is inhibited. The report contains remediation -+ hints for user to resolve the problem before the upgrade. -+ -+ On systems with only one physical network interface with eth0 NIC, register -+ task to disable systemd-udevd persistent network naming (set `net.ifnames=0` -+ on kernel cmdline for the upgrade environment and the upgraded system. - """ - - name = 'persistentnetnamesdisable' -- consumes = (PersistentNetNamesFacts,) -+ consumes = (PersistentNetNamesFacts, KernelCmdline) - produces = (Report, TargetKernelCmdlineArgTasks, UpgradeKernelCmdlineArgTasks) - tags = (ChecksPhaseTag, IPUWorkflowTag) - -diff --git a/repos/system_upgrade/common/actors/persistentnetnamesdisable/libraries/persistentnetnamesdisable.py b/repos/system_upgrade/common/actors/persistentnetnamesdisable/libraries/persistentnetnamesdisable.py -index 38eef133..0a4209b8 100644 ---- a/repos/system_upgrade/common/actors/persistentnetnamesdisable/libraries/persistentnetnamesdisable.py -+++ b/repos/system_upgrade/common/actors/persistentnetnamesdisable/libraries/persistentnetnamesdisable.py -@@ -1,9 +1,11 @@ - import re - - from leapp import reporting --from leapp.libraries.common.config.version import get_target_major_version -+from leapp.exceptions import StopActorExecutionError -+from leapp.libraries.common.config.version import get_source_major_version, get_target_major_version - from leapp.libraries.stdlib import api - from leapp.models import ( -+ KernelCmdline, - KernelCmdlineArg, - PersistentNetNamesFacts, - TargetKernelCmdlineArgTasks, -@@ -29,6 +31,40 @@ def single_eth0(interfaces): - return len(interfaces) == 1 and interfaces[0].name == 'eth0' - - -+def is_kernel_arg_present(key, value=None): -+ """ -+ Return True if requested argument is set in kernel cmdline. Return False otherwise. -+ -+ If the `value` is specified, check also whether the specific value is set. -+ The function consumes :class:`KernelCmdline`. -+ -+ :param key: The kernel argument to search for -+ :type key: str -+ :param value: If string is specified, check for a specific string as well. -+ :type value: str|None -+ :rtype: bool -+ """ -+ # NOTE(pstodulk): with small update a possible candidate to move into the -+ # kernel shared library. For now, keeping it just in this actor. -+ k_cmdline = next(api.consume(KernelCmdline), None) -+ if not k_cmdline: -+ # NOTE(pstodulk): this hypothetical situation, skipping coverage by -+ # unit tests -+ raise StopActorExecutionError( -+ message='Missing information about current kernel command line.', -+ details={ -+ 'details': 'Missing the KernelCmdline message.' -+ } -+ ) -+ -+ for k_arg in k_cmdline.parameters: -+ if k_arg.key != key: -+ continue -+ if value is None or k_arg.value == value: -+ return True -+ return False -+ -+ - def disable_persistent_naming(): - api.current_logger().info( - "Single eth0 network interface detected." -@@ -40,27 +76,30 @@ def disable_persistent_naming(): - - - def report_ethX_ifaces(): -- report_entries = [ -- reporting.Title('Unsupported network configuration'), -- reporting.Summary( -- 'Detected multiple physical network interfaces where one or more' -- ' use kernel naming (e.g. eth0). Upgrade process cannot continue' -- ' because stability of names can not be guaranteed.' -- ), -+ url_title_kb = 'How to Perform an In-Place Upgrade when Using Kernel-Assigned NIC Names' -+ hint_text = f'Rename all ethX network interfaces following the "{url_title_kb}" solution article.' -+ report_external_links = [ - reporting.ExternalLink( -- title='How to Perform an In-Place Upgrade when Using Kernel-Assigned NIC Names', -+ title=url_title_kb, - url='https://access.redhat.com/solutions/4067471' -- ), -- reporting.Remediation( -- hint='Rename all ethX network interfaces following the attached KB solution article.' -- ), -- reporting.Severity(reporting.Severity.HIGH), -- reporting.Groups([reporting.Groups.NETWORK]), -- reporting.Groups([reporting.Groups.INHIBITOR]) -+ ) - ] -+ if not is_kernel_arg_present('net.naming-scheme') and not is_kernel_arg_present('net.ifnames', '0'): -+ hint_text += ( -+ ' If the detected ethX interfaces are not manually configured, it is' -+ ' possible that new names were not assigned due to a naming conflict' -+ ' in the current `net.naming-scheme`. This can be resolved by' -+ ' configuring a newer naming scheme via the kernel argument.' -+ ' For more information, see "Implementing consistent network interface naming".' -+ ) - -+ # NOTE(pstodulk): the link is covered for RHEL 8, 9, 10 -+ report_external_links.append(reporting.ExternalLink( -+ title='Implementing consistent network interface naming', -+ url='https://red.ht/rhel-{}-consistent-nic-naming'.format(get_source_major_version()) -+ )) - if get_target_major_version() == '9': -- report_entries.append( -+ report_external_links.append( - reporting.ExternalLink( - title='RHEL 8 to RHEL 9: inplace upgrade fails at ' - '"Network configuration for unsupported device types detected"', -@@ -68,6 +107,19 @@ def report_ethX_ifaces(): - ) - ) - -+ report_entries = [ -+ reporting.Title('Unsupported network configuration'), -+ reporting.Summary( -+ 'Detected multiple physical network interfaces where one or more' -+ ' use kernel naming (e.g. eth0). Upgrade process cannot continue' -+ ' because stability of names can not be guaranteed.' -+ ), -+ reporting.Remediation(hint=hint_text), -+ reporting.Severity(reporting.Severity.HIGH), -+ reporting.Groups([reporting.Groups.NETWORK]), -+ reporting.Groups([reporting.Groups.INHIBITOR]) -+ ] + report_external_links -+ - create_report(report_entries) - - -diff --git a/repos/system_upgrade/common/actors/persistentnetnamesdisable/tests/test_persistentnetnamesdisable.py b/repos/system_upgrade/common/actors/persistentnetnamesdisable/tests/test_persistentnetnamesdisable.py -index 81b5a28c..4b8669bb 100644 ---- a/repos/system_upgrade/common/actors/persistentnetnamesdisable/tests/test_persistentnetnamesdisable.py -+++ b/repos/system_upgrade/common/actors/persistentnetnamesdisable/tests/test_persistentnetnamesdisable.py -@@ -1,8 +1,11 @@ - import pytest - - from leapp.libraries.actor import persistentnetnamesdisable -+from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked - from leapp.models import ( - Interface, -+ KernelCmdline, -+ KernelCmdlineArg, - PCIAddress, - PersistentNetNamesFacts, - TargetKernelCmdlineArgTasks, -@@ -65,6 +68,7 @@ def test_actor_single_eth0(current_actor_context): - 'target_version', ['9', '10'] - ) - def test_actor_more_ethX(monkeypatch, current_actor_context, target_version): -+ monkeypatch.setattr(persistentnetnamesdisable, 'get_source_major_version', lambda: str(int(target_version) - 1)) - monkeypatch.setattr(persistentnetnamesdisable, 'get_target_major_version', lambda: target_version) - pci1 = PCIAddress(domain="0000", bus="3e", function="00", device="PCI bridge") - pci2 = PCIAddress(domain="0000", bus="3d", function="00", device="Serial controller") -@@ -84,7 +88,10 @@ def test_actor_more_ethX(monkeypatch, current_actor_context, target_version): - pci_info=pci2, - devpath="/devices/hidraw/hidraw0") - ] -- current_actor_context.feed(PersistentNetNamesFacts(interfaces=interface)) -+ current_actor_context.feed( -+ PersistentNetNamesFacts(interfaces=interface), -+ KernelCmdline(parameters=[KernelCmdlineArg(key='what', value='ever')]) -+ ) - current_actor_context.run() - - report_fields = current_actor_context.consume(Report)[0].report -@@ -122,6 +129,7 @@ def test_actor_single_int_not_ethX(current_actor_context): - 'target_version', ['9', '10'] - ) - def test_actor_ethX_and_not_ethX(monkeypatch, current_actor_context, target_version): -+ monkeypatch.setattr(persistentnetnamesdisable, 'get_source_major_version', lambda: str(int(target_version) - 1)) - monkeypatch.setattr(persistentnetnamesdisable, 'get_target_major_version', lambda: target_version) - pci1 = PCIAddress(domain="0000", bus="3e", function="00", device="PCI bridge") - pci2 = PCIAddress(domain="0000", bus="3d", function="00", device="Serial controller") -@@ -141,7 +149,10 @@ def test_actor_ethX_and_not_ethX(monkeypatch, current_actor_context, target_vers - pci_info=pci2, - devpath="/devices/hidraw/hidraw0") - ] -- current_actor_context.feed(PersistentNetNamesFacts(interfaces=interface)) -+ current_actor_context.feed( -+ PersistentNetNamesFacts(interfaces=interface), -+ KernelCmdline(parameters=[KernelCmdlineArg(key='what', value='ever')]) -+ ) - current_actor_context.run() - assert current_actor_context.consume(Report) - -@@ -158,3 +169,66 @@ def test_actor_ethX_and_not_ethX(monkeypatch, current_actor_context, target_vers - assert rhel8to9_present - else: - assert not rhel8to9_present -+ -+ -+@pytest.mark.parametrize(('result_expected', 'key', 'value'), ( -+ (True, 'net.ifnames', None), -+ (True, 'net.ifnames', '0'), -+ (False, 'net.ifname', None), -+ (False, 'inet.ifnames', None), -+ (False, 'missing', None), -+ (False, 'missing', 'whatever'), -+ (False, 'net.ifnames', '1'), -+)) -+def test_is_kernel_arg_present(monkeypatch, result_expected, key, value): -+ k_args = [ -+ KernelCmdlineArg(key='Foo', value='0'), -+ KernelCmdlineArg(key='net.ifnames', value='0'), -+ KernelCmdlineArg(key='Something', value='None'), -+ ] -+ curr_actor_mocked = CurrentActorMocked( -+ msgs=[KernelCmdline(parameters=k_args)] -+ ) -+ monkeypatch.setattr(persistentnetnamesdisable.api, 'current_actor', curr_actor_mocked) -+ assert result_expected is persistentnetnamesdisable.is_kernel_arg_present(key, value) -+ -+ -+@pytest.mark.parametrize(('naming_expected', 'k_args'), ( -+ (False, [KernelCmdlineArg(key='net.ifnames', value='0')]), -+ (True, [KernelCmdlineArg(key='net.ifnames', value='1')]), -+ (True, [KernelCmdlineArg(key='net.naming-scheme-foo', value='rhel-8.10')]), -+ ( -+ # NOTE(pstodulk): this is kind of nonsense, but let's test it -+ False, -+ [ -+ KernelCmdlineArg(key='net.naming-scheme', value='rhel-8.10'), -+ KernelCmdlineArg(key='net.ifnames', value='0'), -+ ] -+ ), -+ (False, [KernelCmdlineArg(key='net.naming-scheme', value='rhel-8.10')]), -+ (False, [KernelCmdlineArg(key='net.naming-scheme', value='rhel-9.10')]), -+)) -+@pytest.mark.parametrize('src_ver', ('8.10', '9.8', '10.6')) -+def test_report_ethx_ifaces_scheme(monkeypatch, naming_expected, src_ver, k_args): -+ _v_split = src_ver.split('.') -+ dst_ver = '{}.{}'.format(int(_v_split[0]) + 1, _v_split[1]) -+ curr_actor_mocked = CurrentActorMocked( -+ msgs=[KernelCmdline(parameters=k_args)], -+ src_ver=src_ver, -+ dst_ver=dst_ver -+ ) -+ monkeypatch.setattr(persistentnetnamesdisable, 'create_report', create_report_mocked()) -+ monkeypatch.setattr(persistentnetnamesdisable.api, 'current_actor', curr_actor_mocked) -+ -+ persistentnetnamesdisable.report_ethX_ifaces() -+ assert persistentnetnamesdisable.create_report.called -+ report = persistentnetnamesdisable.create_report.reports[0] -+ -+ if naming_expected: -+ url = 'https://red.ht/rhel-{}-consistent-nic-naming'.format(_v_split[0]) -+ assert any(url == link['url'] for link in report['detail']['external']) -+ assert 'net.naming-scheme' in report['detail']['remediations'][0]['context'] -+ else: -+ url_str = 'consistent-nic-naming' -+ assert not any(url_str in link['url'] for link in report['detail']['external']) -+ assert 'net.naming-scheme' not in report['detail']['remediations'][0]['context'] --- -2.53.0 - diff --git a/0034-fix-quoting-in-list-representation-of-commands.patch b/0034-fix-quoting-in-list-representation-of-commands.patch deleted file mode 100644 index 42f104b..0000000 --- a/0034-fix-quoting-in-list-representation-of-commands.patch +++ /dev/null @@ -1,46 +0,0 @@ -From 0706cb54f4d1ba953de5e348cb03f9553b952669 Mon Sep 17 00:00:00 2001 -From: Peter Mocary -Date: Tue, 17 Mar 2026 13:01:17 +0100 -Subject: [PATCH 34/44] fix quoting in list representation of commands - -Removed redundant quotes included due to incorrect handling of command -conversion to string representation in leapp framework's reporting -module. The reporting module is fixed by RHEL-156521 and this patch is a -followup that fixes usage of the module so that the commands are -propagated to the leapp-report.txt correctly. - -Jira: RHEL-155517, RHEL-155515 ---- - .../actors/checkdnfpluginpath/libraries/checkdnfpluginpath.py | 2 +- - repos/system_upgrade/common/actors/checkrootsymlinks/actor.py | 2 +- - 2 files changed, 2 insertions(+), 2 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/checkdnfpluginpath/libraries/checkdnfpluginpath.py b/repos/system_upgrade/common/actors/checkdnfpluginpath/libraries/checkdnfpluginpath.py -index ce705361..be169e7e 100644 ---- a/repos/system_upgrade/common/actors/checkdnfpluginpath/libraries/checkdnfpluginpath.py -+++ b/repos/system_upgrade/common/actors/checkdnfpluginpath/libraries/checkdnfpluginpath.py -@@ -21,7 +21,7 @@ def check_dnf_pluginpath(dnf_pluginpath_detected): - reporting.Remediation( - hint='Remove or comment out the pluginpath option in the DNF ' - 'configuration file to be able to upgrade the system', -- commands=[['sed', '-i', '\'s/^pluginpath[[:space:]]*=/#pluginpath=/\'', DNF_CONFIG_PATH]], -+ commands=[['sed', '-i', 's/^pluginpath[[:space:]]*=/#pluginpath=/', DNF_CONFIG_PATH]], - ), - reporting.Severity(reporting.Severity.HIGH), - reporting.Groups([reporting.Groups.INHIBITOR]), -diff --git a/repos/system_upgrade/common/actors/checkrootsymlinks/actor.py b/repos/system_upgrade/common/actors/checkrootsymlinks/actor.py -index 7b89bf7a..bee672bf 100644 ---- a/repos/system_upgrade/common/actors/checkrootsymlinks/actor.py -+++ b/repos/system_upgrade/common/actors/checkrootsymlinks/actor.py -@@ -55,7 +55,7 @@ class CheckRootSymlinks(Actor): - os.path.relpath(item.target, '/'), - os.path.join('/', item.name)]) - commands.append(command) -- rem_commands = [['sh', '-c', '"{}"'.format(' && '.join(commands))]] -+ rem_commands = [['sh', '-c', '{}'.format(' && '.join(commands))]] - # Generate reports about non-utf8 absolute links presence - nonutf_count = len(absolute_links_nonutf) - if nonutf_count > 0: --- -2.53.0 - diff --git a/0035-fixup-fix-quoting-in-list-representation-of-commands.patch b/0035-fixup-fix-quoting-in-list-representation-of-commands.patch deleted file mode 100644 index 03335d6..0000000 --- a/0035-fixup-fix-quoting-in-list-representation-of-commands.patch +++ /dev/null @@ -1,116 +0,0 @@ -From a71fb36ca529b323a904adc2e9048a93074bf723 Mon Sep 17 00:00:00 2001 -From: Peter Mocary -Date: Wed, 25 Mar 2026 18:01:42 +0100 -Subject: [PATCH 35/44] fixup! fix quoting in list representation of commands - ---- - packaging/leapp-repository.spec | 2 +- - .../common/actors/checkrootsymlinks/actor.py | 6 +++--- - .../libraries/checkyumpluginsenabled.py | 2 +- - .../common/actors/verifydialogs/libraries/verifydialogs.py | 7 ++++--- - .../actors/inhibitcgroupsv1/libraries/inhibitcgroupsv1.py | 6 +++--- - .../actors/inhibitcgroupsv1/tests/test_inhibitcgroupsv1.py | 4 ++-- - 6 files changed, 14 insertions(+), 13 deletions(-) - -diff --git a/packaging/leapp-repository.spec b/packaging/leapp-repository.spec -index 97bc261a..3ffafafa 100644 ---- a/packaging/leapp-repository.spec -+++ b/packaging/leapp-repository.spec -@@ -120,7 +120,7 @@ Requires: leapp-repository-dependencies = %{leapp_repo_deps} - - # IMPORTANT: this is capability provided by the leapp framework rpm. - # Check that 'version' instead of the real framework rpm version. --Requires: leapp-framework >= 6.2, leapp-framework < 7 -+Requires: leapp-framework >= 6.4, leapp-framework < 7 - - # Since we provide sub-commands for the leapp utility, we expect the leapp - # tool to be installed as well. -diff --git a/repos/system_upgrade/common/actors/checkrootsymlinks/actor.py b/repos/system_upgrade/common/actors/checkrootsymlinks/actor.py -index bee672bf..2e805542 100644 ---- a/repos/system_upgrade/common/actors/checkrootsymlinks/actor.py -+++ b/repos/system_upgrade/common/actors/checkrootsymlinks/actor.py -@@ -52,10 +52,10 @@ class CheckRootSymlinks(Actor): - for item in absolute_links: - command = ' '.join(['ln', - '-snf', -- os.path.relpath(item.target, '/'), -- os.path.join('/', item.name)]) -+ f"'{os.path.relpath(item.target, '/')}'", -+ f"'{os.path.join('/', item.name)}'"]) - commands.append(command) -- rem_commands = [['sh', '-c', '{}'.format(' && '.join(commands))]] -+ rem_commands = [['bash', '-c', '{}'.format(' && '.join(commands))]] - # Generate reports about non-utf8 absolute links presence - nonutf_count = len(absolute_links_nonutf) - if nonutf_count > 0: -diff --git a/repos/system_upgrade/common/actors/checkyumpluginsenabled/libraries/checkyumpluginsenabled.py b/repos/system_upgrade/common/actors/checkyumpluginsenabled/libraries/checkyumpluginsenabled.py -index 87ff6511..9afa51ac 100644 ---- a/repos/system_upgrade/common/actors/checkyumpluginsenabled/libraries/checkyumpluginsenabled.py -+++ b/repos/system_upgrade/common/actors/checkyumpluginsenabled/libraries/checkyumpluginsenabled.py -@@ -53,7 +53,7 @@ def check_required_dnf_plugins_enabled(pkg_manager_info): - .format(dnf_conf_path, plugin_configs_dir) - ), - # Provide all commands as one due to problems with satellites -- commands=[['bash', '-c', '"{0}"'.format('; '.join(remediation_commands))]] -+ commands=[['bash', '-c', '{0}'.format('; '.join(remediation_commands))]] - ), - reporting.ExternalLink( - url='https://access.redhat.com/solutions/7028063', -diff --git a/repos/system_upgrade/common/actors/verifydialogs/libraries/verifydialogs.py b/repos/system_upgrade/common/actors/verifydialogs/libraries/verifydialogs.py -index a79079b1..84b745ca 100644 ---- a/repos/system_upgrade/common/actors/verifydialogs/libraries/verifydialogs.py -+++ b/repos/system_upgrade/common/actors/verifydialogs/libraries/verifydialogs.py -@@ -13,13 +13,14 @@ def check_dialogs(inhibit_if_no_userchoice=True): - dialogs_remediation = ('Please register user choices with leapp answer cli command or by manually editing ' - 'the answerfile.') - # FIXME: Enable more choices once we can do multi-command remediations -- cmd_remediation = [['leapp', 'answer', '--section', "{}={}".format(s, choice)] -- for s, choices in dialog.answerfile_sections.items() for choice in choices[:1]] -+ cmd_remediations = [['leapp', 'answer', '--section', '{}={}'.format(s, choice)] -+ for s, choices in dialog.answerfile_sections.items() -+ for choice in choices[:1]] - report_data = [reporting.Title('Missing required answers in the answer file'), - reporting.Severity(reporting.Severity.HIGH), - reporting.Summary(summary.format('\n'.join(sections))), - reporting.Groups([reporting.Groups.INHIBITOR] if inhibit_if_no_userchoice else []), -- reporting.Remediation(hint=dialogs_remediation, commands=cmd_remediation), -+ reporting.Remediation(hint=dialogs_remediation, commands=cmd_remediations), - reporting.ExternalLink( - url='https://access.redhat.com/solutions/7035321', - title='Leapp upgrade fail with error "Inhibitor: Missing required answers ' -diff --git a/repos/system_upgrade/el9toel10/actors/inhibitcgroupsv1/libraries/inhibitcgroupsv1.py b/repos/system_upgrade/el9toel10/actors/inhibitcgroupsv1/libraries/inhibitcgroupsv1.py -index 6ae41be2..a54883b2 100644 ---- a/repos/system_upgrade/el9toel10/actors/inhibitcgroupsv1/libraries/inhibitcgroupsv1.py -+++ b/repos/system_upgrade/el9toel10/actors/inhibitcgroupsv1/libraries/inhibitcgroupsv1.py -@@ -49,9 +49,9 @@ def process(): - # remove the args from commandline, the defaults are the desired values - commands=[ - [ -- "grubby", -- "--update-kernel=ALL", -- '--remove-args="{}"'.format(" ".join(remediation_cmd_args)), -+ 'grubby', -+ '--update-kernel', 'ALL', -+ '--remove-args', '{}'.format(' '.join(remediation_cmd_args)) - ], - ], - ), -diff --git a/repos/system_upgrade/el9toel10/actors/inhibitcgroupsv1/tests/test_inhibitcgroupsv1.py b/repos/system_upgrade/el9toel10/actors/inhibitcgroupsv1/tests/test_inhibitcgroupsv1.py -index 9b3ec96f..629e8798 100644 ---- a/repos/system_upgrade/el9toel10/actors/inhibitcgroupsv1/tests/test_inhibitcgroupsv1.py -+++ b/repos/system_upgrade/el9toel10/actors/inhibitcgroupsv1/tests/test_inhibitcgroupsv1.py -@@ -39,9 +39,9 @@ def test_inhibit_should_inhibit(monkeypatch, cmdline_params): - assert reporting.Groups.INHIBITOR in report["groups"] - - command = [r for r in report["detail"]["remediations"] if r["type"] == "command"][0] -- assert "systemd.unified_cgroup_hierarchy" in command['context'][2] -+ assert "systemd.unified_cgroup_hierarchy" in command['context'][4] - if len(cmdline_params) == 2: -- assert "systemd.legacy_systemd_cgroup_controller" in command['context'][2] -+ assert "systemd.legacy_systemd_cgroup_controller" in command['context'][4] - - - @pytest.mark.parametrize( --- -2.53.0 - diff --git a/0036-pulseaudiocheck-Add-PulseAudio-config-check-for-RHEL.patch b/0036-pulseaudiocheck-Add-PulseAudio-config-check-for-RHEL.patch deleted file mode 100644 index 45ac1a0..0000000 --- a/0036-pulseaudiocheck-Add-PulseAudio-config-check-for-RHEL.patch +++ /dev/null @@ -1,533 +0,0 @@ -From 7d29232bdc45e15401d24a86f5508c3b3de826d1 Mon Sep 17 00:00:00 2001 -From: Tomas Pelka -Date: Thu, 2 Apr 2026 09:24:33 +0200 -Subject: [PATCH 36/44] pulseaudiocheck: Add PulseAudio config check for RHEL 9 - to 10 upgrade - -PulseAudio is replaced by PipeWire in RHEL 10. Add a scanner/checker -actor pair that detects custom PulseAudio configuration which won't -carry over after the upgrade. - -The scanner (FactsCollectionPhase) checks for: -- Modified default config files via RPM verification -- Drop-in fragments in /etc/pulse/default.pa.d/ and system.pa.d/ -- Per-user configuration in ~/.config/pulse/ - -The checker (ChecksPhase) consumes the scan results and produces -a report with a link to the PipeWire migration guide. - -Resolves: DESKTOP-1151 ---- - .../pulseaudiocheck/checkpulseaudio/actor.py | 20 +++ - .../libraries/checkpulseaudio.py | 86 ++++++++++++ - .../tests/test_checkpulseaudio.py | 127 ++++++++++++++++++ - .../pulseaudiocheck/scanpulseaudio/actor.py | 20 +++ - .../libraries/scanpulseaudio.py | 98 ++++++++++++++ - .../tests/test_scanpulseaudio.py | 77 +++++++++++ - .../models/pulseaudioconfiguration.py | 24 ++++ - 7 files changed, 452 insertions(+) - create mode 100644 repos/system_upgrade/el9toel10/actors/pulseaudiocheck/checkpulseaudio/actor.py - create mode 100644 repos/system_upgrade/el9toel10/actors/pulseaudiocheck/checkpulseaudio/libraries/checkpulseaudio.py - create mode 100644 repos/system_upgrade/el9toel10/actors/pulseaudiocheck/checkpulseaudio/tests/test_checkpulseaudio.py - create mode 100644 repos/system_upgrade/el9toel10/actors/pulseaudiocheck/scanpulseaudio/actor.py - create mode 100644 repos/system_upgrade/el9toel10/actors/pulseaudiocheck/scanpulseaudio/libraries/scanpulseaudio.py - create mode 100644 repos/system_upgrade/el9toel10/actors/pulseaudiocheck/scanpulseaudio/tests/test_scanpulseaudio.py - create mode 100644 repos/system_upgrade/el9toel10/models/pulseaudioconfiguration.py - -diff --git a/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/checkpulseaudio/actor.py b/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/checkpulseaudio/actor.py -new file mode 100644 -index 00000000..9417d6d6 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/checkpulseaudio/actor.py -@@ -0,0 +1,20 @@ -+from leapp.actors import Actor -+from leapp.libraries.actor.checkpulseaudio import check_pulseaudio -+from leapp.models import DistributionSignedRPM, PulseAudioConfiguration, Report -+from leapp.tags import ChecksPhaseTag, IPUWorkflowTag -+ -+ -+class CheckPulseAudio(Actor): -+ """ -+ Check for custom PulseAudio configuration that won't carry over after upgrade. -+ -+ PulseAudio is replaced by PipeWire with the pipewire-pulseaudio compatibility -+ plugin in RHEL 10. Custom PulseAudio configuration will not be applied. -+ """ -+ name = 'check_pulseaudio' -+ consumes = (DistributionSignedRPM, PulseAudioConfiguration) -+ produces = (Report,) -+ tags = (ChecksPhaseTag, IPUWorkflowTag) -+ -+ def process(self): -+ check_pulseaudio() -diff --git a/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/checkpulseaudio/libraries/checkpulseaudio.py b/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/checkpulseaudio/libraries/checkpulseaudio.py -new file mode 100644 -index 00000000..0453ca05 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/checkpulseaudio/libraries/checkpulseaudio.py -@@ -0,0 +1,86 @@ -+from leapp import reporting -+from leapp.libraries.common.distro import DISTRO_REPORT_NAMES -+from leapp.libraries.common.rpms import has_package -+from leapp.libraries.stdlib import api -+from leapp.models import DistributionSignedRPM, PulseAudioConfiguration -+ -+FMT_LIST_SEPARATOR = '\n - ' -+ -+ -+def _report_custom_pulseaudio_config(modified_defaults, dropin_dirs, user_config_dirs): -+ """ -+ Create a report warning about custom PulseAudio configuration. -+ -+ :param modified_defaults: list of modified default config files -+ :type modified_defaults: list -+ :param dropin_dirs: list of drop-in directories with content -+ :type dropin_dirs: list -+ :param user_config_dirs: list of per-user config directories -+ :type user_config_dirs: list -+ """ -+ details = [] -+ if modified_defaults: -+ details.append( -+ 'The following default PulseAudio configuration files have been modified:{sep}{files}'.format( -+ sep=FMT_LIST_SEPARATOR, -+ files=FMT_LIST_SEPARATOR.join(modified_defaults), -+ ) -+ ) -+ if dropin_dirs: -+ details.append( -+ 'The following PulseAudio drop-in configuration directories contain custom ' -+ 'fragments:{sep}{dirs}'.format( -+ sep=FMT_LIST_SEPARATOR, -+ dirs=FMT_LIST_SEPARATOR.join(dropin_dirs), -+ ) -+ ) -+ if user_config_dirs: -+ details.append( -+ 'Per-user PulseAudio configuration was found in:{sep}{dirs}'.format( -+ sep=FMT_LIST_SEPARATOR, -+ dirs=FMT_LIST_SEPARATOR.join(user_config_dirs), -+ ) -+ ) -+ -+ summary = ( -+ 'PulseAudio is replaced by PipeWire in {target_distro} 10. The PipeWire pipewire-pulseaudio plugin provides ' -+ 'compatibility with the default PulseAudio configuration, but custom PulseAudio configuration will ' -+ 'not be applied after the upgrade. Review your PulseAudio configuration and migrate any custom ' -+ 'settings to PipeWire equivalents after upgrading.' -+ ).format_map(DISTRO_REPORT_NAMES) -+ if details: -+ summary += '\n\n' + '\n\n'.join(details) -+ -+ all_paths = modified_defaults + dropin_dirs + user_config_dirs -+ reporting.create_report([ -+ reporting.Title('Custom PulseAudio configuration detected'), -+ reporting.Summary(summary), -+ reporting.Severity(reporting.Severity.MEDIUM), -+ reporting.Groups([reporting.Groups.SERVICES]), -+ reporting.ExternalLink(title='Migrate PulseAudio to PipeWire', -+ url='https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Migrate-PulseAudio'), -+ reporting.Remediation( -+ hint='Review your PulseAudio configuration and plan to migrate custom settings to PipeWire ' -+ 'after the upgrade. The pipewire-pulseaudio plugin handles default configuration automatically.' -+ ), -+ reporting.RelatedResource('package', 'pulseaudio'), -+ ] + [reporting.RelatedResource('file', f) for f in all_paths]) -+ -+ -+def check_pulseaudio(): -+ """ -+ Consume PulseAudioConfiguration and generate report if custom config is found. -+ """ -+ if not has_package(DistributionSignedRPM, 'pulseaudio'): -+ api.current_logger().debug('PulseAudio is not installed, skipping check.') -+ return -+ -+ msg = next(api.consume(PulseAudioConfiguration), None) -+ if not msg: -+ api.current_logger().debug('No PulseAudioConfiguration message received.') -+ return -+ -+ if msg.modified_defaults or msg.dropin_dirs or msg.user_config_dirs: -+ _report_custom_pulseaudio_config(msg.modified_defaults, msg.dropin_dirs, msg.user_config_dirs) -+ else: -+ api.current_logger().info('No custom PulseAudio configuration detected.') -diff --git a/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/checkpulseaudio/tests/test_checkpulseaudio.py b/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/checkpulseaudio/tests/test_checkpulseaudio.py -new file mode 100644 -index 00000000..518bfb73 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/checkpulseaudio/tests/test_checkpulseaudio.py -@@ -0,0 +1,127 @@ -+from leapp import reporting -+from leapp.libraries.actor.checkpulseaudio import check_pulseaudio -+from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked -+from leapp.libraries.stdlib import api -+from leapp.models import DistributionSignedRPM, PulseAudioConfiguration, RPM -+ -+ -+def _generate_rpm_with_name(name): -+ """ -+ Generate new RPM model item with given name. -+ -+ :param name: rpm name -+ :type name: str -+ :return: new RPM object with name parameter set -+ :rtype: RPM -+ """ -+ return RPM(name=name, -+ version='0.1', -+ release='1.sm01', -+ epoch='1', -+ pgpsig='RSA/SHA256, Mon 01 Jan 1970 00:00:00 AM -03, Key ID 199e2f91fd431d51', -+ packager='Red Hat, Inc. ', -+ arch='noarch') -+ -+ -+class TestCheckPulseaudio: -+ """Tests for check_pulseaudio checker function.""" -+ -+ def test_pulseaudio_not_installed(self, monkeypatch): -+ rpms = [_generate_rpm_with_name('some-other-package')] -+ msg = PulseAudioConfiguration(modified_defaults=['/etc/pulse/daemon.conf']) -+ curr_actor_mocked = CurrentActorMocked(msgs=[DistributionSignedRPM(items=rpms), msg]) -+ monkeypatch.setattr(api, 'current_actor', curr_actor_mocked) -+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) -+ -+ check_pulseaudio() -+ -+ assert not reporting.create_report.called -+ -+ def test_no_message(self, monkeypatch): -+ rpms = [_generate_rpm_with_name('pulseaudio')] -+ curr_actor_mocked = CurrentActorMocked(msgs=[DistributionSignedRPM(items=rpms)]) -+ monkeypatch.setattr(api, 'current_actor', curr_actor_mocked) -+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) -+ -+ check_pulseaudio() -+ -+ assert not reporting.create_report.called -+ -+ def test_no_custom_config(self, monkeypatch): -+ rpms = [_generate_rpm_with_name('pulseaudio')] -+ msg = PulseAudioConfiguration() -+ curr_actor_mocked = CurrentActorMocked(msgs=[DistributionSignedRPM(items=rpms), msg]) -+ monkeypatch.setattr(api, 'current_actor', curr_actor_mocked) -+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) -+ -+ check_pulseaudio() -+ -+ assert not reporting.create_report.called -+ -+ def test_modified_defaults(self, monkeypatch): -+ rpms = [_generate_rpm_with_name('pulseaudio')] -+ msg = PulseAudioConfiguration( -+ modified_defaults=['/etc/pulse/daemon.conf'], -+ ) -+ curr_actor_mocked = CurrentActorMocked(msgs=[DistributionSignedRPM(items=rpms), msg]) -+ monkeypatch.setattr(api, 'current_actor', curr_actor_mocked) -+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) -+ -+ check_pulseaudio() -+ -+ assert reporting.create_report.called == 1 -+ report_fields = reporting.create_report.report_fields -+ assert 'Custom PulseAudio configuration detected' in report_fields['title'] -+ assert '/etc/pulse/daemon.conf' in report_fields['summary'] -+ -+ def test_dropin_dirs(self, monkeypatch): -+ rpms = [_generate_rpm_with_name('pulseaudio')] -+ msg = PulseAudioConfiguration( -+ dropin_dirs=['/etc/pulse/default.pa.d'], -+ ) -+ curr_actor_mocked = CurrentActorMocked(msgs=[DistributionSignedRPM(items=rpms), msg]) -+ monkeypatch.setattr(api, 'current_actor', curr_actor_mocked) -+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) -+ -+ check_pulseaudio() -+ -+ assert reporting.create_report.called == 1 -+ report_fields = reporting.create_report.report_fields -+ assert '/etc/pulse/default.pa.d' in report_fields['summary'] -+ -+ def test_user_config_dirs(self, monkeypatch): -+ rpms = [_generate_rpm_with_name('pulseaudio')] -+ msg = PulseAudioConfiguration( -+ user_config_dirs=['/home/admin/.config/pulse'], -+ ) -+ curr_actor_mocked = CurrentActorMocked(msgs=[DistributionSignedRPM(items=rpms), msg]) -+ monkeypatch.setattr(api, 'current_actor', curr_actor_mocked) -+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) -+ -+ check_pulseaudio() -+ -+ assert reporting.create_report.called == 1 -+ report_fields = reporting.create_report.report_fields -+ assert '/home/admin/.config/pulse' in report_fields['summary'] -+ -+ def test_report_all_sources(self, monkeypatch): -+ rpms = [_generate_rpm_with_name('pulseaudio')] -+ msg = PulseAudioConfiguration( -+ modified_defaults=['/etc/pulse/daemon.conf'], -+ dropin_dirs=['/etc/pulse/default.pa.d'], -+ user_config_dirs=['/root/.config/pulse'], -+ ) -+ curr_actor_mocked = CurrentActorMocked(msgs=[DistributionSignedRPM(items=rpms), msg]) -+ monkeypatch.setattr(api, 'current_actor', curr_actor_mocked) -+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) -+ -+ check_pulseaudio() -+ -+ assert reporting.create_report.called == 1 -+ report_fields = reporting.create_report.report_fields -+ resources = report_fields['detail']['related_resources'] -+ pkg_resources = [r for r in resources if r['scheme'] == 'package'] -+ file_resources = [r for r in resources if r['scheme'] == 'file'] -+ assert len(pkg_resources) == 1 -+ assert pkg_resources[0]['title'] == 'pulseaudio' -+ assert len(file_resources) == 3 -diff --git a/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/scanpulseaudio/actor.py b/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/scanpulseaudio/actor.py -new file mode 100644 -index 00000000..5614c802 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/scanpulseaudio/actor.py -@@ -0,0 +1,20 @@ -+from leapp.actors import Actor -+from leapp.libraries.actor.scanpulseaudio import scan_pulseaudio -+from leapp.models import PulseAudioConfiguration -+from leapp.tags import FactsPhaseTag, IPUWorkflowTag -+ -+ -+class ScanPulseAudio(Actor): -+ """ -+ Scan the system for PulseAudio custom configuration. -+ -+ Detects whether PulseAudio is installed and checks for custom -+ configuration that will not carry over to PipeWire after upgrade. -+ """ -+ name = 'scan_pulseaudio' -+ consumes = () -+ produces = (PulseAudioConfiguration,) -+ tags = (FactsPhaseTag, IPUWorkflowTag) -+ -+ def process(self): -+ self.produce(scan_pulseaudio()) -diff --git a/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/scanpulseaudio/libraries/scanpulseaudio.py b/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/scanpulseaudio/libraries/scanpulseaudio.py -new file mode 100644 -index 00000000..e5c2be53 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/scanpulseaudio/libraries/scanpulseaudio.py -@@ -0,0 +1,98 @@ -+import os -+import pwd -+ -+from leapp.libraries.common.rpms import check_file_modification -+from leapp.models import PulseAudioConfiguration -+ -+# System-wide PulseAudio configuration directory -+PULSEAUDIO_CONFIG_DIR = '/etc/pulse' -+ -+# Drop-in directories included from default.pa and system.pa -+_DROPIN_DIRS = ( -+ '/etc/pulse/default.pa.d', -+ '/etc/pulse/system.pa.d', -+) -+ -+# Files that are part of the default PulseAudio installation -+_DEFAULT_CONFIG_FILES = frozenset(( -+ 'client.conf', -+ 'daemon.conf', -+ 'default.pa', -+ 'system.pa', -+)) -+ -+# Per-user PulseAudio config directory relative to home -+_USER_CONFIG_SUBDIR = '.config/pulse' -+ -+ -+def _get_dropin_dirs_with_content(): -+ """ -+ Return list of drop-in directories that exist and contain files. -+ -+ PulseAudio includes fragments from /etc/pulse/default.pa.d/ and -+ /etc/pulse/system.pa.d/ via .include directives. These directories -+ do not exist by default. -+ -+ :return: list of drop-in directory paths that contain files -+ :rtype: list -+ """ -+ found = [] -+ for dropin_dir in _DROPIN_DIRS: -+ if os.path.isdir(dropin_dir) and os.listdir(dropin_dir): -+ found.append(dropin_dir) -+ return found -+ -+ -+def _get_user_config_dirs(): -+ """ -+ Return list of user home directories that contain PulseAudio configuration. -+ -+ Checks ~/.config/pulse/ for each user with a valid home directory. -+ -+ :return: list of per-user PulseAudio config directory paths -+ :rtype: list -+ """ -+ found = [] -+ for user in pwd.getpwall(): -+ pulse_dir = os.path.join(user.pw_dir, _USER_CONFIG_SUBDIR) -+ if os.path.isdir(pulse_dir) and os.listdir(pulse_dir): -+ found.append(pulse_dir) -+ return sorted(found) -+ -+ -+def _check_default_configs_modified(): -+ """ -+ Check whether any of the default PulseAudio config files have been modified. -+ -+ Uses RPM verification to detect changes to files owned by the pulseaudio -+ package. Returns list of modified default config file paths. -+ -+ :return: list of modified default config file paths -+ :rtype: list -+ """ -+ modified = [] -+ for filename in sorted(_DEFAULT_CONFIG_FILES): -+ filepath = os.path.join(PULSEAUDIO_CONFIG_DIR, filename) -+ if os.path.isfile(filepath): -+ if check_file_modification(filepath): -+ modified.append(filepath) -+ -+ return modified -+ -+ -+def scan_pulseaudio(): -+ """ -+ Scan the system for PulseAudio configuration and return findings. -+ -+ :return: PulseAudioConfiguration message with scan results -+ :rtype: PulseAudioConfiguration -+ """ -+ modified_defaults = _check_default_configs_modified() -+ dropin_dirs = _get_dropin_dirs_with_content() -+ user_config_dirs = _get_user_config_dirs() -+ -+ return PulseAudioConfiguration( -+ modified_defaults=modified_defaults, -+ dropin_dirs=dropin_dirs, -+ user_config_dirs=user_config_dirs, -+ ) -diff --git a/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/scanpulseaudio/tests/test_scanpulseaudio.py b/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/scanpulseaudio/tests/test_scanpulseaudio.py -new file mode 100644 -index 00000000..f499aafe ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/scanpulseaudio/tests/test_scanpulseaudio.py -@@ -0,0 +1,77 @@ -+import os -+ -+from leapp.libraries.actor import scanpulseaudio -+from leapp.libraries.actor.scanpulseaudio import _get_dropin_dirs_with_content, _get_user_config_dirs, scan_pulseaudio -+ -+ -+class TestGetDropinDirsWithContent: -+ """Tests for _get_dropin_dirs_with_content.""" -+ -+ def test_no_dropin_dirs(self, monkeypatch): -+ monkeypatch.setattr(os.path, 'isdir', lambda _: False) -+ assert _get_dropin_dirs_with_content() == [] -+ -+ def test_empty_dropin_dirs(self, monkeypatch): -+ monkeypatch.setattr(os.path, 'isdir', lambda _: True) -+ monkeypatch.setattr(os, 'listdir', lambda _: []) -+ assert _get_dropin_dirs_with_content() == [] -+ -+ def test_dropin_dirs_with_content(self, monkeypatch): -+ monkeypatch.setattr(os.path, 'isdir', lambda _: True) -+ monkeypatch.setattr(os, 'listdir', lambda _: ['custom.conf']) -+ result = _get_dropin_dirs_with_content() -+ assert result == ['/etc/pulse/default.pa.d', '/etc/pulse/system.pa.d'] -+ -+ def test_only_one_dropin_dir_exists(self, monkeypatch): -+ monkeypatch.setattr(os.path, 'isdir', lambda path: path == '/etc/pulse/default.pa.d') -+ monkeypatch.setattr(os, 'listdir', lambda _: ['custom.conf']) -+ result = _get_dropin_dirs_with_content() -+ assert result == ['/etc/pulse/default.pa.d'] -+ -+ -+class TestGetUserConfigDirs: -+ """Tests for _get_user_config_dirs.""" -+ -+ def test_no_user_config(self, monkeypatch): -+ monkeypatch.setattr(os.path, 'isdir', lambda _: False) -+ assert _get_user_config_dirs() == [] -+ -+ def test_user_config_found(self, monkeypatch): -+ monkeypatch.setattr(os.path, 'isdir', lambda path: path == '/home/testuser/.config/pulse') -+ monkeypatch.setattr(os, 'listdir', lambda _: ['default.pa']) -+ -+ class FakeUser: -+ pw_dir = '/home/testuser' -+ -+ monkeypatch.setattr(scanpulseaudio.pwd, 'getpwall', lambda: [FakeUser()]) -+ result = _get_user_config_dirs() -+ assert result == ['/home/testuser/.config/pulse'] -+ -+ -+class TestScanPulseaudio: -+ """Tests for scan_pulseaudio main function.""" -+ -+ def test_no_custom_config(self, monkeypatch): -+ monkeypatch.setattr(scanpulseaudio, '_check_default_configs_modified', lambda: []) -+ monkeypatch.setattr(scanpulseaudio, '_get_dropin_dirs_with_content', lambda: []) -+ monkeypatch.setattr(scanpulseaudio, '_get_user_config_dirs', lambda: []) -+ -+ result = scan_pulseaudio() -+ -+ assert result.modified_defaults == [] -+ assert result.dropin_dirs == [] -+ assert result.user_config_dirs == [] -+ -+ def test_with_all_findings(self, monkeypatch): -+ monkeypatch.setattr(scanpulseaudio, '_check_default_configs_modified', -+ lambda: ['/etc/pulse/daemon.conf']) -+ monkeypatch.setattr(scanpulseaudio, '_get_dropin_dirs_with_content', -+ lambda: ['/etc/pulse/default.pa.d']) -+ monkeypatch.setattr(scanpulseaudio, '_get_user_config_dirs', -+ lambda: ['/root/.config/pulse']) -+ -+ result = scan_pulseaudio() -+ -+ assert result.modified_defaults == ['/etc/pulse/daemon.conf'] -+ assert result.dropin_dirs == ['/etc/pulse/default.pa.d'] -+ assert result.user_config_dirs == ['/root/.config/pulse'] -diff --git a/repos/system_upgrade/el9toel10/models/pulseaudioconfiguration.py b/repos/system_upgrade/el9toel10/models/pulseaudioconfiguration.py -new file mode 100644 -index 00000000..bfb7aa22 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/models/pulseaudioconfiguration.py -@@ -0,0 +1,24 @@ -+from leapp.models import fields, Model -+from leapp.topics import SystemInfoTopic -+ -+ -+class PulseAudioConfiguration(Model): -+ """ -+ Model describing the state of PulseAudio configuration on the system. -+ """ -+ topic = SystemInfoTopic -+ -+ modified_defaults = fields.List(fields.String(), default=[]) -+ """ -+ Default config files modified from RPM originals (full paths) -+ """ -+ -+ dropin_dirs = fields.List(fields.String(), default=[]) -+ """ -+ Drop-in directories that exist and contain files (full paths) -+ """ -+ -+ user_config_dirs = fields.List(fields.String(), default=[]) -+ """ -+ Per-user config directories that exist and contain files (full paths) -+ """ --- -2.53.0 - diff --git a/0037-fix-checkyumpluginsenabled-correctly-create-remediat.patch b/0037-fix-checkyumpluginsenabled-correctly-create-remediat.patch deleted file mode 100644 index 73c8c07..0000000 --- a/0037-fix-checkyumpluginsenabled-correctly-create-remediat.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 87c192519e8764f59516cca563816f062428a533 Mon Sep 17 00:00:00 2001 -From: Peter Mocary -Date: Thu, 2 Apr 2026 16:28:48 +0200 -Subject: [PATCH 37/44] fix(checkyumpluginsenabled): correctly create - remediation command - -The remediation command for the inhibitor triggered when required dnf -plugins are not enabled was created incorrectly. This patch fixes the -command creation. ---- - .../libraries/checkyumpluginsenabled.py | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/checkyumpluginsenabled/libraries/checkyumpluginsenabled.py b/repos/system_upgrade/common/actors/checkyumpluginsenabled/libraries/checkyumpluginsenabled.py -index 9afa51ac..869e2a88 100644 ---- a/repos/system_upgrade/common/actors/checkyumpluginsenabled/libraries/checkyumpluginsenabled.py -+++ b/repos/system_upgrade/common/actors/checkyumpluginsenabled/libraries/checkyumpluginsenabled.py -@@ -36,8 +36,8 @@ def check_required_dnf_plugins_enabled(pkg_manager_info): - product_id_plugin_conf = os.path.join(plugin_configs_dir, 'product-id.conf') - - remediation_commands = [ -- f"sed -i 's/^plugins=0/plugins=1/' '{dnf_conf_path}'" -- f"sed -i 's/^enabled=0/enabled=1/' '{rhsm_plugin_conf}'" -+ f"sed -i 's/^plugins=0/plugins=1/' '{dnf_conf_path}'", -+ f"sed -i 's/^enabled=0/enabled=1/' '{rhsm_plugin_conf}'", - f"sed -i 's/^enabled=0/enabled=1/' '{product_id_plugin_conf}'" - ] - --- -2.53.0 - diff --git a/0038-fix-rhui-consider-source-clients-always-signed.patch b/0038-fix-rhui-consider-source-clients-always-signed.patch deleted file mode 100644 index 14a96cd..0000000 --- a/0038-fix-rhui-consider-source-clients-always-signed.patch +++ /dev/null @@ -1,201 +0,0 @@ -From 0cfeee9fa113690c7e58877edf65842aad7d7ce5 Mon Sep 17 00:00:00 2001 -From: Michal Hecko -Date: Fri, 27 Mar 2026 13:53:49 +0100 -Subject: [PATCH 38/44] fix(rhui): consider source clients always signed - -Previously, all RHUI clients mentioned in the cloud map in the rhui.py -library were considered as signed by RH. However, configs allow using -clients that are not known to leapp, and, therefore, such clients would -not be considered as signed. Consequently, these packages would not be -removed, causing the upgrade to fail. This patch changes this behavior -so that all source clients mentioned in the RHUIInfo message are -considered as signed. ---- - .../filterrpmtransactionevents/actor.py | 12 +- - .../tests/test_filterrpmtransactionevents.py | 120 +++++++++++++++++- - 2 files changed, 125 insertions(+), 7 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/filterrpmtransactionevents/actor.py b/repos/system_upgrade/common/actors/filterrpmtransactionevents/actor.py -index 582a5821..aa735da3 100644 ---- a/repos/system_upgrade/common/actors/filterrpmtransactionevents/actor.py -+++ b/repos/system_upgrade/common/actors/filterrpmtransactionevents/actor.py -@@ -1,8 +1,11 @@ - from leapp.actors import Actor -+from leapp.libraries.common.rpms import has_package - from leapp.models import ( - DistributionSignedRPM, - FilteredRpmTransactionTasks, -+ InstalledRPM, - PESRpmTransactionTasks, -+ RHUIInfo, - RpmTransactionTasks - ) - from leapp.tags import ChecksPhaseTag, IPUWorkflowTag -@@ -18,7 +21,7 @@ class FilterRpmTransactionTasks(Actor): - """ - - name = 'check_rpm_transaction_events' -- consumes = (PESRpmTransactionTasks, RpmTransactionTasks, DistributionSignedRPM,) -+ consumes = (PESRpmTransactionTasks, RpmTransactionTasks, DistributionSignedRPM, RHUIInfo, InstalledRPM) - produces = (FilteredRpmTransactionTasks,) - tags = (IPUWorkflowTag, ChecksPhaseTag) - -@@ -27,6 +30,13 @@ class FilterRpmTransactionTasks(Actor): - for rpm_pkgs in self.consume(DistributionSignedRPM): - installed_pkgs.update([pkg.name for pkg in rpm_pkgs.items]) - -+ # Consider all RHUI source clients as signed, so that we can always remove them -+ rhui_info = next(self.consume(RHUIInfo), None) -+ if rhui_info: -+ for source_client in rhui_info.src_client_pkg_names: -+ if has_package(InstalledRPM, source_client): -+ installed_pkgs.add(source_client) -+ - local_rpms = set() - to_install = set() - to_remove = set() -diff --git a/repos/system_upgrade/common/actors/filterrpmtransactionevents/tests/test_filterrpmtransactionevents.py b/repos/system_upgrade/common/actors/filterrpmtransactionevents/tests/test_filterrpmtransactionevents.py -index 7173805e..a228ed07 100644 ---- a/repos/system_upgrade/common/actors/filterrpmtransactionevents/tests/test_filterrpmtransactionevents.py -+++ b/repos/system_upgrade/common/actors/filterrpmtransactionevents/tests/test_filterrpmtransactionevents.py -@@ -1,7 +1,37 @@ --from leapp.models import DistributionSignedRPM, FilteredRpmTransactionTasks, Module, RPM, RpmTransactionTasks -+from leapp.models import ( -+ DistributionSignedRPM, -+ FilteredRpmTransactionTasks, -+ InstalledRPM, -+ Module, -+ RHUIInfo, -+ RPM, -+ RpmTransactionTasks, -+ TargetRHUIPostInstallTasks, -+ TargetRHUIPreInstallTasks, -+ TargetRHUISetupInfo -+) - from leapp.snactor.fixture import current_actor_context - - RH_PACKAGER = 'Red Hat, Inc. ' -+UNSIGNED_PACKAGER = 'Third Party ' -+ -+ -+def _make_rpm(name, packager=RH_PACKAGER): -+ return RPM(name=name, version='0.1', release='1.sm01', epoch='1', -+ packager=packager, arch='noarch', pgpsig='SOME_PGP_SIG') -+ -+ -+def _make_rhui_info(src_clients, target_clients, provider='azure'): -+ setup_info = TargetRHUISetupInfo( -+ preinstall_tasks=TargetRHUIPreInstallTasks(), -+ postinstall_tasks=TargetRHUIPostInstallTasks(), -+ ) -+ return RHUIInfo( -+ provider=provider, -+ src_client_pkg_names=src_clients, -+ target_client_pkg_names=target_clients, -+ target_client_setup_info=setup_info, -+ ) - - - def test_actor_execution(current_actor_context): -@@ -10,11 +40,7 @@ def test_actor_execution(current_actor_context): - - - def test_actor_execution_with_sample_data(current_actor_context): -- installed_rpm = [ -- RPM(name='sample01', version='0.1', release='1.sm01', epoch='1', packager=RH_PACKAGER, arch='noarch', -- pgpsig='SOME_PGP_SIG'), -- RPM(name='sample02', version='0.1', release='1.sm01', epoch='1', packager=RH_PACKAGER, arch='noarch', -- pgpsig='SOME_PGP_SIG')] -+ installed_rpm = [_make_rpm('sample01'), _make_rpm('sample02')] - modules_to_enable = [Module(name='enable', stream='1'), Module(name='enable', stream='2')] - modules_to_reset = [Module(name='reset', stream='1'), Module(name='reset', stream='2')] - current_actor_context.feed(DistributionSignedRPM(items=installed_rpm)) -@@ -43,3 +69,85 @@ def test_actor_execution_with_sample_data(current_actor_context): - assert all(m.name == 'reset' for m in result[0].modules_to_reset) - assert '1' in {m.stream for m in result[0].modules_to_reset} - assert '2' in {m.stream for m in result[0].modules_to_reset} -+ -+ -+def test_rhui_source_client_treated_as_signed(current_actor_context): -+ """Installed RHUI source clients should be removable even if they are not distribution-signed.""" -+ rhui_client_rpm = _make_rpm('rhui-azure-rhel8', packager=UNSIGNED_PACKAGER) -+ signed_rpm = _make_rpm('sample01') -+ -+ current_actor_context.feed(DistributionSignedRPM(items=[signed_rpm])) -+ current_actor_context.feed(InstalledRPM(items=[rhui_client_rpm, signed_rpm])) -+ current_actor_context.feed(_make_rhui_info(['rhui-azure-rhel8'], ['rhui-azure-rhel9'])) -+ current_actor_context.feed(RpmTransactionTasks(to_remove=['rhui-azure-rhel8'])) -+ current_actor_context.run() -+ -+ result = current_actor_context.consume(FilteredRpmTransactionTasks) -+ assert len(result) == 1 -+ assert 'rhui-azure-rhel8' in result[0].to_remove -+ -+ -+def test_rhui_source_client_not_installed_is_ignored(current_actor_context): -+ """RHUI source clients that are not installed should not be added to the installed set.""" -+ signed_rpm = _make_rpm('sample01') -+ -+ current_actor_context.feed(DistributionSignedRPM(items=[signed_rpm])) -+ current_actor_context.feed(InstalledRPM(items=[signed_rpm])) -+ current_actor_context.feed(_make_rhui_info(['rhui-azure-rhel8'], ['rhui-azure-rhel9'])) -+ current_actor_context.feed(RpmTransactionTasks(to_remove=['rhui-azure-rhel8'])) -+ current_actor_context.run() -+ -+ result = current_actor_context.consume(FilteredRpmTransactionTasks) -+ assert len(result) == 1 -+ assert 'rhui-azure-rhel8' not in result[0].to_remove -+ -+ -+def test_no_rhui_info_unsigned_pkg_not_removable(current_actor_context): -+ """Without RHUIInfo, unsigned packages should not be removable (baseline behavior).""" -+ unsigned_rpm = _make_rpm('some-unsigned-pkg', packager=UNSIGNED_PACKAGER) -+ signed_rpm = _make_rpm('sample01') -+ -+ current_actor_context.feed(DistributionSignedRPM(items=[signed_rpm])) -+ current_actor_context.feed(InstalledRPM(items=[unsigned_rpm, signed_rpm])) -+ current_actor_context.feed(RpmTransactionTasks(to_remove=['some-unsigned-pkg'])) -+ current_actor_context.run() -+ -+ result = current_actor_context.consume(FilteredRpmTransactionTasks) -+ assert len(result) == 1 -+ assert 'some-unsigned-pkg' not in result[0].to_remove -+ -+ -+def test_rhui_multiple_source_clients(current_actor_context): -+ """Multiple RHUI source clients should all be treated as signed when installed.""" -+ client_rpms = [ -+ _make_rpm('rhui-client-a', packager=UNSIGNED_PACKAGER), -+ _make_rpm('rhui-client-b', packager=UNSIGNED_PACKAGER), -+ ] -+ -+ current_actor_context.feed(DistributionSignedRPM(items=[])) -+ current_actor_context.feed(InstalledRPM(items=client_rpms)) -+ current_actor_context.feed(_make_rhui_info(['rhui-client-a', 'rhui-client-b'], ['rhui-client-target'])) -+ current_actor_context.feed(RpmTransactionTasks( -+ to_remove=['rhui-client-a', 'rhui-client-b'], -+ )) -+ current_actor_context.run() -+ -+ result = current_actor_context.consume(FilteredRpmTransactionTasks) -+ assert len(result) == 1 -+ assert 'rhui-client-a' in result[0].to_remove -+ assert 'rhui-client-b' in result[0].to_remove -+ -+ -+def test_rhui_source_client_ends_up_in_upgrade_if_not_removed(current_actor_context): -+ """An installed RHUI source client not in to_remove/to_install should be upgraded.""" -+ rhui_client_rpm = _make_rpm('rhui-azure-rhel8', packager=UNSIGNED_PACKAGER) -+ -+ current_actor_context.feed(DistributionSignedRPM(items=[])) -+ current_actor_context.feed(InstalledRPM(items=[rhui_client_rpm])) -+ current_actor_context.feed(_make_rhui_info(['rhui-azure-rhel8'], ['rhui-azure-rhel9'])) -+ current_actor_context.feed(RpmTransactionTasks()) -+ current_actor_context.run() -+ -+ result = current_actor_context.consume(FilteredRpmTransactionTasks) -+ assert len(result) == 1 -+ assert 'rhui-azure-rhel8' in result[0].to_upgrade --- -2.53.0 - diff --git a/0039-Ensure-that-greenboot-rpms-are-removed-during-IPU.patch b/0039-Ensure-that-greenboot-rpms-are-removed-during-IPU.patch deleted file mode 100644 index ac85e1c..0000000 --- a/0039-Ensure-that-greenboot-rpms-are-removed-during-IPU.patch +++ /dev/null @@ -1,42 +0,0 @@ -From f11f7c4c022b990f3cad15eff5149591e747a27b Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Thu, 2 Apr 2026 10:57:55 +0200 -Subject: [PATCH 39/44] Ensure that greenboot rpms are removed during IPU - -The greenboot packages have a value just for rpm-ostree based systems. -However, if someone install them anyway (e.g. configuration mistake..), -they would damage the upgraded systems - mainly in case IPU 8.10 -> 9.8+. -Systemd fails with error: - ``` - Failed to isolate the default target. - ``` - -Due to bugs in greenboot scriptlets, that do not count with DNF -execution inside container, nor with the update of greenboot packages -from 0.14.x to 0.16.x and newer. - -As there is not value to have these packages on rpm based system -at all, just remove them during the upgrade to stay on a safe side. ---- - etc/leapp/transaction/to_remove | 8 ++++++++ - 1 file changed, 8 insertions(+) - -diff --git a/etc/leapp/transaction/to_remove b/etc/leapp/transaction/to_remove -index 0feb7827..420078f4 100644 ---- a/etc/leapp/transaction/to_remove -+++ b/etc/leapp/transaction/to_remove -@@ -1,3 +1,11 @@ - ### List of packages (each on new line) to be removed from the upgrade transaction - # Removing initial-setup package to avoid it asking for EULA acceptance during upgrade - OAMG-1531 - initial-setup -+ -+# greenboot packages have a value just for rpm-ostree based systems -+# however, if someone would install them anyway, they would damage -+# the upgraded systems (mainly in case IPU 8.10 -> 9.8+). -+# As there is not value for of these packages on systems upgraded by leapp -+# (rpm based only), let's just remove them to stay safe -+greenboot -+greenboot-default-health-checks --- -2.53.0 - diff --git a/0040-Regenerate-grub-config-during-8-9-conversions.patch b/0040-Regenerate-grub-config-during-8-9-conversions.patch deleted file mode 100644 index 44b7d25..0000000 --- a/0040-Regenerate-grub-config-during-8-9-conversions.patch +++ /dev/null @@ -1,205 +0,0 @@ -From 83540ae7dbd6cb030a024249d6308c56f0d3216d Mon Sep 17 00:00:00 2001 -From: Matej Matuska -Date: Tue, 31 Mar 2026 17:23:39 +0200 -Subject: [PATCH 40/44] Regenerate grub config during 8->9 conversions - -This is mainly a preventative action to make sure that the grub config -is compatible with the target grub. - -The actor only runs grub2-mkconfig when BLS is enabled in -/etc/default/grub. When BLS is disabled, the regeneration is taken care -of by the grub kernel install scripts (in /usr/lib/kernel/install/), -which do call grub2-mkconfig as required. - -On 9to10 conversions the kernel install scripts handle regeneration -whether the BLS is enabled or not. - -Note that in some cases grub2-mkconfig might be ran twice, as there are -2 more actors that run it (ensurevalidgrubcfghybrid and -grub2mkconfigonppc64). This shouldn't be a problem since the generation -is quick. However a better solution could be designed in the future. - -Jira: RHEL-110712 ---- - .../actors/regenerategrubcfg/actor.py | 23 ++++ - .../libraries/regenerategrubcfg.py | 28 +++++ - .../tests/test_regenerategrubcfg.py | 102 ++++++++++++++++++ - 3 files changed, 153 insertions(+) - create mode 100644 repos/system_upgrade/el8toel9/actors/regenerategrubcfg/actor.py - create mode 100644 repos/system_upgrade/el8toel9/actors/regenerategrubcfg/libraries/regenerategrubcfg.py - create mode 100644 repos/system_upgrade/el8toel9/actors/regenerategrubcfg/tests/test_regenerategrubcfg.py - -diff --git a/repos/system_upgrade/el8toel9/actors/regenerategrubcfg/actor.py b/repos/system_upgrade/el8toel9/actors/regenerategrubcfg/actor.py -new file mode 100644 -index 00000000..d87e9c7b ---- /dev/null -+++ b/repos/system_upgrade/el8toel9/actors/regenerategrubcfg/actor.py -@@ -0,0 +1,23 @@ -+from leapp.actors import Actor -+from leapp.libraries.actor import regenerategrubcfg -+from leapp.models import DefaultGrubInfo, TransactionCompleted -+from leapp.tags import ApplicationsPhaseTag, IPUWorkflowTag -+ -+ -+class RegenerateGrubCfg(Actor): -+ """ -+ Regenerate GRUB2 configuration during conversion from EL8 to EL9. -+ -+ During distribution conversions (e.g. CentOS to RHEL), the GRUB2 -+ configuration may need to be regenerated to ensure compatibility -+ with the target distribution's GRUB2 tooling. This actor runs -+ grub2-mkconfig when BLS is enabled in /etc/default/grub. -+ """ -+ -+ name = 'regenerate_grub_cfg' -+ consumes = (DefaultGrubInfo, TransactionCompleted) -+ produces = () -+ tags = (IPUWorkflowTag, ApplicationsPhaseTag) -+ -+ def process(self): -+ regenerategrubcfg.process() -diff --git a/repos/system_upgrade/el8toel9/actors/regenerategrubcfg/libraries/regenerategrubcfg.py b/repos/system_upgrade/el8toel9/actors/regenerategrubcfg/libraries/regenerategrubcfg.py -new file mode 100644 -index 00000000..bedd0897 ---- /dev/null -+++ b/repos/system_upgrade/el8toel9/actors/regenerategrubcfg/libraries/regenerategrubcfg.py -@@ -0,0 +1,28 @@ -+from leapp.libraries.common import grub -+from leapp.libraries.common.config import architecture, is_conversion -+from leapp.libraries.stdlib import api, CalledProcessError, run -+from leapp.models import DefaultGrubInfo -+ -+GRUB_CFG_PATH = '/boot/grub2/grub.cfg' -+ -+ -+def process(): -+ if architecture.matches_architecture(architecture.ARCH_S390X): -+ return -+ -+ if not is_conversion(): -+ return -+ -+ default_grub_msg = next(api.consume(DefaultGrubInfo), None) -+ if not default_grub_msg: -+ api.current_logger().warning('No DefaultGrubInfo message, skipping GRUB config regeneration.') -+ return -+ -+ if not grub.is_blscfg_enabled_in_defaultgrub(default_grub_msg): -+ return -+ -+ api.current_logger().info('Conversion detected with BLS enabled, regenerating GRUB config') -+ try: -+ run(['grub2-mkconfig', '-o', GRUB_CFG_PATH]) -+ except CalledProcessError as e: -+ api.current_logger().error('Failed to regenerate GRUB config: {}'.format(e)) -diff --git a/repos/system_upgrade/el8toel9/actors/regenerategrubcfg/tests/test_regenerategrubcfg.py b/repos/system_upgrade/el8toel9/actors/regenerategrubcfg/tests/test_regenerategrubcfg.py -new file mode 100644 -index 00000000..7d8fb6f3 ---- /dev/null -+++ b/repos/system_upgrade/el8toel9/actors/regenerategrubcfg/tests/test_regenerategrubcfg.py -@@ -0,0 +1,102 @@ -+from unittest import mock -+ -+import pytest -+ -+from leapp.libraries.actor import regenerategrubcfg -+from leapp.libraries.common.config import architecture -+from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked -+from leapp.libraries.stdlib import api, CalledProcessError -+from leapp.models import DefaultGrub, DefaultGrubInfo -+ -+GRUB2_MKCONFIG_CMD = ['grub2-mkconfig', '-o', '/boot/grub2/grub.cfg'] -+ -+BLS_ENABLED = DefaultGrubInfo( -+ default_grub_info=[DefaultGrub(name='GRUB_ENABLE_BLSCFG', value='true')] -+) -+BLS_DISABLED = DefaultGrubInfo( -+ default_grub_info=[DefaultGrub(name='GRUB_ENABLE_BLSCFG', value='false')] -+) -+ -+ -+@pytest.fixture -+def mocked_run(): -+ with mock.patch.object(regenerategrubcfg, 'run') as m: -+ yield m -+ -+ -+@pytest.fixture(autouse=True) -+def mocked_logger(): -+ with mock.patch.object(api, 'current_logger', logger_mocked()): -+ yield api.current_logger -+ -+ -+@pytest.fixture -+def mock_actor(monkeypatch): -+ -+ def make_mock(msgs, arch=architecture.ARCH_X86_64, is_conversion=False): -+ instance = CurrentActorMocked( -+ msgs=msgs, -+ arch=arch, -+ src_ver="8.10", -+ dst_ver="9.6", -+ ) -+ monkeypatch.setattr(api, 'current_actor', instance) -+ # let's use this to cover all conversion paths -+ monkeypatch.setattr(regenerategrubcfg, 'is_conversion', lambda: is_conversion) -+ return instance -+ -+ return make_mock -+ -+ -+def test_conversion_bls_enabled_regenerates(mock_actor, mocked_run): -+ """Conversion with BLS enabled -> regenerate.""" -+ mock_actor([BLS_ENABLED], is_conversion=True) -+ regenerategrubcfg.process() -+ mocked_run.assert_called_once_with(GRUB2_MKCONFIG_CMD) -+ -+ -+def test_conversion_bls_disabled_skips(mock_actor, mocked_run): -+ """Conversion with BLS not enabled -> skip.""" -+ mock_actor([BLS_DISABLED], is_conversion=True) -+ regenerategrubcfg.process() -+ mocked_run.assert_not_called() -+ -+ -+def test_non_conversion_skips(mock_actor, mocked_run): -+ """Non-conversion upgrade -> skip.""" -+ mock_actor([BLS_ENABLED]) -+ regenerategrubcfg.process() -+ mocked_run.assert_not_called() -+ -+ -+def test_s390x_skips(mock_actor, mocked_run): -+ """s390x -> skip (uses ZIPL).""" -+ mock_actor([BLS_ENABLED], arch=architecture.ARCH_S390X) -+ regenerategrubcfg.process() -+ mocked_run.assert_not_called() -+ -+ -+def test_no_default_grub_info_skips(mock_actor, mocked_run, mocked_logger): -+ """No DefaultGrubInfo -> skip.""" -+ mock_actor([], is_conversion=True) -+ regenerategrubcfg.process() -+ mocked_run.assert_not_called() -+ assert any( -+ "No DefaultGrubInfo message, skipping GRUB config regeneration." in msg -+ for msg in mocked_logger.warnmsg -+ ) -+ -+ -+def test_failure_nonfatal(mock_actor, mocked_run, mocked_logger): -+ """grub2-mkconfig failure -> non-fatal, logs error.""" -+ mocked_run.side_effect = CalledProcessError( -+ message='A Leapp Command Error occurred.', -+ command=GRUB2_MKCONFIG_CMD, -+ result={'signal': None, 'exit_code': 1, 'pid': 0, 'stdout': 'fake', 'stderr': 'fake'} -+ ) -+ mock_actor([BLS_ENABLED], is_conversion=True) -+ -+ regenerategrubcfg.process() -+ -+ mocked_run.assert_called_once_with(GRUB2_MKCONFIG_CMD) -+ assert any('Failed to regenerate GRUB config' in msg for msg in mocked_logger.errmsg) --- -2.53.0 - diff --git a/0041-python-tests-deps-update-version-requirements-per-py.patch b/0041-python-tests-deps-update-version-requirements-per-py.patch deleted file mode 100644 index 2c4fe24..0000000 --- a/0041-python-tests-deps-update-version-requirements-per-py.patch +++ /dev/null @@ -1,75 +0,0 @@ -From 971e5e329290d8e5047ac85c6c85943dfadd5fe6 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Tue, 14 Apr 2026 11:25:01 +0200 -Subject: [PATCH 41/44] python tests deps: update version requirements per - python - -tl;dr; We haven't updated the requirements.txt file for a long time -(basically since IPU 7 -> 8; if we do not count some minor updates). -Let's update dependencies to use newer python modules for newer -versions of python. - -* Pytest - Originally we required pytest v6.2.5 for any python 3.6+. This is - pretty old requirement coming from time of python ~ 3.8 and recently - a CVE-2025-71176 occuared, which is fix in pytest v9.0.3. - So let's update ranges to actually allow use of newer pytest on newer - systems: - - +----------------+---------------+ - | pytest version | python range | - +----------------+---------------+ - | 6.2.5 | 3.6.x - 3.7.x | - | 7.4.4 | 3.8.x - 3.9.x | - | 9.0.3 | 3.10+ | - +----------------+---------------+ - Note that Pytest 8.x is messing with imports which breaks leapp - import handling unfortunately (race-condition). Seems that these - problems are fixed for Pytest 9 with Python 3.10+. In case we - figure out problems anyway, pytest will need to be executed with - additional parameters to use old import style again. As there is - no high demand for newer pytest on python 3.9, let's stick just - with working pytest versions. - -* Pyudev - The original v0.22 is used up to CS 9 (pyton 3.9-). Use 0.24.1 for - python 3.10+ (reflects CS 10 as well) - -* Distro - The original v1.5.0 can be used till CS 9 (python 3.9 included) safely - as nowadays. Let's use version 1.9.0 for Python 3.10+ - -* Drop unsued dependencies (old python that is no longer supported - in the project). ---- - requirements.txt | 13 +++++++------ - 1 file changed, 7 insertions(+), 6 deletions(-) - -diff --git a/requirements.txt b/requirements.txt -index 3c79b23d..e4294718 100644 ---- a/requirements.txt -+++ b/requirements.txt -@@ -5,13 +5,14 @@ isort - funcsigs==1.0.2 - mock==2.0.0 - pylint --pytest==4.6.11; python_version < '3.0' --pytest==6.2.5; python_version >= '3.6' --pyudev==0.22.0 --distro==1.5.0 -+pytest==6.2.5; python_version >= '3.6' and python_version < '3.8' -+pytest==7.4.4; python_version >= '3.8' and python_version < '3.10' -+pytest==9.0.3; python_version >= '3.10' -+pyudev==0.22.0; python_version < '3.10' -+pyudev==0.24.1; python_version >= '3.10' -+distro==1.5.0; python_version < '3.10' -+distro==1.9.0; python_version >= '3.10' - ipaddress==1.0.23 - git+https://github.com/oamg/leapp - requests --# pinning a py27 troublemaking transitive dependency --lazy-object-proxy==1.5.2; python_version < '3' - rpm --- -2.53.0 - diff --git a/0042-Drop-dependency-on-mock.patch b/0042-Drop-dependency-on-mock.patch deleted file mode 100644 index 0108063..0000000 --- a/0042-Drop-dependency-on-mock.patch +++ /dev/null @@ -1,149 +0,0 @@ -From 0f0be8e4ca80721173d289a3db0b170334529c2c Mon Sep 17 00:00:00 2001 -From: Matej Matuska -Date: Wed, 29 Oct 2025 19:31:00 +0100 -Subject: [PATCH 42/44] Drop dependency on mock - -The mock library is now a part of the Python standard library since -Python 3.3 and is available as the unittest.mock. The 'mock' library is -only a backport which is not needed anymore. - -Also remove some unnecessary imports. ---- - commands/tests/test_upgrade_paths.py | 2 +- - .../common/actors/biosdevname/tests/test_biosdevname.py | 3 ++- - .../actors/cephvolumescan/tests/test_cephvolumescan.py | 5 +---- - .../tests/test_distributionsignedrpmscanner.py | 3 +-- - .../actors/ifcfgscanner/tests/unit_test_ifcfgscanner.py | 2 +- - .../common/actors/scanmemory/tests/test_scanmemory.py | 3 ++- - .../tests/component_test_selinuxcontentscanner.py | 4 +--- - .../el8toel9/actors/nisscanner/tests/test_nisscan.py | 3 ++- - requirements.txt | 1 - - 9 files changed, 11 insertions(+), 15 deletions(-) - -diff --git a/commands/tests/test_upgrade_paths.py b/commands/tests/test_upgrade_paths.py -index 773cdf1c..83e8b7f9 100644 ---- a/commands/tests/test_upgrade_paths.py -+++ b/commands/tests/test_upgrade_paths.py -@@ -1,7 +1,7 @@ - import os - import resource -+import unittest.mock as mock - --import mock - import pytest - - from leapp.cli.commands import command_utils -diff --git a/repos/system_upgrade/common/actors/biosdevname/tests/test_biosdevname.py b/repos/system_upgrade/common/actors/biosdevname/tests/test_biosdevname.py -index 3aa8c614..1b11174f 100644 ---- a/repos/system_upgrade/common/actors/biosdevname/tests/test_biosdevname.py -+++ b/repos/system_upgrade/common/actors/biosdevname/tests/test_biosdevname.py -@@ -1,7 +1,8 @@ -+from unittest.mock import mock_open, patch -+ - import pytest - import pyudev - import six --from mock import mock_open, patch - - from leapp.exceptions import StopActorExecutionError - from leapp.libraries.actor import biosdevname -diff --git a/repos/system_upgrade/common/actors/cephvolumescan/tests/test_cephvolumescan.py b/repos/system_upgrade/common/actors/cephvolumescan/tests/test_cephvolumescan.py -index 168b8fc2..14e1f359 100644 ---- a/repos/system_upgrade/common/actors/cephvolumescan/tests/test_cephvolumescan.py -+++ b/repos/system_upgrade/common/actors/cephvolumescan/tests/test_cephvolumescan.py -@@ -1,9 +1,6 @@ --import pytest --from mock import Mock, patch -+from unittest.mock import patch - - from leapp.libraries.actor import cephvolumescan --from leapp.models import InstalledRPM, LsblkEntry, RPM, StorageInfo --from leapp.reporting import Report - - CONT_PS_COMMAND_OUTPUT = { - "stdout": -diff --git a/repos/system_upgrade/common/actors/distributionsignedrpmscanner/tests/test_distributionsignedrpmscanner.py b/repos/system_upgrade/common/actors/distributionsignedrpmscanner/tests/test_distributionsignedrpmscanner.py -index b0c616cb..e5db0c8f 100644 ---- a/repos/system_upgrade/common/actors/distributionsignedrpmscanner/tests/test_distributionsignedrpmscanner.py -+++ b/repos/system_upgrade/common/actors/distributionsignedrpmscanner/tests/test_distributionsignedrpmscanner.py -@@ -1,4 +1,4 @@ --import mock -+import unittest.mock as mock - - from leapp.libraries.common import rpms - from leapp.libraries.common.config import mock_configs -@@ -8,7 +8,6 @@ from leapp.models import ( - fields, - InstalledRPM, - InstalledUnsignedRPM, -- IPUConfig, - Model, - OSRelease, - RPM, -diff --git a/repos/system_upgrade/common/actors/ifcfgscanner/tests/unit_test_ifcfgscanner.py b/repos/system_upgrade/common/actors/ifcfgscanner/tests/unit_test_ifcfgscanner.py -index d996de84..bef3d485 100644 ---- a/repos/system_upgrade/common/actors/ifcfgscanner/tests/unit_test_ifcfgscanner.py -+++ b/repos/system_upgrade/common/actors/ifcfgscanner/tests/unit_test_ifcfgscanner.py -@@ -1,9 +1,9 @@ - import errno - import textwrap -+import unittest.mock as mock - from io import StringIO - from os.path import basename - --import mock - import six - - from leapp.libraries.actor import ifcfgscanner -diff --git a/repos/system_upgrade/common/actors/scanmemory/tests/test_scanmemory.py b/repos/system_upgrade/common/actors/scanmemory/tests/test_scanmemory.py -index 13e4e724..20fa8e91 100644 ---- a/repos/system_upgrade/common/actors/scanmemory/tests/test_scanmemory.py -+++ b/repos/system_upgrade/common/actors/scanmemory/tests/test_scanmemory.py -@@ -1,4 +1,5 @@ --import mock -+import unittest.mock as mock -+ - import six - - from leapp.libraries.actor import scanmemory -diff --git a/repos/system_upgrade/common/actors/selinux/selinuxcontentscanner/tests/component_test_selinuxcontentscanner.py b/repos/system_upgrade/common/actors/selinux/selinuxcontentscanner/tests/component_test_selinuxcontentscanner.py -index 802e038a..15aef5d8 100644 ---- a/repos/system_upgrade/common/actors/selinux/selinuxcontentscanner/tests/component_test_selinuxcontentscanner.py -+++ b/repos/system_upgrade/common/actors/selinux/selinuxcontentscanner/tests/component_test_selinuxcontentscanner.py -@@ -4,9 +4,7 @@ import pytest - - from leapp.libraries.common.config import mock_configs - from leapp.libraries.stdlib import api, CalledProcessError, run --from leapp.models import SELinuxCustom, SELinuxFacts, SELinuxModule, SELinuxModules, SELinuxRequestRPMs --from leapp.reporting import Report --from leapp.snactor.fixture import current_actor_context -+from leapp.models import SELinuxCustom, SELinuxFacts, SELinuxModules, SELinuxRequestRPMs - - # compat module ensures compatibility with newer systems and is not part of testing - TEST_MODULES = [ -diff --git a/repos/system_upgrade/el8toel9/actors/nisscanner/tests/test_nisscan.py b/repos/system_upgrade/el8toel9/actors/nisscanner/tests/test_nisscan.py -index ed000ce0..d7e77a28 100644 ---- a/repos/system_upgrade/el8toel9/actors/nisscanner/tests/test_nisscan.py -+++ b/repos/system_upgrade/el8toel9/actors/nisscanner/tests/test_nisscan.py -@@ -1,4 +1,5 @@ --import mock -+import unittest.mock as mock -+ - import pytest - import six - -diff --git a/requirements.txt b/requirements.txt -index e4294718..ee1d9006 100644 ---- a/requirements.txt -+++ b/requirements.txt -@@ -3,7 +3,6 @@ - flake8 - isort - funcsigs==1.0.2 --mock==2.0.0 - pylint - pytest==6.2.5; python_version >= '3.6' and python_version < '3.8' - pytest==7.4.4; python_version >= '3.8' and python_version < '3.10' --- -2.53.0 - diff --git a/0043-remove-single-quoting-from-remediation-commands-1520.patch b/0043-remove-single-quoting-from-remediation-commands-1520.patch deleted file mode 100644 index 6a38567..0000000 --- a/0043-remove-single-quoting-from-remediation-commands-1520.patch +++ /dev/null @@ -1,194 +0,0 @@ -From d8c82c9460e2ab08ac735c7af06ec4a73a9b4de4 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Peter=20Mo=C4=8D=C3=A1ry?= - <68905580+PeterMocary@users.noreply.github.com> -Date: Thu, 16 Apr 2026 23:00:14 +0200 -Subject: [PATCH 43/44] remove single quoting from remediation commands (#1520) - -The framework now uses shlex.quote to always make literals out of -remediation command arguments. Some of the existing remediation commands used single -quotes in their subcommands so the resulting command in leapp-report.txt used -unintuitive escaping of single quotes. - -The chackrootsymlinks can still end up with a single quote if the user -has it in one of the symlinks that need to be adjusted. However, it is -not possible to handle this differently without introducing side effects. - -jira: RHEL-156521 ---- - packaging/leapp-repository.spec | 2 +- - .../common/actors/checkrootsymlinks/actor.py | 53 +-------------- - .../libraries/checkrootsymlinks.py | 65 +++++++++++++++++++ - .../libraries/checkyumpluginsenabled.py | 6 +- - 4 files changed, 71 insertions(+), 55 deletions(-) - create mode 100644 repos/system_upgrade/common/actors/checkrootsymlinks/libraries/checkrootsymlinks.py - -diff --git a/packaging/leapp-repository.spec b/packaging/leapp-repository.spec -index 3ffafafa..70b1d7d3 100644 ---- a/packaging/leapp-repository.spec -+++ b/packaging/leapp-repository.spec -@@ -120,7 +120,7 @@ Requires: leapp-repository-dependencies = %{leapp_repo_deps} - - # IMPORTANT: this is capability provided by the leapp framework rpm. - # Check that 'version' instead of the real framework rpm version. --Requires: leapp-framework >= 6.4, leapp-framework < 7 -+Requires: leapp-framework >= 6.5, leapp-framework < 7 - - # Since we provide sub-commands for the leapp utility, we expect the leapp - # tool to be installed as well. -diff --git a/repos/system_upgrade/common/actors/checkrootsymlinks/actor.py b/repos/system_upgrade/common/actors/checkrootsymlinks/actor.py -index 2e805542..391e5589 100644 ---- a/repos/system_upgrade/common/actors/checkrootsymlinks/actor.py -+++ b/repos/system_upgrade/common/actors/checkrootsymlinks/actor.py -@@ -1,8 +1,5 @@ --import os -- --from leapp import reporting - from leapp.actors import Actor --from leapp.exceptions import StopActorExecutionError -+from leapp.libraries.actor import checkrootsymlinks - from leapp.models import Report, RootDirectory - from leapp.tags import ChecksPhaseTag, IPUWorkflowTag - -@@ -20,50 +17,4 @@ class CheckRootSymlinks(Actor): - tags = (IPUWorkflowTag, ChecksPhaseTag) - - def process(self): -- rootdir = next(self.consume(RootDirectory), None) -- if not rootdir: -- raise StopActorExecutionError('Cannot check root symlinks', -- details={'Problem': 'Did not receive a message with ' -- 'root subdirectories'}) -- absolute_links = [item for item in rootdir.items if item.target and os.path.isabs(item.target)] -- absolute_links_nonutf = [item for item in rootdir.invalid_items if item.target and os.path.isabs(item.target)] -- if not absolute_links and not absolute_links_nonutf: -- return -- -- report_fields = [ -- reporting.Title('Upgrade requires links in root directory to be relative'), -- reporting.Summary( -- 'After rebooting, parts of the upgrade process can fail if symbolic links in / ' -- 'point to absolute paths.\n' -- 'Please change these links to relative ones.' -- ), -- reporting.ExternalLink( -- url='https://access.redhat.com/solutions/6989732', -- title='leapp upgrade stops with Inhibitor "Upgrade requires links in root ' -- 'directory to be relative"' -- ), -- reporting.Severity(reporting.Severity.HIGH), -- reporting.Groups([reporting.Groups.INHIBITOR])] -- -- # Generate reports about absolute links presence -- rem_commands = [] -- if absolute_links: -- commands = [] -- for item in absolute_links: -- command = ' '.join(['ln', -- '-snf', -- f"'{os.path.relpath(item.target, '/')}'", -- f"'{os.path.join('/', item.name)}'"]) -- commands.append(command) -- rem_commands = [['bash', '-c', '{}'.format(' && '.join(commands))]] -- # Generate reports about non-utf8 absolute links presence -- nonutf_count = len(absolute_links_nonutf) -- if nonutf_count > 0: -- # for non-utf encoded filenames can't provide a remediation command, so will mention this fact in a hint -- rem_hint = ("{} symbolic links point to absolute paths that have non-utf8 encoding and need to be" -- " fixed additionally".format(nonutf_count)) -- report_fields.append(reporting.Remediation(hint=rem_hint, commands=rem_commands)) -- else: -- report_fields.append(reporting.Remediation(commands=rem_commands)) -- -- reporting.create_report(report_fields) -+ checkrootsymlinks.process() -diff --git a/repos/system_upgrade/common/actors/checkrootsymlinks/libraries/checkrootsymlinks.py b/repos/system_upgrade/common/actors/checkrootsymlinks/libraries/checkrootsymlinks.py -new file mode 100644 -index 00000000..2c5676bd ---- /dev/null -+++ b/repos/system_upgrade/common/actors/checkrootsymlinks/libraries/checkrootsymlinks.py -@@ -0,0 +1,65 @@ -+import os -+ -+from leapp import reporting -+from leapp.exceptions import StopActorExecutionError -+from leapp.libraries.stdlib import api -+from leapp.models import RootDirectory -+ -+ -+def _dquote(s): -+ """Double-quote a string for use inside a non-interactive shell script.""" -+ escaped = (s.replace('\\', '\\\\') -+ .replace('"', '\\"') -+ .replace('$', '\\$') -+ .replace('`', '\\`')) -+ return '"{}"'.format(escaped) -+ -+ -+def process(): -+ rootdir = next(api.consume(RootDirectory), None) -+ if not rootdir: -+ raise StopActorExecutionError('Cannot check root symlinks', -+ details={'Problem': 'Did not receive a message with ' -+ 'root subdirectories'}) -+ absolute_links = [item for item in rootdir.items if item.target and os.path.isabs(item.target)] -+ absolute_links_nonutf = [item for item in rootdir.invalid_items if item.target and os.path.isabs(item.target)] -+ if not absolute_links and not absolute_links_nonutf: -+ return -+ -+ report_fields = [ -+ reporting.Title('Upgrade requires links in root directory to be relative'), -+ reporting.Summary( -+ 'After rebooting, parts of the upgrade process can fail if symbolic links in / ' -+ 'point to absolute paths.\n' -+ 'Please change these links to relative ones.' -+ ), -+ reporting.ExternalLink( -+ url='https://access.redhat.com/solutions/6989732', -+ title='leapp upgrade stops with Inhibitor "Upgrade requires links in root ' -+ 'directory to be relative"' -+ ), -+ reporting.Severity(reporting.Severity.HIGH), -+ reporting.Groups([reporting.Groups.INHIBITOR])] -+ -+ # Generate reports about absolute links presence -+ rem_commands = [] -+ if absolute_links: -+ commands = [] -+ for item in absolute_links: -+ command = ' '.join(['ln', -+ '-snf', -+ _dquote(os.path.relpath(item.target, '/')), -+ _dquote(os.path.join('/', item.name))]) -+ commands.append(command) -+ rem_commands = [['bash', '-c', '{}'.format(' && '.join(commands))]] -+ # Generate reports about non-utf8 absolute links presence -+ nonutf_count = len(absolute_links_nonutf) -+ if nonutf_count > 0: -+ # for non-utf encoded filenames can't provide a remediation command, so will mention this fact in a hint -+ rem_hint = ("{} symbolic links point to absolute paths that have non-utf8 encoding and need to be" -+ " fixed additionally".format(nonutf_count)) -+ report_fields.append(reporting.Remediation(hint=rem_hint, commands=rem_commands)) -+ else: -+ report_fields.append(reporting.Remediation(commands=rem_commands)) -+ -+ reporting.create_report(report_fields) -diff --git a/repos/system_upgrade/common/actors/checkyumpluginsenabled/libraries/checkyumpluginsenabled.py b/repos/system_upgrade/common/actors/checkyumpluginsenabled/libraries/checkyumpluginsenabled.py -index 869e2a88..5c99a0d9 100644 ---- a/repos/system_upgrade/common/actors/checkyumpluginsenabled/libraries/checkyumpluginsenabled.py -+++ b/repos/system_upgrade/common/actors/checkyumpluginsenabled/libraries/checkyumpluginsenabled.py -@@ -36,9 +36,9 @@ def check_required_dnf_plugins_enabled(pkg_manager_info): - product_id_plugin_conf = os.path.join(plugin_configs_dir, 'product-id.conf') - - remediation_commands = [ -- f"sed -i 's/^plugins=0/plugins=1/' '{dnf_conf_path}'", -- f"sed -i 's/^enabled=0/enabled=1/' '{rhsm_plugin_conf}'", -- f"sed -i 's/^enabled=0/enabled=1/' '{product_id_plugin_conf}'" -+ f'sed -i "s/^plugins=0/plugins=1/" "{dnf_conf_path}"', -+ f'sed -i "s/^enabled=0/enabled=1/" "{rhsm_plugin_conf}"', -+ f'sed -i "s/^enabled=0/enabled=1/" "{product_id_plugin_conf}"', - ] - - reporting.create_report([ --- -2.53.0 - diff --git a/0044-Update-leapp-data-data-stream-4.3-CTC-1.patch b/0044-Update-leapp-data-data-stream-4.3-CTC-1.patch deleted file mode 100644 index 83e584b..0000000 --- a/0044-Update-leapp-data-data-stream-4.3-CTC-1.patch +++ /dev/null @@ -1,4401 +0,0 @@ -From a86bee719a998d629efd7b7be1bf2ff08ee43234 Mon Sep 17 00:00:00 2001 -From: Leapp CI Bot -Date: Mon, 16 Feb 2026 03:12:49 +0000 -Subject: [PATCH 44/44] Update leapp data: data stream 4.3: CTC 1 - -* repomap: add GCP RHUI repos + EUS for RT/NFV - The following mappings for UpgPath(src_major='9', dst_major='10') have been added: - - MappingEntry(src='rhel9-rhui-google-compute-engine', dst=('rhel10-rhui-google-compute-engine-leapp',)) - - The following repos have been added: - - Repo(pesid='rhel10-AppStream', major_version='10', repoid='rhui-rhel-10-for-x86_64-appstream-e4s-rhui-rpms', repo_type='rpm', channel='e4s', arch='x86_64', rhui='google', distro='rhel') - - Repo(pesid='rhel10-AppStream', major_version='10', repoid='rhui-rhel-10-for-x86_64-appstream-rhui-rpms', repo_type='rpm', channel='ga', arch='x86_64', rhui='google', distro='rhel') - - Repo(pesid='rhel10-BaseOS', major_version='10', repoid='rhui-rhel-10-for-x86_64-baseos-e4s-rhui-rpms', repo_type='rpm', channel='e4s', arch='x86_64', rhui='google', distro='rhel') - - Repo(pesid='rhel10-BaseOS', major_version='10', repoid='rhui-rhel-10-for-x86_64-baseos-rhui-rpms', repo_type='rpm', channel='ga', arch='x86_64', rhui='google', distro='rhel') - - Repo(pesid='rhel10-CRB', major_version='10', repoid='rhui-codeready-builder-for-rhel-10-x86_64-rhui-rpms', repo_type='rpm', channel='ga', arch='x86_64', rhui='google', distro='rhel') - - Repo(pesid='rhel10-HighAvailability', major_version='10', repoid='rhui-rhel-10-for-x86_64-highavailability-e4s-rhui-rpms', repo_type='rpm', channel='e4s', arch='x86_64', rhui='google', distro='rhel') - - Repo(pesid='rhel10-NFV', major_version='10', repoid='rhel-10-for-aarch64-nfv-eus-rpms', repo_type='rpm', channel='eus', arch='aarch64', rhui=None, distro='rhel') - - Repo(pesid='rhel10-NFV', major_version='10', repoid='rhel-10-for-ppc64le-nfv-beta-rpms', repo_type='rpm', channel='beta', arch='ppc64le', rhui=None, distro='rhel') - - Repo(pesid='rhel10-NFV', major_version='10', repoid='rhel-10-for-s390x-nfv-beta-rpms', repo_type='rpm', channel='beta', arch='s390x', rhui=None, distro='rhel') - - Repo(pesid='rhel10-NFV', major_version='10', repoid='rhel-10-for-x86_64-nfv-eus-rpms', repo_type='rpm', channel='eus', arch='x86_64', rhui=None, distro='rhel') - - Repo(pesid='rhel10-RT', major_version='10', repoid='rhel-10-for-aarch64-rt-eus-rpms', repo_type='rpm', channel='eus', arch='aarch64', rhui=None, distro='rhel') - - Repo(pesid='rhel10-RT', major_version='10', repoid='rhel-10-for-ppc64le-rt-beta-rpms', repo_type='rpm', channel='beta', arch='ppc64le', rhui=None, distro='rhel') - - Repo(pesid='rhel10-RT', major_version='10', repoid='rhel-10-for-s390x-rt-beta-rpms', repo_type='rpm', channel='beta', arch='s390x', rhui=None, distro='rhel') - - Repo(pesid='rhel10-RT', major_version='10', repoid='rhel-10-for-x86_64-rt-eus-rpms', repo_type='rpm', channel='eus', arch='x86_64', rhui=None, distro='rhel') - - Repo(pesid='rhel10-SAP-NetWeaver', major_version='10', repoid='rhel-10-for-aarch64-sap-netweaver-beta-rpms', repo_type='rpm', channel='beta', arch='aarch64', rhui=None, distro='rhel') - - Repo(pesid='rhel10-SAP-NetWeaver', major_version='10', repoid='rhui-rhel-10-for-x86_64-sap-netweaver-e4s-rhui-rpms', repo_type='rpm', channel='e4s', arch='x86_64', rhui='google', distro='rhel') - - Repo(pesid='rhel10-SAP-Solutions', major_version='10', repoid='rhui-rhel-10-for-x86_64-sap-solutions-e4s-rhui-rpms', repo_type='rpm', channel='e4s', arch='x86_64', rhui='google', distro='rhel') - - Repo(pesid='rhel10-Supplementary', major_version='10', repoid='rhui-rhel-10-for-x86_64-supplementary-rhui-rpms', repo_type='rpm', channel='ga', arch='x86_64', rhui='google', distro='rhel') - - Repo(pesid='rhel10-rhui-google-compute-engine', major_version='10', repoid='google-compute-engine', repo_type='rpm', channel='ga', arch='x86_64', rhui='google', distro='rhel') - - Repo(pesid='rhel10-rhui-google-compute-engine-leapp', major_version='10', repoid='google-compute-engine-el10-for-x86_64', repo_type='rpm', channel='ga', arch='x86_64', rhui='google', distro='rhel') - - Repo(pesid='rhel9-NFV', major_version='9', repoid='rhel-9-for-aarch64-nfv-eus-rpms', repo_type='rpm', channel='eus', arch='aarch64', rhui=None, distro='rhel') - - Repo(pesid='rhel9-NFV', major_version='9', repoid='rhel-9-for-ppc64le-nfv-beta-rpms', repo_type='rpm', channel='beta', arch='ppc64le', rhui=None, distro='rhel') - - Repo(pesid='rhel9-NFV', major_version='9', repoid='rhel-9-for-s390x-nfv-beta-rpms', repo_type='rpm', channel='beta', arch='s390x', rhui=None, distro='rhel') - - Repo(pesid='rhel9-NFV', major_version='9', repoid='rhel-9-for-x86_64-nfv-eus-rpms', repo_type='rpm', channel='eus', arch='x86_64', rhui=None, distro='rhel') - - Repo(pesid='rhel9-RT', major_version='9', repoid='rhel-9-for-aarch64-rt-eus-rpms', repo_type='rpm', channel='eus', arch='aarch64', rhui=None, distro='rhel') - - Repo(pesid='rhel9-RT', major_version='9', repoid='rhel-9-for-ppc64le-rt-beta-rpms', repo_type='rpm', channel='beta', arch='ppc64le', rhui=None, distro='rhel') - - Repo(pesid='rhel9-RT', major_version='9', repoid='rhel-9-for-s390x-rt-beta-rpms', repo_type='rpm', channel='beta', arch='s390x', rhui=None, distro='rhel') - - Repo(pesid='rhel9-RT', major_version='9', repoid='rhel-9-for-x86_64-rt-eus-rpms', repo_type='rpm', channel='eus', arch='x86_64', rhui=None, distro='rhel') - - Repo(pesid='rhel9-SAP-NetWeaver', major_version='9', repoid='rhel-9-for-aarch64-sap-netweaver-beta-rpms', repo_type='rpm', channel='beta', arch='aarch64', rhui=None, distro='rhel') - -* DDDD: Add Emulex devices, update Mellanox for RHEL 10 - - Add 2 Emulex lpfc devices for RHEL 7 (LPe15000/LPe16000 VF, OneConnect FCoE VF) - - Update ConnectX-9 (0x1025): Now available and maintained in RHEL 10 - - Add ConnectX-10 (0x1027): New device entry (not yet available) - - Update BlueField-4 ConnectX-8 (0xA2DF): Now available and maintained in RHEL 10 - (DDDD summary generated with Claude Code Opus 4.6) - -* PES data: Reflect newest changes for RHEL 9.9 & 10.3 ---- - .../files/device_driver_deprecation_data.json | 51 +- - etc/leapp/files/pes-events.json | 2534 +++++++++++++++-- - etc/leapp/files/repomap.json | 263 +- - 3 files changed, 2539 insertions(+), 309 deletions(-) - -diff --git a/etc/leapp/files/device_driver_deprecation_data.json b/etc/leapp/files/device_driver_deprecation_data.json -index c38c2840..df35538f 100644 ---- a/etc/leapp/files/device_driver_deprecation_data.json -+++ b/etc/leapp/files/device_driver_deprecation_data.json -@@ -1,6 +1,6 @@ - { - "provided_data_streams": [ -- "4.2" -+ "4.3" - ], - "data": [ - { -@@ -4764,6 +4764,32 @@ - 8 - ] - }, -+ { -+ "available_in_rhel": [ -+ 7 -+ ], -+ "deprecation_announced": "", -+ "device_id": "0x10df:0xe208", -+ "device_name": "Emulex Corporation: LPe15000/LPe16000 Series 8Gb/16Gb Fibre Channel Adapter (VF)", -+ "device_type": "pci", -+ "driver_name": "lpfc", -+ "maintained_in_rhel": [ -+ 7 -+ ] -+ }, -+ { -+ "available_in_rhel": [ -+ 7 -+ ], -+ "deprecation_announced": "", -+ "device_id": "0x10df:0xe268", -+ "device_name": "Emulex Corporation: OneConnect FCoE Initiator (Lancer VF)", -+ "device_type": "pci", -+ "driver_name": "lpfc", -+ "maintained_in_rhel": [ -+ 7 -+ ] -+ }, - { - "available_in_rhel": [ - 7, -@@ -5316,12 +5342,25 @@ - ] - }, - { -- "available_in_rhel": [], -+ "available_in_rhel": [ -+ 10 -+ ], - "deprecation_announced": "", - "device_id": "0x15B3:0x1025", - "device_name": "ConnectX-9", - "device_type": "pci", - "driver_name": "mlx5", -+ "maintained_in_rhel": [ -+ 10 -+ ] -+ }, -+ { -+ "available_in_rhel": [], -+ "deprecation_announced": "", -+ "device_id": "0x15B3:0x1027", -+ "device_name": "ConnectX-10", -+ "device_type": "pci", -+ "driver_name": "mlx5", - "maintained_in_rhel": [] - }, - { -@@ -5343,13 +5382,17 @@ - ] - }, - { -- "available_in_rhel": [], -+ "available_in_rhel": [ -+ 10 -+ ], - "deprecation_announced": "", - "device_id": "0x15B3:0xA2DF", - "device_name": "BlueField-4 integrated ConnectX-8 network controller", - "device_type": "pci", - "driver_name": "mlx5", -- "maintained_in_rhel": [] -+ "maintained_in_rhel": [ -+ 10 -+ ] - }, - { - "available_in_rhel": [ -diff --git a/etc/leapp/files/pes-events.json b/etc/leapp/files/pes-events.json -index 07a716f0..33f64187 100644 ---- a/etc/leapp/files/pes-events.json -+++ b/etc/leapp/files/pes-events.json -@@ -1,7 +1,7 @@ - { --"timestamp": "202602051305Z", -+"timestamp": "202603261505Z", - "provided_data_streams": [ --"4.2" -+"4.3" - ], - "packageinfo": [ - { -@@ -541188,6 +541188,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "ruby", -@@ -541198,13 +541202,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -541255,6 +541266,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "ruby-bundled-gems", -@@ -541265,13 +541280,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -541322,6 +541344,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "ruby-default-gems", -@@ -541332,13 +541358,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -541389,6 +541422,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "ruby-devel", -@@ -541399,13 +541436,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -541456,6 +541500,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "ruby-doc", -@@ -541466,13 +541514,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -541523,6 +541578,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "rubygem-bigdecimal", -@@ -541533,13 +541592,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -541590,6 +541656,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "rubygem-bundler", -@@ -541600,13 +541670,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -541657,6 +541734,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "rubygem-io-console", -@@ -541667,13 +541748,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -541724,6 +541812,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "rubygem-irb", -@@ -541734,13 +541826,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -541791,6 +541890,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "rubygem-json", -@@ -541801,13 +541904,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -541858,6 +541968,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "rubygem-minitest", -@@ -541868,13 +541982,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -541925,6 +542046,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "rubygem-power_assert", -@@ -541935,13 +542060,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -541992,6 +542124,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "rubygem-psych", -@@ -542002,13 +542138,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -542055,6 +542198,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "rubygem-racc", -@@ -542065,13 +542212,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.3" - }, - "out_modulestream": null -@@ -542115,6 +542269,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "rubygem-rake", -@@ -542125,13 +542283,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -542182,6 +542347,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "rubygem-rbs", -@@ -542192,13 +542361,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -542249,6 +542425,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "rubygem-rdoc", -@@ -542259,13 +542439,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -542316,6 +542503,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "rubygem-rexml", -@@ -542326,13 +542517,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -542383,6 +542581,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "rubygem-rss", -@@ -542393,13 +542595,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -542450,6 +542659,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "rubygems", -@@ -542460,13 +542673,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -542517,6 +542737,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "rubygems-devel", -@@ -542527,13 +542751,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -542584,6 +542815,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "rubygem-test-unit", -@@ -542594,13 +542829,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -542651,6 +542893,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "rubygem-typeprof", -@@ -542661,13 +542907,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -542718,6 +542971,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "ruby-libs", -@@ -542728,13 +542985,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -544792,6 +545056,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "rubygem-pg", -@@ -544802,13 +545070,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -544859,6 +545134,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "rubygem-mysql2", -@@ -544869,13 +545148,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -544926,6 +545212,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "rubygem-pg-doc", -@@ -544936,13 +545226,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -544993,6 +545290,10 @@ null - { - "name": "ruby", - "stream": "3.3" -+}, -+{ -+"name": "ruby", -+"stream": "4.0" - } - ], - "name": "rubygem-mysql2-doc", -@@ -545003,13 +545304,20 @@ null - }, - "initial_release": { - "major_version": 9, --"minor_version": 5, -+"minor_version": 9, - "os_name": "RHEL" - }, - "modulestream_maps": [ - { - "in_modulestream": { - "name": "ruby", -+"stream": "4.0" -+}, -+"out_modulestream": null -+}, -+{ -+"in_modulestream": { -+"name": "ruby", - "stream": "3.1" - }, - "out_modulestream": null -@@ -715291,29 +715599,1570 @@ null - "s390x", - "x86_64" - ], --"id": 20115, -+"id": 20115, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "mariadb11.8-server-galera", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26817 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20116, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "mariadb11.8-server-utils", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26818 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20117, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "mariadb11.8-test", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26819 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20118, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26820 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20119, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-bcmath", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26821 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20120, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-cli", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26822 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20121, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-common", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26823 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20122, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-dba", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26824 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20123, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-dbg", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26825 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20124, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-devel", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26826 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20125, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-embedded", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26827 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20126, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-enchant", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26828 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20127, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-ffi", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26829 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20128, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-fpm", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26830 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20129, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-gd", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26831 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20130, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-gmp", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26832 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20131, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-intl", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26833 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20132, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-ldap", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26834 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20133, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-mbstring", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26835 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20134, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-mysqlnd", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26836 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20135, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-odbc", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26837 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20136, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-opcache", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26838 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20137, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-pdo", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26839 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20138, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-pgsql", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26840 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20139, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-process", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26841 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20140, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-snmp", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26842 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20141, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-soap", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26843 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20142, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-xml", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26844 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20143, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-pecl-xdebug3", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26845 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20144, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-pecl-rrd", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26846 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20145, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-pecl-zip", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26847 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20146, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-pecl-apcu", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26848 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20147, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-pecl-apcu-devel", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26849 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20148, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "php8.4-pecl-redis6", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26850 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20149, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "clevis-pin-trustee", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26851 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20150, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "ansible-collection-redhat-leapp", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26852 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 7, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20151, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "rhel-net-naming-sysattrs", -+"repository": "rhel9-BaseOS" -+} -+], -+"set_id": 26853 -+}, -+"initial_release": { -+"major_version": 9, -+"minor_version": 8, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [ -+{ -+"in_modulestream": null, -+"out_modulestream": null -+} -+], -+"out_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "net-naming-sysattrs", -+"repository": "rhel10-BaseOS" -+} -+], -+"set_id": 26854 -+}, -+"release": { -+"major_version": 10, -+"minor_version": 0, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"x86_64" -+], -+"id": 20152, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "go-fdo-client", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26855 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"x86_64" -+], -+"id": 20153, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "go-fdo-server", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26856 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20154, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "libdazzle-devel", -+"repository": "rhel9-CRB" -+} -+], -+"set_id": 26857 -+}, -+"initial_release": { -+"major_version": 9, -+"minor_version": 7, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 9, -+"minor_version": 8, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20155, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "libhandy-devel", -+"repository": "rhel9-CRB" -+} -+], -+"set_id": 26858 -+}, -+"initial_release": { -+"major_version": 9, -+"minor_version": 7, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 9, -+"minor_version": 8, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20156, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "trustee-kbs", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26859 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 4, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20157, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "keylime-agent-rust", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26860 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [ -+{ -+"in_modulestream": null, -+"out_modulestream": null -+} -+], -+"out_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "keylime-agent-rust", -+"repository": "rhel10-AppStream" -+}, -+{ -+"modulestreams": [ -+null -+], -+"name": "keylime-agent-rust-common", -+"repository": "rhel10-AppStream" -+}, -+{ -+"modulestreams": [ -+null -+], -+"name": "keylime-agent-rust-ima-emulator", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26861 -+}, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20158, -+"in_packageset": { -+"package": [ -+{ -+"modulestreams": [ -+null -+], -+"name": "keylime-agent-rust-push", -+"repository": "rhel10-AppStream" -+} -+], -+"set_id": 26862 -+}, -+"initial_release": { -+"major_version": 10, -+"minor_version": 1, -+"os_name": "RHEL" -+}, -+"modulestream_maps": [], -+"out_packageset": null, -+"release": { -+"major_version": 10, -+"minor_version": 2, -+"os_name": "RHEL" -+} -+}, -+{ -+"action": 0, -+"architectures": [ -+"aarch64", -+"ppc64le", -+"s390x", -+"x86_64" -+], -+"id": 20159, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "mariadb11.8-server-galera", --"repository": "rhel10-AppStream" -+"name": "ruby", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26817 -+"set_id": 26863 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -715325,29 +717174,32 @@ null - "s390x", - "x86_64" - ], --"id": 20116, -+"id": 20160, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "mariadb11.8-server-utils", --"repository": "rhel10-AppStream" -+"name": "ruby-bundled-gems", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26818 -+"set_id": 26864 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -715359,29 +717211,32 @@ null - "s390x", - "x86_64" - ], --"id": 20117, -+"id": 20161, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "mariadb11.8-test", --"repository": "rhel10-AppStream" -+"name": "ruby-default-gems", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26819 -+"set_id": 26865 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -715393,29 +717248,32 @@ null - "s390x", - "x86_64" - ], --"id": 20118, -+"id": 20162, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4", --"repository": "rhel10-AppStream" -+"name": "ruby-devel", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26820 -+"set_id": 26866 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -715427,29 +717285,32 @@ null - "s390x", - "x86_64" - ], --"id": 20119, -+"id": 20163, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4-bcmath", --"repository": "rhel10-AppStream" -+"name": "ruby-doc", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26821 -+"set_id": 26867 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -715461,29 +717322,32 @@ null - "s390x", - "x86_64" - ], --"id": 20120, -+"id": 20164, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4-cli", --"repository": "rhel10-AppStream" -+"name": "rubygem-bigdecimal", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26822 -+"set_id": 26868 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -715495,29 +717359,32 @@ null - "s390x", - "x86_64" - ], --"id": 20121, -+"id": 20165, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4-common", --"repository": "rhel10-AppStream" -+"name": "rubygem-bundler", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26823 -+"set_id": 26869 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -715529,29 +717396,32 @@ null - "s390x", - "x86_64" - ], --"id": 20122, -+"id": 20166, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4-dba", --"repository": "rhel10-AppStream" -+"name": "rubygem-io-console", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26824 -+"set_id": 26870 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -715563,29 +717433,32 @@ null - "s390x", - "x86_64" - ], --"id": 20123, -+"id": 20167, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4-dbg", --"repository": "rhel10-AppStream" -+"name": "rubygem-irb", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26825 -+"set_id": 26871 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -715597,29 +717470,32 @@ null - "s390x", - "x86_64" - ], --"id": 20124, -+"id": 20168, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4-devel", --"repository": "rhel10-AppStream" -+"name": "rubygem-json", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26826 -+"set_id": 26872 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -715631,29 +717507,32 @@ null - "s390x", - "x86_64" - ], --"id": 20125, -+"id": 20169, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4-embedded", --"repository": "rhel10-AppStream" -+"name": "rubygem-minitest", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26827 -+"set_id": 26873 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -715665,29 +717544,32 @@ null - "s390x", - "x86_64" - ], --"id": 20126, -+"id": 20170, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4-enchant", --"repository": "rhel10-AppStream" -+"name": "rubygem-mysql2", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26828 -+"set_id": 26874 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -715699,29 +717581,32 @@ null - "s390x", - "x86_64" - ], --"id": 20127, -+"id": 20171, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4-ffi", --"repository": "rhel10-AppStream" -+"name": "rubygem-mysql2-doc", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26829 -+"set_id": 26875 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -715733,29 +717618,32 @@ null - "s390x", - "x86_64" - ], --"id": 20128, -+"id": 20172, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4-fpm", --"repository": "rhel10-AppStream" -+"name": "rubygem-pg", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26830 -+"set_id": 26876 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -715767,29 +717655,32 @@ null - "s390x", - "x86_64" - ], --"id": 20129, -+"id": 20173, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4-gd", --"repository": "rhel10-AppStream" -+"name": "rubygem-pg-doc", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26831 -+"set_id": 26877 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -715801,29 +717692,32 @@ null - "s390x", - "x86_64" - ], --"id": 20130, -+"id": 20174, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4-gmp", --"repository": "rhel10-AppStream" -+"name": "rubygem-power_assert", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26832 -+"set_id": 26878 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -715835,29 +717729,32 @@ null - "s390x", - "x86_64" - ], --"id": 20131, -+"id": 20175, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4-intl", --"repository": "rhel10-AppStream" -+"name": "rubygem-psych", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26833 -+"set_id": 26879 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -715869,29 +717766,32 @@ null - "s390x", - "x86_64" - ], --"id": 20132, -+"id": 20176, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4-ldap", --"repository": "rhel10-AppStream" -+"name": "rubygem-racc", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26834 -+"set_id": 26880 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -715903,29 +717803,32 @@ null - "s390x", - "x86_64" - ], --"id": 20133, -+"id": 20177, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4-mbstring", --"repository": "rhel10-AppStream" -+"name": "rubygem-rake", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26835 -+"set_id": 26881 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -715937,29 +717840,32 @@ null - "s390x", - "x86_64" - ], --"id": 20134, -+"id": 20178, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4-mysqlnd", --"repository": "rhel10-AppStream" -+"name": "rubygem-rbs", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26836 -+"set_id": 26882 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -715971,29 +717877,32 @@ null - "s390x", - "x86_64" - ], --"id": 20135, -+"id": 20179, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4-odbc", --"repository": "rhel10-AppStream" -+"name": "rubygem-rdoc", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26837 -+"set_id": 26883 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -716005,29 +717914,32 @@ null - "s390x", - "x86_64" - ], --"id": 20136, -+"id": 20180, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4-opcache", --"repository": "rhel10-AppStream" -+"name": "rubygem-rexml", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26838 -+"set_id": 26884 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -716039,29 +717951,32 @@ null - "s390x", - "x86_64" - ], --"id": 20137, -+"id": 20181, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4-pdo", --"repository": "rhel10-AppStream" -+"name": "rubygem-rss", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26839 -+"set_id": 26885 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -716073,29 +717988,32 @@ null - "s390x", - "x86_64" - ], --"id": 20138, -+"id": 20182, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4-pgsql", --"repository": "rhel10-AppStream" -+"name": "rubygems", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26840 -+"set_id": 26886 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -716107,29 +718025,32 @@ null - "s390x", - "x86_64" - ], --"id": 20139, -+"id": 20183, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4-process", --"repository": "rhel10-AppStream" -+"name": "rubygems-devel", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26841 -+"set_id": 26887 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -716141,29 +718062,32 @@ null - "s390x", - "x86_64" - ], --"id": 20140, -+"id": 20184, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4-snmp", --"repository": "rhel10-AppStream" -+"name": "rubygem-test-unit", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26842 -+"set_id": 26888 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -716175,29 +718099,32 @@ null - "s390x", - "x86_64" - ], --"id": 20141, -+"id": 20185, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4-soap", --"repository": "rhel10-AppStream" -+"name": "rubygem-typeprof", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26843 -+"set_id": 26889 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -716209,29 +718136,32 @@ null - "s390x", - "x86_64" - ], --"id": 20142, -+"id": 20186, - "in_packageset": { - "package": [ - { - "modulestreams": [ --null -+{ -+"name": "ruby", -+"stream": "4.0" -+} - ], --"name": "php8.4-xml", --"repository": "rhel10-AppStream" -+"name": "ruby-libs", -+"repository": "rhel9-AppStream" - } - ], --"set_id": 26844 -+"set_id": 26890 - }, - "initial_release": { --"major_version": 10, --"minor_version": 1, -+"major_version": 9, -+"minor_version": 7, - "os_name": "RHEL" - }, - "modulestream_maps": [], - "out_packageset": null, - "release": { --"major_version": 10, --"minor_version": 2, -+"major_version": 9, -+"minor_version": 8, - "os_name": "RHEL" - } - }, -@@ -716243,18 +718173,18 @@ null - "s390x", - "x86_64" - ], --"id": 20143, -+"id": 20187, - "in_packageset": { - "package": [ - { - "modulestreams": [ - null - ], --"name": "php8.4-pecl-xdebug3", -+"name": "ruby4.0", - "repository": "rhel10-AppStream" - } - ], --"set_id": 26845 -+"set_id": 26891 - }, - "initial_release": { - "major_version": 10, -@@ -716277,18 +718207,18 @@ null - "s390x", - "x86_64" - ], --"id": 20144, -+"id": 20188, - "in_packageset": { - "package": [ - { - "modulestreams": [ - null - ], --"name": "php8.4-pecl-rrd", -+"name": "ruby4.0-devel", - "repository": "rhel10-AppStream" - } - ], --"set_id": 26846 -+"set_id": 26892 - }, - "initial_release": { - "major_version": 10, -@@ -716311,18 +718241,18 @@ null - "s390x", - "x86_64" - ], --"id": 20145, -+"id": 20189, - "in_packageset": { - "package": [ - { - "modulestreams": [ - null - ], --"name": "php8.4-pecl-zip", -+"name": "ruby4.0-rubygem-mysql2", - "repository": "rhel10-AppStream" - } - ], --"set_id": 26847 -+"set_id": 26893 - }, - "initial_release": { - "major_version": 10, -@@ -716345,18 +718275,18 @@ null - "s390x", - "x86_64" - ], --"id": 20146, -+"id": 20190, - "in_packageset": { - "package": [ - { - "modulestreams": [ - null - ], --"name": "php8.4-pecl-apcu", -+"name": "ruby4.0-rubygem-pg", - "repository": "rhel10-AppStream" - } - ], --"set_id": 26848 -+"set_id": 26894 - }, - "initial_release": { - "major_version": 10, -@@ -716379,18 +718309,18 @@ null - "s390x", - "x86_64" - ], --"id": 20147, -+"id": 20191, - "in_packageset": { - "package": [ - { - "modulestreams": [ - null - ], --"name": "php8.4-pecl-apcu-devel", --"repository": "rhel10-AppStream" -+"name": "ruby4.0-doc", -+"repository": "rhel10-CRB" - } - ], --"set_id": 26849 -+"set_id": 26895 - }, - "initial_release": { - "major_version": 10, -@@ -716409,22 +718339,20 @@ null - "action": 0, - "architectures": [ - "aarch64", --"ppc64le", --"s390x", - "x86_64" - ], --"id": 20148, -+"id": 20192, - "in_packageset": { - "package": [ - { - "modulestreams": [ - null - ], --"name": "php8.4-pecl-redis6", -+"name": "libkrun", - "repository": "rhel10-AppStream" - } - ], --"set_id": 26850 -+"set_id": 26896 - }, - "initial_release": { - "major_version": 10, -diff --git a/etc/leapp/files/repomap.json b/etc/leapp/files/repomap.json -index a57a04e4..57ae0690 100644 ---- a/etc/leapp/files/repomap.json -+++ b/etc/leapp/files/repomap.json -@@ -1,8 +1,8 @@ - { -- "datetime": "202602061951Z", -+ "datetime": "202604161146Z", - "version_format": "1.3.0", - "provided_data_streams": [ -- "4.2" -+ "4.3" - ], - "mapping": [ - { -@@ -272,6 +272,12 @@ - "target": [ - "rhel10-rhui-microsoft-sap-ha" - ] -+ }, -+ { -+ "source": "rhel9-rhui-google-compute-engine", -+ "target": [ -+ "rhel10-rhui-google-compute-engine-leapp" -+ ] - } - ] - } -@@ -544,6 +550,24 @@ - "distro": "rhel", - "rhui": "alibaba" - }, -+ { -+ "major_version": "10", -+ "repoid": "rhui-rhel-10-for-x86_64-baseos-e4s-rhui-rpms", -+ "arch": "x86_64", -+ "channel": "e4s", -+ "repo_type": "rpm", -+ "distro": "rhel", -+ "rhui": "google" -+ }, -+ { -+ "major_version": "10", -+ "repoid": "rhui-rhel-10-for-x86_64-baseos-rhui-rpms", -+ "arch": "x86_64", -+ "channel": "ga", -+ "repo_type": "rpm", -+ "distro": "rhel", -+ "rhui": "google" -+ }, - { - "major_version": "10", - "repoid": "rhui-rhel-10-for-x86_64-baseos-rhui-rpms", -@@ -822,6 +846,24 @@ - "distro": "rhel", - "rhui": "alibaba" - }, -+ { -+ "major_version": "10", -+ "repoid": "rhui-rhel-10-for-x86_64-appstream-e4s-rhui-rpms", -+ "arch": "x86_64", -+ "channel": "e4s", -+ "repo_type": "rpm", -+ "distro": "rhel", -+ "rhui": "google" -+ }, -+ { -+ "major_version": "10", -+ "repoid": "rhui-rhel-10-for-x86_64-appstream-rhui-rpms", -+ "arch": "x86_64", -+ "channel": "ga", -+ "repo_type": "rpm", -+ "distro": "rhel", -+ "rhui": "google" -+ }, - { - "major_version": "10", - "repoid": "rhui-rhel-10-for-x86_64-appstream-rhui-rpms", -@@ -1041,6 +1083,15 @@ - "distro": "rhel", - "rhui": "alibaba" - }, -+ { -+ "major_version": "10", -+ "repoid": "rhui-codeready-builder-for-rhel-10-x86_64-rhui-rpms", -+ "arch": "x86_64", -+ "channel": "ga", -+ "repo_type": "rpm", -+ "distro": "rhel", -+ "rhui": "google" -+ }, - { - "major_version": "10", - "repoid": "rhui-codeready-builder-for-rhel-10-x86_64-rhui-rpms", -@@ -1178,6 +1229,15 @@ - "distro": "rhel", - "rhui": "alibaba" - }, -+ { -+ "major_version": "10", -+ "repoid": "rhui-rhel-10-for-x86_64-supplementary-rhui-rpms", -+ "arch": "x86_64", -+ "channel": "ga", -+ "repo_type": "rpm", -+ "distro": "rhel", -+ "rhui": "google" -+ }, - { - "major_version": "10", - "repoid": "rhui-rhel-10-for-x86_64-supplementary-rhui-rpms", -@@ -1208,6 +1268,14 @@ - "repo_type": "rpm", - "distro": "rhel" - }, -+ { -+ "major_version": "10", -+ "repoid": "rhel-10-for-aarch64-rt-eus-rpms", -+ "arch": "aarch64", -+ "channel": "eus", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, - { - "major_version": "10", - "repoid": "rhel-10-for-aarch64-rt-rpms", -@@ -1216,6 +1284,14 @@ - "repo_type": "rpm", - "distro": "rhel" - }, -+ { -+ "major_version": "10", -+ "repoid": "rhel-10-for-ppc64le-rt-beta-rpms", -+ "arch": "ppc64le", -+ "channel": "beta", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, - { - "major_version": "10", - "repoid": "rhel-10-for-ppc64le-rt-rpms", -@@ -1224,6 +1300,14 @@ - "repo_type": "rpm", - "distro": "rhel" - }, -+ { -+ "major_version": "10", -+ "repoid": "rhel-10-for-s390x-rt-beta-rpms", -+ "arch": "s390x", -+ "channel": "beta", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, - { - "major_version": "10", - "repoid": "rhel-10-for-s390x-rt-rpms", -@@ -1248,6 +1332,14 @@ - "repo_type": "rpm", - "distro": "rhel" - }, -+ { -+ "major_version": "10", -+ "repoid": "rhel-10-for-x86_64-rt-eus-rpms", -+ "arch": "x86_64", -+ "channel": "eus", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, - { - "major_version": "10", - "repoid": "rhel-10-for-x86_64-rt-rpms", -@@ -1309,6 +1401,14 @@ - "repo_type": "rpm", - "distro": "rhel" - }, -+ { -+ "major_version": "10", -+ "repoid": "rhel-10-for-aarch64-nfv-eus-rpms", -+ "arch": "aarch64", -+ "channel": "eus", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, - { - "major_version": "10", - "repoid": "rhel-10-for-aarch64-nfv-rpms", -@@ -1317,6 +1417,14 @@ - "repo_type": "rpm", - "distro": "rhel" - }, -+ { -+ "major_version": "10", -+ "repoid": "rhel-10-for-ppc64le-nfv-beta-rpms", -+ "arch": "ppc64le", -+ "channel": "beta", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, - { - "major_version": "10", - "repoid": "rhel-10-for-ppc64le-nfv-rpms", -@@ -1325,6 +1433,14 @@ - "repo_type": "rpm", - "distro": "rhel" - }, -+ { -+ "major_version": "10", -+ "repoid": "rhel-10-for-s390x-nfv-beta-rpms", -+ "arch": "s390x", -+ "channel": "beta", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, - { - "major_version": "10", - "repoid": "rhel-10-for-s390x-nfv-rpms", -@@ -1349,6 +1465,14 @@ - "repo_type": "rpm", - "distro": "rhel" - }, -+ { -+ "major_version": "10", -+ "repoid": "rhel-10-for-x86_64-nfv-eus-rpms", -+ "arch": "x86_64", -+ "channel": "eus", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, - { - "major_version": "10", - "repoid": "rhel-10-for-x86_64-nfv-rpms", -@@ -1362,6 +1486,14 @@ - { - "pesid": "rhel10-SAP-NetWeaver", - "entries": [ -+ { -+ "major_version": "10", -+ "repoid": "rhel-10-for-aarch64-sap-netweaver-beta-rpms", -+ "arch": "aarch64", -+ "channel": "beta", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, - { - "major_version": "10", - "repoid": "rhel-10-for-aarch64-sap-netweaver-rpms", -@@ -1492,6 +1624,15 @@ - "channel": "ga", - "repo_type": "rpm", - "distro": "rhel" -+ }, -+ { -+ "major_version": "10", -+ "repoid": "rhui-rhel-10-for-x86_64-sap-netweaver-e4s-rhui-rpms", -+ "arch": "x86_64", -+ "channel": "e4s", -+ "repo_type": "rpm", -+ "distro": "rhel", -+ "rhui": "google" - } - ] - }, -@@ -1563,6 +1704,15 @@ - "channel": "ga", - "repo_type": "rpm", - "distro": "rhel" -+ }, -+ { -+ "major_version": "10", -+ "repoid": "rhui-rhel-10-for-x86_64-sap-solutions-e4s-rhui-rpms", -+ "arch": "x86_64", -+ "channel": "e4s", -+ "repo_type": "rpm", -+ "distro": "rhel", -+ "rhui": "google" - } - ] - }, -@@ -1779,6 +1929,15 @@ - "repo_type": "rpm", - "distro": "rhel" - }, -+ { -+ "major_version": "10", -+ "repoid": "rhui-rhel-10-for-x86_64-highavailability-e4s-rhui-rpms", -+ "arch": "x86_64", -+ "channel": "e4s", -+ "repo_type": "rpm", -+ "distro": "rhel", -+ "rhui": "google" -+ }, - { - "major_version": "10", - "repoid": "rhui-rhel-10-for-x86_64-highavailability-rhui-rpms", -@@ -1977,6 +2136,34 @@ - } - ] - }, -+ { -+ "pesid": "rhel10-rhui-google-compute-engine", -+ "entries": [ -+ { -+ "major_version": "10", -+ "repoid": "google-compute-engine", -+ "arch": "x86_64", -+ "channel": "ga", -+ "repo_type": "rpm", -+ "distro": "rhel", -+ "rhui": "google" -+ } -+ ] -+ }, -+ { -+ "pesid": "rhel10-rhui-google-compute-engine-leapp", -+ "entries": [ -+ { -+ "major_version": "10", -+ "repoid": "google-compute-engine-el10-for-x86_64", -+ "arch": "x86_64", -+ "channel": "ga", -+ "repo_type": "rpm", -+ "distro": "rhel", -+ "rhui": "google" -+ } -+ ] -+ }, - { - "pesid": "rhel8-BaseOS", - "entries": [ -@@ -5067,6 +5254,14 @@ - "repo_type": "rpm", - "distro": "rhel" - }, -+ { -+ "major_version": "9", -+ "repoid": "rhel-9-for-aarch64-rt-eus-rpms", -+ "arch": "aarch64", -+ "channel": "eus", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, - { - "major_version": "9", - "repoid": "rhel-9-for-aarch64-rt-rpms", -@@ -5075,6 +5270,14 @@ - "repo_type": "rpm", - "distro": "rhel" - }, -+ { -+ "major_version": "9", -+ "repoid": "rhel-9-for-ppc64le-rt-beta-rpms", -+ "arch": "ppc64le", -+ "channel": "beta", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, - { - "major_version": "9", - "repoid": "rhel-9-for-ppc64le-rt-rpms", -@@ -5083,6 +5286,14 @@ - "repo_type": "rpm", - "distro": "rhel" - }, -+ { -+ "major_version": "9", -+ "repoid": "rhel-9-for-s390x-rt-beta-rpms", -+ "arch": "s390x", -+ "channel": "beta", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, - { - "major_version": "9", - "repoid": "rhel-9-for-s390x-rt-rpms", -@@ -5107,6 +5318,14 @@ - "repo_type": "rpm", - "distro": "rhel" - }, -+ { -+ "major_version": "9", -+ "repoid": "rhel-9-for-x86_64-rt-eus-rpms", -+ "arch": "x86_64", -+ "channel": "eus", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, - { - "major_version": "9", - "repoid": "rhel-9-for-x86_64-rt-rpms", -@@ -5168,6 +5387,14 @@ - "repo_type": "rpm", - "distro": "rhel" - }, -+ { -+ "major_version": "9", -+ "repoid": "rhel-9-for-aarch64-nfv-eus-rpms", -+ "arch": "aarch64", -+ "channel": "eus", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, - { - "major_version": "9", - "repoid": "rhel-9-for-aarch64-nfv-rpms", -@@ -5176,6 +5403,14 @@ - "repo_type": "rpm", - "distro": "rhel" - }, -+ { -+ "major_version": "9", -+ "repoid": "rhel-9-for-ppc64le-nfv-beta-rpms", -+ "arch": "ppc64le", -+ "channel": "beta", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, - { - "major_version": "9", - "repoid": "rhel-9-for-ppc64le-nfv-rpms", -@@ -5184,6 +5419,14 @@ - "repo_type": "rpm", - "distro": "rhel" - }, -+ { -+ "major_version": "9", -+ "repoid": "rhel-9-for-s390x-nfv-beta-rpms", -+ "arch": "s390x", -+ "channel": "beta", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, - { - "major_version": "9", - "repoid": "rhel-9-for-s390x-nfv-rpms", -@@ -5208,6 +5451,14 @@ - "repo_type": "rpm", - "distro": "rhel" - }, -+ { -+ "major_version": "9", -+ "repoid": "rhel-9-for-x86_64-nfv-eus-rpms", -+ "arch": "x86_64", -+ "channel": "eus", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, - { - "major_version": "9", - "repoid": "rhel-9-for-x86_64-nfv-rpms", -@@ -5221,6 +5472,14 @@ - { - "pesid": "rhel9-SAP-NetWeaver", - "entries": [ -+ { -+ "major_version": "9", -+ "repoid": "rhel-9-for-aarch64-sap-netweaver-beta-rpms", -+ "arch": "aarch64", -+ "channel": "beta", -+ "repo_type": "rpm", -+ "distro": "rhel" -+ }, - { - "major_version": "9", - "repoid": "rhel-9-for-aarch64-sap-netweaver-rpms", --- -2.53.0 - diff --git a/0045-repofileutils-disable-ConfigParser-interpolation-whe.patch b/0045-repofileutils-disable-ConfigParser-interpolation-whe.patch deleted file mode 100644 index 25dcf3e..0000000 --- a/0045-repofileutils-disable-ConfigParser-interpolation-whe.patch +++ /dev/null @@ -1,72 +0,0 @@ -From 97bdaa4d90bdbb951de9d920036d93c08579bf2e Mon Sep 17 00:00:00 2001 -From: Pradeep Jagtap -Date: Fri, 17 Apr 2026 16:56:28 +0530 -Subject: [PATCH 045/105] repofileutils: disable ConfigParser interpolation - when parsing .repo files - -This occurs when parsing repository definitions containing percent-encoded -values (e.g. '%252B'in baseurls). Python's ConfigParser enables -interpolation by default and treats '%' as a special token, which causes -valid URLs to fail during parsing. - -Disable interpolation by using parse_config(..., no_interpolation=True) -so that repository values are handled literally, consistent with DNF/YUM -behavior. - -Jira-ref: RHEL-162328 ---- - .../common/libraries/repofileutils.py | 2 +- - repos/system_upgrade/common/libraries/utils.py | 14 ++++++++++++-- - 2 files changed, 13 insertions(+), 3 deletions(-) - -diff --git a/repos/system_upgrade/common/libraries/repofileutils.py b/repos/system_upgrade/common/libraries/repofileutils.py -index d8e087c1..4518b091 100644 ---- a/repos/system_upgrade/common/libraries/repofileutils.py -+++ b/repos/system_upgrade/common/libraries/repofileutils.py -@@ -48,7 +48,7 @@ def parse_repofile(repofile): - """ - data = [] - with open(repofile, mode='r') as fp: -- cp = utils.parse_config(fp, strict=False) -+ cp = utils.parse_config(fp, strict=False, no_interpolation=True) - for repoid in cp.sections(): - try: - data.append(_parse_repository(repoid, dict(cp.items(repoid)))) -diff --git a/repos/system_upgrade/common/libraries/utils.py b/repos/system_upgrade/common/libraries/utils.py -index b7aa9c74..bd5337c5 100644 ---- a/repos/system_upgrade/common/libraries/utils.py -+++ b/repos/system_upgrade/common/libraries/utils.py -@@ -10,7 +10,7 @@ from leapp.libraries.stdlib import api, CalledProcessError, config, run, STDOUT - from leapp.utils.deprecation import deprecated - - --def parse_config(cfg=None, strict=True): -+def parse_config(cfg=None, strict=True, no_interpolation=False): - """ - Applies a workaround to parse a config file using py3 AND py2 - -@@ -18,10 +18,20 @@ def parse_config(cfg=None, strict=True): - the old ones (Py2) obsoletes, these function was created to use the - ConfigParser on Py2 and Py3 - -+ When no_interpolation is True, values are read literally. Use this for -+ inputs that may contain percent-encoded URLs (e.g. yum .repo baseurl), -+ since default ConfigParser treats ``%`` as interpolation syntax. -+ - :type cfg: str - :type strict: bool -+ :type no_interpolation: bool - """ -- if six.PY3: -+ if no_interpolation: -+ if six.PY3: -+ parser = six.moves.configparser.RawConfigParser(strict=strict) # pylint: disable=unexpected-keyword-arg -+ else: -+ parser = six.moves.configparser.RawConfigParser() -+ elif six.PY3: - parser = six.moves.configparser.ConfigParser(strict=strict) # pylint: disable=unexpected-keyword-arg - else: - parser = six.moves.configparser.ConfigParser() --- -2.54.0 - diff --git a/0046-Drop-six-dependency-and-simplify-config-parsing-to-P.patch b/0046-Drop-six-dependency-and-simplify-config-parsing-to-P.patch deleted file mode 100644 index 8d046c7..0000000 --- a/0046-Drop-six-dependency-and-simplify-config-parsing-to-P.patch +++ /dev/null @@ -1,157 +0,0 @@ -From 4ccbb21ba2fec74400ad846e41248a0606fd256e Mon Sep 17 00:00:00 2001 -From: Pradeep Jagtap -Date: Mon, 20 Apr 2026 14:15:50 +0530 -Subject: [PATCH 046/105] Drop six dependency and simplify config parsing to - Python 3 native configparser - -- Replaced six.moves.configparser with standard configparser -- Removed Python 2 compatibility code paths -- Simplified string/file handling using read_string and read_file -- Updated parse_config implementation for Python 3 only ---- - .../libraries/tests/test_repofileutils.py | 20 +++++++++ - .../tests/test_utils_parse_config.py | 30 +++++++++++++ - .../system_upgrade/common/libraries/utils.py | 42 +++++++------------ - 3 files changed, 65 insertions(+), 27 deletions(-) - create mode 100644 repos/system_upgrade/common/libraries/tests/test_utils_parse_config.py - -diff --git a/repos/system_upgrade/common/libraries/tests/test_repofileutils.py b/repos/system_upgrade/common/libraries/tests/test_repofileutils.py -index d161e4bc..f9fb5623 100644 ---- a/repos/system_upgrade/common/libraries/tests/test_repofileutils.py -+++ b/repos/system_upgrade/common/libraries/tests/test_repofileutils.py -@@ -75,3 +75,23 @@ def test_parse_repofile(): - repos_duplicate = [repo for repo in repofile.data if repo.repoid == 'duplicate'] - assert len(repos_duplicate) == 1 # only one instance got through - assert repos_duplicate[0].name == 'Duplicate 2' # and it's the latter one -+ -+ -+def test_parse_repofile_preserves_url_encoded_characters(tmp_path): -+ """Percent signs from URL encoding must not be treated as config interpolation.""" -+ repo_path = tmp_path / 'urlencoded.repo' -+ expected_baseurl = ( -+ 'https://cdn.example.com/content/dist/rhel8/%24releasever/x86_64/os/' -+ '?token=foo%2Fbar%3Dbaz' -+ ) -+ repo_path.write_text( -+ '[urlencoded]\n' -+ 'name=URL-encoded baseurl\n' -+ 'baseurl={}\n' -+ 'enabled=0\n'.format(expected_baseurl), -+ encoding='utf-8', -+ ) -+ -+ repofile = repofileutils.parse_repofile(str(repo_path)) -+ repo = next(r for r in repofile.data if r.repoid == 'urlencoded') -+ assert repo.baseurl == expected_baseurl -diff --git a/repos/system_upgrade/common/libraries/tests/test_utils_parse_config.py b/repos/system_upgrade/common/libraries/tests/test_utils_parse_config.py -new file mode 100644 -index 00000000..64c9e735 ---- /dev/null -+++ b/repos/system_upgrade/common/libraries/tests/test_utils_parse_config.py -@@ -0,0 +1,30 @@ -+from configparser import InterpolationSyntaxError -+ -+import pytest -+ -+from leapp.libraries.common import utils -+ -+ -+def test_parse_config_no_interpolation_preserves_url_encoding(): -+ """Values with URL-encoded sequences must be read literally (no % interpolation).""" -+ cfg_text = ( -+ '[repo]\n' -+ 'name=test\n' -+ 'baseurl=https://example.com/path%20with%20spaces/repo%3Fquery=1\n' -+ ) -+ parser = utils.parse_config(cfg_text, strict=False, no_interpolation=True) -+ assert parser.get('repo', 'baseurl') == ( -+ 'https://example.com/path%20with%20spaces/repo%3Fquery=1' -+ ) -+ -+ -+def test_parse_config_interpolation_rejects_bare_percent_sequences(): -+ """Default ConfigParser treats '%' as interpolation; invalid sequences must raise.""" -+ cfg_text = ( -+ '[repo]\n' -+ 'name=test\n' -+ 'baseurl=https://example.com/path%20with%20spaces/repo\n' -+ ) -+ parser = utils.parse_config(cfg_text, strict=False, no_interpolation=False) -+ with pytest.raises(InterpolationSyntaxError): -+ parser.get('repo', 'baseurl') -diff --git a/repos/system_upgrade/common/libraries/utils.py b/repos/system_upgrade/common/libraries/utils.py -index bd5337c5..2c9aaee9 100644 ---- a/repos/system_upgrade/common/libraries/utils.py -+++ b/repos/system_upgrade/common/libraries/utils.py -@@ -1,8 +1,7 @@ - import functools - import os - import sys -- --import six -+from configparser import ConfigParser, RawConfigParser - - from leapp.exceptions import StopActorExecutionError - from leapp.libraries.common import mounting -@@ -12,44 +11,33 @@ from leapp.utils.deprecation import deprecated - - def parse_config(cfg=None, strict=True, no_interpolation=False): - """ -- Applies a workaround to parse a config file using py3 AND py2 -+ Parse a config input. - -- ConfigParser has a new def to read strings/files in Py3, making -- the old ones (Py2) obsoletes, these function was created to use the -- ConfigParser on Py2 and Py3 -+ When ``no_interpolation`` is True, ``RawConfigParser`` is used to read -+ values literally. This is useful for inputs that may contain URLs with -+ '%' characters for URL encoding (e.g. yum .repo baseurl). - -- When no_interpolation is True, values are read literally. Use this for -- inputs that may contain percent-encoded URLs (e.g. yum .repo baseurl), -- since default ConfigParser treats ``%`` as interpolation syntax. -+ When set to False, the default ``ConfigParser`` is used, where '%' is treated -+ as interpolation syntax and must be escaped (%%) if used literally. - -- :type cfg: str -+ :param cfg: Config data as a string or a file-like object -+ :type cfg: str or file-like object -+ :param strict: Enable strict parsing (no duplicate sections/options) - :type strict: bool -+ :param no_interpolation: Disable interpolation and read values literally - :type no_interpolation: bool - """ - if no_interpolation: -- if six.PY3: -- parser = six.moves.configparser.RawConfigParser(strict=strict) # pylint: disable=unexpected-keyword-arg -- else: -- parser = six.moves.configparser.RawConfigParser() -- elif six.PY3: -- parser = six.moves.configparser.ConfigParser(strict=strict) # pylint: disable=unexpected-keyword-arg -+ parser = RawConfigParser(strict=strict) - else: -- parser = six.moves.configparser.ConfigParser() -+ parser = ConfigParser(strict=strict) - - # we do not handle exception here, handle with it when these function is called -- if cfg and six.PY3: -- # Python 3 -- if isinstance(cfg, six.string_types): -+ if cfg: -+ if isinstance(cfg, str): - parser.read_string(cfg) - else: - parser.read_file(cfg) -- elif cfg: -- # Python 2 -- from cStringIO import StringIO # pylint: disable=import-outside-toplevel -- if isinstance(cfg, six.string_types): -- parser.readfp(StringIO(cfg)) # pylint: disable=deprecated-method -- else: -- parser.readfp(cfg) # pylint: disable=deprecated-method - return parser - - --- -2.54.0 - diff --git a/0047-mounting-ignore-failures-when-callling-mount-a.patch b/0047-mounting-ignore-failures-when-callling-mount-a.patch deleted file mode 100644 index 8c78fb9..0000000 --- a/0047-mounting-ignore-failures-when-callling-mount-a.patch +++ /dev/null @@ -1,102 +0,0 @@ -From 966e7854ec63a977656743b53b1b2811f255a006 Mon Sep 17 00:00:00 2001 -From: Michal Hecko -Date: Thu, 16 Apr 2026 17:57:17 +0200 -Subject: [PATCH 047/105] mounting: ignore failures when callling mount -a - -The `mount -a` calls are implemented just as a safeguard. However, the -semantics of `nofail` as seen by `mount` are different as the ones seen -by `systemd` in that they can result in a non-zero exit code e.g. if the -block device does not contain a valid filesystem. In contrast, systemd -threads `nofail` so that the unit does not fail (even if the filesystem -is broken). This patch adds error handling that ignores non-zero exit -codes. - -Jira-ref: RHEL-56176 ---- - .../files/dracut/85sys-upgrade-redhat/do-upgrade.sh | 8 +++++--- - .../libraries/removeupgradebootentry.py | 13 ++++++++++--- - .../libraries/removeupgradeefientry.py | 11 ++++++++++- - 3 files changed, 25 insertions(+), 7 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/do-upgrade.sh b/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/do-upgrade.sh -index 683131ec..41eb1625 100755 ---- a/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/do-upgrade.sh -+++ b/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/do-upgrade.sh -@@ -236,7 +236,8 @@ do_upgrade() { - # NOTE: in case we would need to run leapp before pivot, we would need to - # specify where the root is, e.g. --root=/sysroot - # TODO: update: systemd-nspawn -- /usr/bin/systemd-nspawn $NSPAWN_OPTS -D "$NEWROOT" /usr/bin/bash -c "mount -a; $LEAPPBIN upgrade --resume $args" -+ # shellcheck disable=SC2086 # The NSPAWN_OPTS and args variables are not quoted since they are supposed to expand into multiple arguments -+ /usr/bin/systemd-nspawn $NSPAWN_OPTS -D "$NEWROOT" /usr/bin/bash -c "mount -a || : ; $LEAPPBIN upgrade --resume $args" - rv=$? - - # NOTE: flush the cached content to disk to ensure everything is written -@@ -272,7 +273,8 @@ do_upgrade() { - # all FSTAB partitions. As mount was working before, hopefully will - # work now as well. Later this should be probably modified as we will - # need to handle more stuff around storage at all. -- /usr/bin/systemd-nspawn $NSPAWN_OPTS -D "$NEWROOT" /usr/bin/bash -c "mount -a; /usr/bin/python3 -B $LEAPP3_BIN upgrade --resume $args" -+ # shellcheck disable=SC2086 # The NSPAWN_OPTS and args variables are not quoted since they are supposed to expand into multiple arguments -+ /usr/bin/systemd-nspawn $NSPAWN_OPTS -D "$NEWROOT" /usr/bin/bash -c "mount -a || : ; /usr/bin/python3 -B $LEAPP3_BIN upgrade --resume $args" - rv=$? - fi - -@@ -328,7 +330,7 @@ save_journal() { - - # We need to run the actual saving of leapp-upgrade.log in a container and mount everything before, to be - # sure /var/log is mounted in case it is on a separate partition. -- local store_cmd="mount -a" -+ local store_cmd="mount -a || : " - local store_cmd="$store_cmd; cat /tmp-leapp-upgrade.log >> /var/log/leapp/leapp-upgrade.log" - - /usr/bin/systemd-nspawn $NSPAWN_OPTS -D "$NEWROOT" /usr/bin/bash -c "$store_cmd" -diff --git a/repos/system_upgrade/common/actors/removeupgradebootentry/libraries/removeupgradebootentry.py b/repos/system_upgrade/common/actors/removeupgradebootentry/libraries/removeupgradebootentry.py -index 7434e48c..71be42c0 100644 ---- a/repos/system_upgrade/common/actors/removeupgradebootentry/libraries/removeupgradebootentry.py -+++ b/repos/system_upgrade/common/actors/removeupgradebootentry/libraries/removeupgradebootentry.py -@@ -45,9 +45,16 @@ def remove_boot_entry(): - - # TODO: Move calling `mount -a` to a separate actor as it is not really related to removing the upgrade boot entry. - # It's worth to call it after removing the boot entry to avoid boot loop in case mounting fails. -- run([ -- '/bin/mount', '-a' -- ]) -+ try: -+ run(['/bin/mount', '-a']) -+ except CalledProcessError as err: -+ # Every mount that is not marked with 'nofail' should be mounted when this code is executed, so mount -a can -+ # fail only on corrupted nofail devices. We ignore errors here so that our mount -a behaves consistently -+ # with systemd's mount units marked with 'nofail' -- failures of such units will not prevent the system from -+ # booting. If a mount mounts a block dev with a corrupted FS, then the system could not have used the device, -+ # so it does not contain anything important for the upgrade. -+ api.current_logger().warning('Failed to execute `mount -a` with the following error: {}.'.format(err)) -+ pass - - - def get_upgrade_kernel_filepath(): -diff --git a/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/libraries/removeupgradeefientry.py b/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/libraries/removeupgradeefientry.py -index 7e2972e5..0e2346f0 100644 ---- a/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/libraries/removeupgradeefientry.py -+++ b/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/libraries/removeupgradeefientry.py -@@ -55,7 +55,16 @@ def remove_upgrade_efi_entry(): - # TODO: Move calling `mount -a` to a separate actor as it is not really - # related to removing the upgrade boot entry. It's worth to call it after - # removing the boot entry to avoid boot loop in case mounting fails. -- run(['/bin/mount', '-a']) -+ try: -+ run(['/bin/mount', '-a']) -+ except CalledProcessError as err: -+ # Every mount that is not marked with 'nofail' should be mounted when this code is executed, so mount -a can -+ # fail only on corrupted nofail devices. We ignore errors here so that our mount -a behaves consistently -+ # with systemd's mount units marked with 'nofail' -- failures of such units will not prevent the system from -+ # booting. If a mount mounts a block dev with a corrupted FS, then the system could not have used the device, -+ # so it does not contain anything important for the upgrade. -+ api.current_logger().warning('Failed to execute `mount -a` with the following error: {}.'.format(err)) -+ pass - - - def _remove_upgrade_blsdir(bootloader_info): --- -2.54.0 - diff --git a/0048-mount_unit_generator-do-not-make-nofail-mounts-requi.patch b/0048-mount_unit_generator-do-not-make-nofail-mounts-requi.patch deleted file mode 100644 index 907fdb1..0000000 --- a/0048-mount_unit_generator-do-not-make-nofail-mounts-requi.patch +++ /dev/null @@ -1,142 +0,0 @@ -From 417ef75d311af2506c4ba568c43698d949479169 Mon Sep 17 00:00:00 2001 -From: Michal Hecko -Date: Tue, 21 Apr 2026 11:30:19 +0200 -Subject: [PATCH 048/105] mount_unit_generator: do not make nofail mounts - required - ---- - .../libraries/mount_unit_generator.py | 28 ++++++++++++++++++- - .../tests/files/unit-nofail.mount | 15 ++++++++++ - .../tests/files/unit.mount | 14 ++++++++++ - .../tests/test_mount_unit_generation.py | 17 +++++++++++ - 4 files changed, 73 insertions(+), 1 deletion(-) - create mode 100644 repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/files/unit-nofail.mount - create mode 100644 repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/files/unit.mount - -diff --git a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/libraries/mount_unit_generator.py b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/libraries/mount_unit_generator.py -index e3070986..900132f2 100644 ---- a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/libraries/mount_unit_generator.py -+++ b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/libraries/mount_unit_generator.py -@@ -30,7 +30,7 @@ def run_systemd_fstab_generator(output_directory): - details = {'details': str(error)} - raise StopActorExecutionError( - 'Failed to generate mount units using systemd-fstab-generator', -- details -+ details=details - ) - - api.current_logger().debug( -@@ -110,6 +110,27 @@ def prefix_all_mount_units_with_sysroot(dir_containing_units): - api.current_logger().debug('Original mount unit {} removed.'.format(unit_file_path)) - - -+def is_unit_marked_with_nofail(unit_path: str) -> bool: -+ try: -+ with open(unit_path) as in_file: -+ lines = [line.strip() for line in in_file.readlines()] -+ except OSError: -+ api.current_logger().debug( -+ 'Could not read the unit {} to determine whether it is nofail.'.format(unit_path) -+ ) -+ return False # This way, the unit will end up in target's '.requires', so it is a safe choice -+ -+ for line in lines: -+ if line.startswith('Options'): -+ line_fragments = line.split('=', 1) -+ if len(line_fragments) <= 1: -+ continue # Should not happen -+ used_options = [opt.strip() for opt in line_fragments[1].split(',')] -+ if 'nofail' in used_options: -+ return True -+ return False -+ -+ - def _fix_symlinks_in_dir(dir_containing_mount_units, target_dir): - """ - Fix broken symlinks in given target_dir due to us modifying (renaming) the mount units. -@@ -137,11 +158,16 @@ def _fix_symlinks_in_dir(dir_containing_mount_units, target_dir): - os.mkdir(target_dir_path) - - api.current_logger().debug('Populating {} with new symlinks.'.format(target_dir)) -+ will_units_be_required = target_dir.endswith('.requires') - - for unit_file in os.listdir(dir_containing_mount_units): - if not unit_file.endswith('.mount'): - continue - -+ is_nofail = is_unit_marked_with_nofail(os.path.join(dir_containing_mount_units, unit_file)) -+ if is_nofail and will_units_be_required: -+ continue -+ - place_fastlink_at = os.path.join(target_dir_path, unit_file) - fastlink_points_to = os.path.join('../', unit_file) - try: -diff --git a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/files/unit-nofail.mount b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/files/unit-nofail.mount -new file mode 100644 -index 00000000..b87ec340 ---- /dev/null -+++ b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/files/unit-nofail.mount -@@ -0,0 +1,15 @@ -+# Automatically generated by systemd-fstab-generator -+ -+[Unit] -+Documentation=man:fstab(5) man:systemd-fstab-generator(8) -+SourcePath=/etc/fstab -+Before=local-fs.target -+Requires=systemd-fsck@dev-disk-by\x2duuid-1ca63aee\x2d37b5\x2d42b7\x2db1a5\x2df752ba09e010.service -+After=systemd-fsck@dev-disk-by\x2duuid-1ca63aee\x2d37b5\x2d42b7\x2db1a5\x2df752ba09e010.service -+After=blockdev@dev-disk-by\x2duuid-1ca63aee\x2d37b5\x2d42b7\x2db1a5\x2df752ba09e010.target -+ -+[Mount] -+What=/dev/disk/by-uuid/1ca63aee-37b5-42b7-b1a5-f752ba09e010 -+Where=/boot -+Type=ext4 -+Options=nofail,noexec -diff --git a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/files/unit.mount b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/files/unit.mount -new file mode 100644 -index 00000000..ad1994e3 ---- /dev/null -+++ b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/files/unit.mount -@@ -0,0 +1,14 @@ -+# Automatically generated by systemd-fstab-generator -+ -+[Unit] -+Documentation=man:fstab(5) man:systemd-fstab-generator(8) -+SourcePath=/etc/fstab -+Before=local-fs.target -+Requires=systemd-fsck@dev-disk-by\x2duuid-1ca63aee\x2d37b5\x2d42b7\x2db1a5\x2df752ba09e010.service -+After=systemd-fsck@dev-disk-by\x2duuid-1ca63aee\x2d37b5\x2d42b7\x2db1a5\x2df752ba09e010.service -+After=blockdev@dev-disk-by\x2duuid-1ca63aee\x2d37b5\x2d42b7\x2db1a5\x2df752ba09e010.target -+ -+[Mount] -+What=/dev/disk/by-uuid/1ca63aee-37b5-42b7-b1a5-f752ba09e010 -+Where=/boot -+Type=ext4 -diff --git a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/test_mount_unit_generation.py b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/test_mount_unit_generation.py -index eb90a75d..99f26ad1 100644 ---- a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/test_mount_unit_generation.py -+++ b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/test_mount_unit_generation.py -@@ -326,3 +326,20 @@ def test_injection_of_sysroot_boot_bindmount_unit(monkeypatch, has_separate_boot - - if has_separate_boot: - assert was_copyfile_for_sysroot_boot_called -+ -+ -+TEST_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'files') -+ -+ -+@pytest.mark.parametrize( -+ ('unit_filename', 'is_nofail'), -+ [ -+ ('unit.mount', False), -+ ('unit-nofail.mount', True), -+ ('non-existing-unit.mount', False), # This file does not exist in the files/ -+ ] -+) -+def test_is_unit_marked_with_nofail(unit_filename, is_nofail): -+ unit_path = os.path.join(TEST_DIR, unit_filename) -+ determined_no_fail = mount_unit_generator.is_unit_marked_with_nofail(unit_path) -+ assert determined_no_fail == is_nofail --- -2.54.0 - diff --git a/0049-leapp_resume.service-use-multi-user.target-instead-o.patch b/0049-leapp_resume.service-use-multi-user.target-instead-o.patch deleted file mode 100644 index df2f4b2..0000000 --- a/0049-leapp_resume.service-use-multi-user.target-instead-o.patch +++ /dev/null @@ -1,133 +0,0 @@ -From d41629259c916a1205eccbafa3b03884cff5949f Mon Sep 17 00:00:00 2001 -From: Peter Mocary -Date: Mon, 27 Apr 2026 09:03:57 +0200 -Subject: [PATCH 049/105] leapp_resume.service: use multi-user.target instead - of default.target - -When selinux autorelabel is triggered, the selinux-autorelabel-generator -links default.target to selinux-autorelabel.target. During boot, -the relabel is performed and then the system is rebooted, resulting in -an intermediate reboot before the system fully starts. - -Since leapp_resume.service was linked into default.target.wants/ and -ordered After=default.target, it was started during this intermediate -autorelabel boot. When the relabel finished and the reboot was -triggered, leapp_resume.service was terminated, preventing post-upgrade -tasks from completing. The service was then rerun on the next boot. - -This patch makes leapp_resume.service rely on multi-user.target instead -of the default.target which is not reached during the autorelabel boot, -ensuring leapp_resume.service only runs on the actual first boot into -the upgraded OS. - -Jira: RHEL-60060 ---- - .../common/actors/createresumeservice/actor.py | 13 ++++++++----- - .../createresumeservice/files/leapp_resume.service | 2 +- - .../tests/test_createresumeservice.py | 13 +++++-------- - .../common/actors/removeresumeservice/actor.py | 13 ++++++++----- - 4 files changed, 22 insertions(+), 19 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/createresumeservice/actor.py b/repos/system_upgrade/common/actors/createresumeservice/actor.py -index 8ac07a06..d7a0a5bc 100644 ---- a/repos/system_upgrade/common/actors/createresumeservice/actor.py -+++ b/repos/system_upgrade/common/actors/createresumeservice/actor.py -@@ -22,18 +22,21 @@ class CreateSystemdResumeService(Actor): - tags = (FinalizationPhaseTag, IPUWorkflowTag) - - def process(self): -- service_name = 'leapp_resume.service' - systemd_dir = '/etc/systemd/system' -+ service_name = 'leapp_resume.service' -+ target_name = 'multi-user.target' - - service_templ_fpath = self.get_file_path(service_name) - shutil.copyfile(service_templ_fpath, os.path.join(systemd_dir, service_name)) - -- service_path = '/etc/systemd/system/{}'.format(service_name) -- symlink_path = '/etc/systemd/system/default.target.wants/{}'.format(service_name) -+ target_wants_path = os.path.join(systemd_dir, '{}.wants'.format(target_name)) -+ -+ service_path = os.path.join(systemd_dir, service_name) -+ symlink_path = os.path.join(target_wants_path, service_name) - -- # in case nothing is enabled in the default target, the directory does not exist -+ # in case nothing is enabled in the target, the directory does not exist - try: -- os.mkdir(os.path.join(systemd_dir, 'default.target.wants')) -+ os.mkdir(target_wants_path) - except OSError: - pass - -diff --git a/repos/system_upgrade/common/actors/createresumeservice/files/leapp_resume.service b/repos/system_upgrade/common/actors/createresumeservice/files/leapp_resume.service -index 859237a1..749b4b84 100644 ---- a/repos/system_upgrade/common/actors/createresumeservice/files/leapp_resume.service -+++ b/repos/system_upgrade/common/actors/createresumeservice/files/leapp_resume.service -@@ -1,6 +1,6 @@ - [Unit] - Description=Temporary Leapp service which resumes execution after reboot --After=default.target -+After=multi-user.target - DefaultDependencies=no - After=dbus.service - After=network-online.target -diff --git a/repos/system_upgrade/common/actors/createresumeservice/tests/test_createresumeservice.py b/repos/system_upgrade/common/actors/createresumeservice/tests/test_createresumeservice.py -index c1cefc37..f2590291 100644 ---- a/repos/system_upgrade/common/actors/createresumeservice/tests/test_createresumeservice.py -+++ b/repos/system_upgrade/common/actors/createresumeservice/tests/test_createresumeservice.py -@@ -1,21 +1,18 @@ - import os - --import distro - import pytest - - - @pytest.mark.skipif(os.getuid() != 0, reason='User is not a root') --@pytest.mark.skipif( -- distro.id() == 'fedora', -- reason='default.target.wants does not exists on Fedora distro', --) - def test_create_resume_service(current_actor_context): -- - current_actor_context.run() - -+ systemd_dir = '/etc/systemd/system' - service_name = 'leapp_resume.service' -- service_path = '/etc/systemd/system/{}'.format(service_name) -- symlink_path = '/etc/systemd/system/default.target.wants/{}'.format(service_name) -+ target_name = 'multi-user.target' -+ -+ service_path = os.path.join(systemd_dir, service_name) -+ symlink_path = os.path.join(systemd_dir, '{}.wants'.format(target_name), service_name) - - try: - assert os.path.isfile(service_path) -diff --git a/repos/system_upgrade/common/actors/removeresumeservice/actor.py b/repos/system_upgrade/common/actors/removeresumeservice/actor.py -index 59935394..cf22ad07 100644 ---- a/repos/system_upgrade/common/actors/removeresumeservice/actor.py -+++ b/repos/system_upgrade/common/actors/removeresumeservice/actor.py -@@ -21,13 +21,16 @@ class RemoveSystemdResumeService(Actor): - tags = (FirstBootPhaseTag.After, IPUWorkflowTag) - - def process(self): -+ systemd_dir = '/etc/systemd/system' - service_name = 'leapp_resume.service' -- if os.path.isfile('/etc/systemd/system/{}'.format(service_name)): -+ target_name = 'multi-user.target' -+ -+ service_path = os.path.join(systemd_dir, service_name) -+ target_wants_path = os.path.join(systemd_dir, '{}.wants'.format(target_name), service_name) -+ -+ if os.path.isfile(service_path): - run(['systemctl', 'disable', service_name]) -- paths_to_unlink = [ -- '/etc/systemd/system/{}'.format(service_name), -- '/etc/systemd/system/default.target.wants/{}'.format(service_name), -- ] -+ paths_to_unlink = [service_path, target_wants_path] - for path in paths_to_unlink: - try: - os.unlink(path) --- -2.54.0 - diff --git a/0050-LiveMode-update-do-upgrade.sh-to-reflect-upstream-ch.patch b/0050-LiveMode-update-do-upgrade.sh-to-reflect-upstream-ch.patch deleted file mode 100644 index 6684d7e..0000000 --- a/0050-LiveMode-update-do-upgrade.sh-to-reflect-upstream-ch.patch +++ /dev/null @@ -1,334 +0,0 @@ -From 985cc58bb3942d5061b92c84bcfe93788e80bca6 Mon Sep 17 00:00:00 2001 -From: Peter Mocary -Date: Wed, 22 Apr 2026 14:01:23 +0200 -Subject: [PATCH 050/105] LiveMode: update do-upgrade.sh to reflect upstream - changes - -- treat the existence of .leapp_upgrade_failed file the same as in the default solution. Reflects changes from 1f8b8f3. -- updates the regex used to extract OS major version so that it is able to extract version without a minor version. Reflects changes from 64ec2ec. -- ignore failures when calling mount -a, makes mount treat nofail option same as systemd. Reflects changes from 966e785. - -Jira: RHEL-104384, RHEL-56040 ---- - .../dracut/85sys-upgrade-redhat/do-upgrade.sh | 10 +- - .../files/do-upgrade.sh | 184 ++---------------- - 2 files changed, 19 insertions(+), 175 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/do-upgrade.sh b/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/do-upgrade.sh -index 41eb1625..7d470459 100755 ---- a/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/do-upgrade.sh -+++ b/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/do-upgrade.sh -@@ -28,6 +28,7 @@ export RHEL_OS_MAJOR_RELEASE - export LEAPPBIN=/usr/bin/leapp - export LEAPPHOME=/root/tmp_leapp_py3 - export LEAPP3_BIN=$LEAPPHOME/leapp3 -+export LEAPP_FAILED_FLAG_FILE="/root/tmp_leapp_py3/.leapp_upgrade_failed" - - export NEWROOT=${NEWROOT:-"/sysroot"} - -@@ -46,7 +47,6 @@ fi - export NSPAWN_OPTS="$NSPAWN_OPTS --keep-unit --register=no --timezone=off --resolv-conf=off" - - --export LEAPP_FAILED_FLAG_FILE="/root/tmp_leapp_py3/.leapp_upgrade_failed" - - # - # Temp for collecting and preparing tarball -@@ -249,10 +249,6 @@ do_upgrade() { - } - - if [ "$rv" -eq 0 ]; then -- # run leapp to proceed phases after the upgrade with Python3 -- #PY_LEAPP_PATH=/usr/lib/python2.7/site-packages/leapp/ -- #$NEWROOT/bin/systemd-nspawn $NSPAWN_OPTS -D $NEWROOT -E PYTHONPATH="${PYTHONPATH}:${PY_LEAPP_PATH}" /usr/bin/python3 $LEAPPBIN upgrade --resume $args -- - # on aarch64 systems during el8 to el9 upgrades the swap is broken due to change in page size (64K to 4k) - # adjust the page size before booting into the new system, as it is possible the swap is necessary for to boot - # `arch` command is not available in the dracut shell, using uname -m instead -@@ -287,7 +283,7 @@ do_upgrade() { - - echo >&2 "Creating file $NEWROOT$LEAPP_FAILED_FLAG_FILE" - echo >&2 "Warning: Leapp upgrade failed and there is an issue blocking the upgrade." -- echo >&2 "Please file a support case with /var/log/leapp/leapp-upgrade.log attached" -+ echo >&2 "Please file a support case with /var/log/leapp/leapp-upgrade.log attached." - - "$NEWROOT/bin/touch" "$NEWROOT$LEAPP_FAILED_FLAG_FILE" - fi -@@ -380,7 +376,7 @@ mount -o "remount,rw" "$NEWROOT" - ) - result=$? - --##### safe the data and remount $NEWROOT as it was previously mounted ##### -+##### save the data and remount $NEWROOT as it was previously mounted ##### - save_journal - - # NOTE: For debugging purposis. It's possible it will be changed in future. -diff --git a/repos/system_upgrade/common/actors/livemode/modify_userspace_for_livemode/files/do-upgrade.sh b/repos/system_upgrade/common/actors/livemode/modify_userspace_for_livemode/files/do-upgrade.sh -index 51ebddb5..6f2e37ef 100755 ---- a/repos/system_upgrade/common/actors/livemode/modify_userspace_for_livemode/files/do-upgrade.sh -+++ b/repos/system_upgrade/common/actors/livemode/modify_userspace_for_livemode/files/do-upgrade.sh -@@ -7,13 +7,13 @@ warn() { - - get_rhel_major_release() { - local os_version -- os_version=$(grep -o '^VERSION="[0-9][0-9]*\.' /etc/os-release | grep -o '[0-9]*') -+ os_version=$(grep -o '^VERSION="[0-9][0-9]*' /etc/os-release | grep -o '[0-9]*') - [ -z "$os_version" ] && { - # This should not happen as /etc/initrd-release is supposed to have API - # stability, but check is better than broken system. - warn "Cannot determine the major RHEL version." - warn "The upgrade environment cannot be setup reliably." -- echo "Content of the /etc/initrd-release:" -+ echo "Content of the /etc/os-release:" - cat /etc/os-release - exit 1 - } -@@ -26,6 +26,7 @@ export RHEL_OS_MAJOR_RELEASE - export LEAPPBIN=/usr/bin/leapp - export LEAPPHOME=/root/tmp_leapp_py3 - export LEAPP3_BIN=$LEAPPHOME/leapp3 -+export LEAPP_FAILED_FLAG_FILE="/root/tmp_leapp_py3/.leapp_upgrade_failed" - - # this was initially a dracut script, hence $NEWROOT. - # the rootfs is mounted on /run/upgrade when booted with dmsquash-live -@@ -48,155 +49,6 @@ fi - export NSPAWN_OPTS="$NSPAWN_OPTS --keep-unit --register=no --timezone=off --resolv-conf=off" - - --export LEAPP_FAILED_FLAG_FILE="/root/tmp_leapp_py3/.leapp_upgrade_failed" -- --# --# Temp for collecting and preparing tarball --# --LEAPP_DEBUG_TMP="/tmp/leapp-debug-root" -- --# --# Number of times to emit all chunks --# --# To avoid spammy parts of console log, second and later emissions --# take longer delay in-between. For example, with N being 3, --# first emission is done immediately, second after 10s, and the --# third one after 20s. --# --IBDMP_ITER=3 -- --# --# Size of one payload chunk --# --# IOW, amount of characters in a single chunk of the base64-encoded --# payload. (By base64 standard, these characters are inherently ASCII, --# so ie. they correspond to bytes.) --# --IBDMP_CHUNKSIZE=40 -- --collect_and_dump_debug_data() { -- # -- # Collect various debug files and dump tarball using ibdmp -- # -- local tmp=$LEAPP_DEBUG_TMP -- local data=$tmp/data -- mkdir -p "$data" || { echo >&2 "fatal: cannot create leapp dump data dir: $data"; exit 4; } -- journalctl -amo verbose >"$data/journalctl.log" -- mkdir -p "$data/var/lib/leapp" -- mkdir -p "$data/var/log" -- cp -vr "$NEWROOT/var/lib/leapp/leapp.db" \ -- "$data/var/lib/leapp" -- cp -vr "$NEWROOT/var/log/leapp" \ -- "$data/var/log" -- tar -cJf "$tmp/data.tar.xz" "$data" -- ibdmp "$tmp/data.tar.xz" -- rm -r "$tmp" --} -- --want_inband_dump() { -- # -- # True if dump collection is needed given leapp exit status $1 and kernel option -- # -- local leapp_es=$1 -- local mode -- local kopt -- kopt=$(getarg 'rd.upgrade.inband') -- case $kopt in -- always|never|onerror) mode="$kopt" ;; -- "") mode="never" ;; -- *) warn "ignoring unknown value of rd.upgrade.inband (dump will be disabled): '$kopt'" -- return 2 ;; -- esac -- case $mode:$leapp_es in -- always:*) return 0 ;; -- never:*) return 1 ;; -- onerror:0) return 1 ;; -- onerror:*) return 0 ;; -- esac --} -- --ibdmp() { -- # -- # Dump tarball $1 in base64 to stdout -- # -- # Tarball is encoded in a way that: -- # -- # * final data can be printed to plain text terminal, -- # * tarball can be restored by scanning the saved -- # terminal output, -- # * corruptions caused by extra terminal noise -- # (extra lines, extra characters within lines, -- # line splits..) can be corrected. -- # -- # That is, -- # -- # 1. encode tarball using base64 -- # -- # 2. prepend line `chunks=CHUNKS,md5=MD5` where -- # MD5 is the MD5 digest of original tarball and -- # CHUNKS is number of upcoming Base64 chunks -- # -- # 3. decorate each chunk with prefix `N:` where -- # N is number of given chunk. -- # -- # 4. Finally print all lines (prepended "header" -- # line and all chunks) several times, where -- # every iteration should be prefixed by -- # `_ibdmp:I/TTL|` and suffixed by `|`. -- # where `I` is iteration number and `TTL` is -- # total iteration numbers. -- # -- # Decoder should look for strings like this: -- # -- # _ibdmp:I/J|CN:PAYLOAD| -- # -- # where I, J and CN are integers and PAYLOAD is a slice of a -- # base64 string. -- # -- # Here, I represents number of iteration, J total of iterations -- # ($IBDMP_ITER), and CN is number of given chunk within this -- # iteration. CN goes from 1 up to number of chunks (CHUNKS) -- # predicted by header. -- # -- # Each set corresponds to one dump of the tarball and error -- # correction is achieved by merging sets using these rules: -- # -- # 1. each set has to contain header (`chunks=CHUNKS, -- # md5=MD5`) prevalent header wins. -- # -- # 2. each set has to contain number of chunks -- # as per header -- # -- # 3. chunks are numbered so they can be compared across -- # sets; prevalent chunk wins. -- # -- # Finally the merged set of chunks is decoded as base64. -- # Resulting data has to match md5 sum or we're hosed. -- # -- local tarball=$1 -- local tmp=$LEAPP_DEBUG_TMP/ibdmp -- local md5 -- local i -- mkdir -p "$tmp" -- base64 -w "$IBDMP_CHUNKSIZE" "$tarball" > "$tmp/b64" -- md5=$(md5sum "$tarball" | sed 's/ .*//') -- chunks=$(wc -l <"$tmp/b64") -- ( -- set +x -- echo "chunks=$chunks,md5=$md5" -- cnum=1 -- while read -r chunk; do -- echo "$cnum:$chunk" -- ((cnum++)) -- done <"$tmp/b64" -- ) >"$tmp/report" -- i=0 -- while test "$i" -lt "$IBDMP_ITER"; do -- sleep "$((i * 10))" -- ((i++)) -- sed "s%^%_ibdmp:$i/$IBDMP_ITER|%; s%$%|%; " <"$tmp/report" -- done --} - - do_upgrade() { - local args="" rv=0 -@@ -218,7 +70,7 @@ do_upgrade() { - - # NOTE: We disable shell-check since we want to word-break NSPAWN_OPTS - # shellcheck disable=SC2086 -- /usr/bin/systemd-nspawn $NSPAWN_OPTS -D "$NEWROOT" /usr/bin/bash -c "mount -a; $LEAPPBIN upgrade --resume $args" -+ /usr/bin/systemd-nspawn $NSPAWN_OPTS -D "$NEWROOT" /usr/bin/bash -c "mount -a || : ; $LEAPPBIN upgrade --resume $args" - rv=$? - - # NOTE: flush the cached content to disk to ensure everything is written -@@ -227,10 +79,6 @@ do_upgrade() { - ## TODO: implement "Break after LEAPP upgrade stop" - - if [ "$rv" -eq 0 ]; then -- # run leapp to proceed phases after the upgrade with Python3 -- #PY_LEAPP_PATH=/usr/lib/python2.7/site-packages/leapp/ -- #$NEWROOT/bin/systemd-nspawn $NSPAWN_OPTS -D $NEWROOT -E PYTHONPATH="${PYTHONPATH}:${PY_LEAPP_PATH}" /usr/bin/python3 $LEAPPBIN upgrade --resume $args -- - # on aarch64 systems during el8 to el9 upgrades the swap is broken due to change in page size (64K to 4k) - # adjust the page size before booting into the new system, as it is possible the swap is necessary for to boot - # `arch` command is not available in the dracut shell, using uname -m instead -@@ -255,7 +103,7 @@ do_upgrade() { - - # NOTE: We disable shell-check since we want to word-break NSPAWN_OPTS - # shellcheck disable=SC2086 -- /usr/bin/systemd-nspawn $NSPAWN_OPTS -D "$NEWROOT" /usr/bin/bash -c "mount -a; /usr/bin/python3 -B $LEAPP3_BIN upgrade --resume $args" -+ /usr/bin/systemd-nspawn $NSPAWN_OPTS -D "$NEWROOT" /usr/bin/bash -c "mount -a || : ; /usr/bin/python3 -B $LEAPP3_BIN upgrade --resume $args" - rv=$? - fi - -@@ -265,14 +113,14 @@ do_upgrade() { - local dirname - dirname="$("$NEWROOT/bin/dirname" "$NEWROOT$LEAPP_FAILED_FLAG_FILE")" - [ -d "$dirname" ] || mkdir "$dirname" -+ -+ echo >&2 "Creating file $NEWROOT$LEAPP_FAILED_FLAG_FILE" -+ echo >&2 "Warning: Leapp upgrade failed and there is an issue blocking the upgrade." -+ echo >&2 "Please file a support case with /var/log/leapp/leapp-upgrade.log attached." -+ - "$NEWROOT/bin/touch" "$NEWROOT$LEAPP_FAILED_FLAG_FILE" - fi - -- # Dump debug data in case something went wrong -- ##if want_inband_dump "$rv"; then -- ## collect_and_dump_debug_data -- ##fi -- - # NOTE: THIS SHOULD BE AGAIN PART OF LEAPP IDEALLY - ## backup old product id certificates - #chroot $NEWROOT /bin/sh -c 'mkdir /etc/pki/product_old; mv -f /etc/pki/product/*.pem /etc/pki/product_old/' -@@ -306,7 +154,7 @@ save_journal() { - - # We need to run the actual saving of leapp-upgrade.log in a container and mount everything before, to be - # sure /var/log is mounted in case it is on a separate partition. -- local store_cmd="mount -a" -+ local store_cmd="mount -a || : " - local store_cmd="$store_cmd; cat /tmp-leapp-upgrade.log >> /var/log/leapp/leapp-upgrade.log" - - # NOTE: We disable shell-check since we want to word-break NSPAWN_OPTS -@@ -333,10 +181,10 @@ awk '{print $1}' /proc/cmdline \ - # check if leapp previously failed in the initramfs, if it did return to the emergency shell - [ -f "$NEWROOT$LEAPP_FAILED_FLAG_FILE" ] && { - echo >&2 "Found file $NEWROOT$LEAPP_FAILED_FLAG_FILE" -- echo >&2 "Error: Leapp previously failed and cannot continue, returning back to emergency shell" -- echo >&2 "Please file a support case with $NEWROOT/var/log/leapp/leapp-upgrade.log attached" -- echo >&2 "To rerun the upgrade upon exiting the dracut shell remove the $NEWROOT$LEAPP_FAILED_FLAG_FILE file" -- exit 1 -+ echo >&2 "Warning: Leapp failed on a previous execution and something might be blocking the upgrade." -+ echo >&2 "Continuing with the upgrade anyway. Note that any subsequent error might be potentially misleading due to a previous failure." -+ echo >&2 "A log file will be generated at $NEWROOT/var/log/leapp/leapp-upgrade.log." -+ echo >&2 "In case of persisting failure, if possible, try to boot to the original system and file a support case with /var/log/leapp/leapp-upgrade.log attached." - } - - [ ! -x "$NEWROOT$LEAPPBIN" ] && { -@@ -348,7 +196,7 @@ awk '{print $1}' /proc/cmdline \ - ) - result=$? - --##### safe the data ##### -+##### save the data ##### - save_journal - - # NOTE: flush the cached content to disk to ensure everything is written --- -2.54.0 - diff --git a/0051-LiveMode-improve-do-upgrade.sh-output-when-.noreboot.patch b/0051-LiveMode-improve-do-upgrade.sh-output-when-.noreboot.patch deleted file mode 100644 index b474bf0..0000000 --- a/0051-LiveMode-improve-do-upgrade.sh-output-when-.noreboot.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 72ec8a691678f5097f6fc9290c5ae1a6b030dac9 Mon Sep 17 00:00:00 2001 -From: Peter Mocary -Date: Thu, 30 Apr 2026 11:53:26 +0200 -Subject: [PATCH 051/105] LiveMode: improve do-upgrade.sh output when .noreboot - file exists - -The output of do-upgrade.sh suggested that a reboot is going to happen -even though it wasn't as .noreboot file stopped it. This patch makes -sure the output is not misleading users. ---- - .../modify_userspace_for_livemode/files/do-upgrade.sh | 9 +++++++-- - 1 file changed, 7 insertions(+), 2 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/livemode/modify_userspace_for_livemode/files/do-upgrade.sh b/repos/system_upgrade/common/actors/livemode/modify_userspace_for_livemode/files/do-upgrade.sh -index 6f2e37ef..b6154573 100755 ---- a/repos/system_upgrade/common/actors/livemode/modify_userspace_for_livemode/files/do-upgrade.sh -+++ b/repos/system_upgrade/common/actors/livemode/modify_userspace_for_livemode/files/do-upgrade.sh -@@ -137,7 +137,7 @@ do_upgrade() { - - save_journal() { - # Q: would it be possible that journal will not be flushed completely yet? -- echo "writing logs to disk and rebooting" -+ echo "writing logs to disk" - - local logfile="/sysroot/tmp-leapp-upgrade.log" - -@@ -213,7 +213,12 @@ sync - #Failed to talk to init daemon: Host is down - #""" - if [ "$result" == "0" ]; then -- [ -f "${NEWROOT}/.noreboot" ] || reboot -+ if [ -f "${NEWROOT}/.noreboot" ]; then -+ echo "Reboot suppressed by ${NEWROOT}/.noreboot" -+ else -+ echo "Rebooting..." -+ reboot -+ fi - else - echo >&2 "The upgrade container returned a non-zero exit code." - exit $result --- -2.54.0 - diff --git a/0052-multipathutil-handle-the-overrides-protocol-subsecti.patch b/0052-multipathutil-handle-the-overrides-protocol-subsecti.patch deleted file mode 100644 index bbf509e..0000000 --- a/0052-multipathutil-handle-the-overrides-protocol-subsecti.patch +++ /dev/null @@ -1,47 +0,0 @@ -From a9aa8a8037a1705a9f967d5f62f6b2db428bf8fc Mon Sep 17 00:00:00 2001 -From: Benjamin Marzinski -Date: Mon, 16 Mar 2026 19:05:54 -0400 -Subject: [PATCH 052/105] multipathutil: handle the overrides:protocol - subsection - -Starting in rhel-8, multipath.conf supports a protocol subection of the -overrides configuration section. Accept this when parsing the config, -and add a test for it. - -Jira: RHEL-151509 ---- - repos/system_upgrade/common/libraries/multipathutil.py | 3 ++- - .../common/libraries/tests/test_multipathutil.py | 3 ++- - 2 files changed, 4 insertions(+), 2 deletions(-) - -diff --git a/repos/system_upgrade/common/libraries/multipathutil.py b/repos/system_upgrade/common/libraries/multipathutil.py -index cb5c3693..78ed5f13 100644 ---- a/repos/system_upgrade/common/libraries/multipathutil.py -+++ b/repos/system_upgrade/common/libraries/multipathutil.py -@@ -7,7 +7,8 @@ _sections = ('defaults', 'blacklist', 'blacklist_exceptions', 'devices', - 'overrides', 'multipaths') - - _subsections = {'blacklist': 'device', 'blacklist_exceptions': 'device', -- 'devices': 'device', 'multipaths': 'multipath'} -+ 'devices': 'device', 'multipaths': 'multipath', -+ 'overrides': 'protocol'} - - - def read_config(path): -diff --git a/repos/system_upgrade/common/libraries/tests/test_multipathutil.py b/repos/system_upgrade/common/libraries/tests/test_multipathutil.py -index 3ddfcddf..852f3463 100644 ---- a/repos/system_upgrade/common/libraries/tests/test_multipathutil.py -+++ b/repos/system_upgrade/common/libraries/tests/test_multipathutil.py -@@ -38,7 +38,8 @@ def test_section_start(): - sections = ('defaults', 'blacklist', 'blacklist_exceptions', 'devices', - 'overrides', 'multipaths') - subsections = {'blacklist': 'device', 'blacklist_exceptions': 'device', -- 'devices': 'device', 'multipaths': 'multipath'} -+ 'devices': 'device', 'multipaths': 'multipath', -+ 'overrides': 'protocol'} - for section in sections: - data = lib.LineData(section + ' {', None, False) - assert data.type == data.TYPE_SECTION_START --- -2.54.0 - diff --git a/0053-multipath-move-config_reader-Actor-and-8to9-Models-t.patch b/0053-multipath-move-config_reader-Actor-and-8to9-Models-t.patch deleted file mode 100644 index a5726f2..0000000 --- a/0053-multipath-move-config_reader-Actor-and-8to9-Models-t.patch +++ /dev/null @@ -1,324 +0,0 @@ -From 71431ef1404f2eb817a37a9574e9fbcf55d6d367 Mon Sep 17 00:00:00 2001 -From: Benjamin Marzinski -Date: Tue, 17 Mar 2026 13:53:47 -0400 -Subject: [PATCH 053/105] multipath: move config_reader Actor and 8to9 Models - to el8toel9 repo - -The config_reader actor is mostly decidated to checking for issues that -can happen during an el8 to el9 upgrade and produces -MultipathConfFacts8to9 messages. The MultipathConfig8to9 and -MultipathConfFacts8to9 models are exclusively for el8 to el9 upgrades. -All of these should live in the el8toel9 repo. - -Jira: RHEL-151509 ---- - .../system_upgrade/common/models/multipath.py | 42 ------------------- - .../actors/multipathconfread}/actor.py | 10 ++--- - .../libraries/multipathconfread.py | 11 ++--- - .../tests/files/all_the_things.conf | 0 - .../tests/files/allow_usb.conf | 0 - .../tests/files/complicated.conf | 0 - .../tests/files/conf1.d/empty.conf | 0 - .../files/conf1.d/nothing_important.conf | 0 - .../tests/files/conf2.d/all_true.conf | 0 - .../tests/files/conf3.d/README | 0 - .../tests/files/converted_the_things.conf | 0 - .../tests/files/default_rhel8.conf | 0 - .../multipathconfread}/tests/files/empty.conf | 0 - .../tests/files/empty_dir.conf | 0 - .../tests/files/missing_dir.conf | 0 - .../tests/files/no_defaults.conf | 0 - .../tests/files/no_foreign.conf | 0 - .../tests/files/not_set_dir.conf | 0 - .../tests/files/set_in_dir.conf | 0 - .../tests/files/two_defaults.conf | 0 - .../tests/test_multipath_conf_read_8to9.py | 30 ------------- - .../el8toel9/models/multipath8to9.py | 34 +++++++++++++++ - 22 files changed, 42 insertions(+), 85 deletions(-) - rename repos/system_upgrade/{common/actors/multipath/config_reader => el8toel9/actors/multipathconfread}/actor.py (68%) - rename repos/system_upgrade/{common/actors/multipath/config_reader => el8toel9/actors/multipathconfread}/libraries/multipathconfread.py (88%) - rename repos/system_upgrade/{common/actors/multipath/config_reader => el8toel9/actors/multipathconfread}/tests/files/all_the_things.conf (100%) - rename repos/system_upgrade/{common/actors/multipath/config_reader => el8toel9/actors/multipathconfread}/tests/files/allow_usb.conf (100%) - rename repos/system_upgrade/{common/actors/multipath/config_reader => el8toel9/actors/multipathconfread}/tests/files/complicated.conf (100%) - rename repos/system_upgrade/{common/actors/multipath/config_reader => el8toel9/actors/multipathconfread}/tests/files/conf1.d/empty.conf (100%) - rename repos/system_upgrade/{common/actors/multipath/config_reader => el8toel9/actors/multipathconfread}/tests/files/conf1.d/nothing_important.conf (100%) - rename repos/system_upgrade/{common/actors/multipath/config_reader => el8toel9/actors/multipathconfread}/tests/files/conf2.d/all_true.conf (100%) - rename repos/system_upgrade/{common/actors/multipath/config_reader => el8toel9/actors/multipathconfread}/tests/files/conf3.d/README (100%) - rename repos/system_upgrade/{common/actors/multipath/config_reader => el8toel9/actors/multipathconfread}/tests/files/converted_the_things.conf (100%) - rename repos/system_upgrade/{common/actors/multipath/config_reader => el8toel9/actors/multipathconfread}/tests/files/default_rhel8.conf (100%) - rename repos/system_upgrade/{common/actors/multipath/config_reader => el8toel9/actors/multipathconfread}/tests/files/empty.conf (100%) - rename repos/system_upgrade/{common/actors/multipath/config_reader => el8toel9/actors/multipathconfread}/tests/files/empty_dir.conf (100%) - rename repos/system_upgrade/{common/actors/multipath/config_reader => el8toel9/actors/multipathconfread}/tests/files/missing_dir.conf (100%) - rename repos/system_upgrade/{common/actors/multipath/config_reader => el8toel9/actors/multipathconfread}/tests/files/no_defaults.conf (100%) - rename repos/system_upgrade/{common/actors/multipath/config_reader => el8toel9/actors/multipathconfread}/tests/files/no_foreign.conf (100%) - rename repos/system_upgrade/{common/actors/multipath/config_reader => el8toel9/actors/multipathconfread}/tests/files/not_set_dir.conf (100%) - rename repos/system_upgrade/{common/actors/multipath/config_reader => el8toel9/actors/multipathconfread}/tests/files/set_in_dir.conf (100%) - rename repos/system_upgrade/{common/actors/multipath/config_reader => el8toel9/actors/multipathconfread}/tests/files/two_defaults.conf (100%) - rename repos/system_upgrade/{common/actors/multipath/config_reader => el8toel9/actors/multipathconfread}/tests/test_multipath_conf_read_8to9.py (83%) - create mode 100644 repos/system_upgrade/el8toel9/models/multipath8to9.py - -diff --git a/repos/system_upgrade/common/models/multipath.py b/repos/system_upgrade/common/models/multipath.py -index 1d1c53b5..9ccaa290 100644 ---- a/repos/system_upgrade/common/models/multipath.py -+++ b/repos/system_upgrade/common/models/multipath.py -@@ -34,45 +34,3 @@ class MultipathConfigUpdatesInfo(Model): - - updates = fields.List(fields.Model(UpdatedMultipathConfig), default=[]) - """ Collection of multipath config updates that must be performed during the upgrade. """ -- -- --class MultipathConfig8to9(Model): -- """ -- Model information about multipath configuration file important for the 8>9 upgrade path. -- -- Note: This model is in the common repository due to the technical reasons -- (reusing parser code in a single actor), and it should not be emitted on -- non-8to9 upgrade paths. In the future, this model will likely be moved into -- el8toel9 repository. -- """ -- topic = SystemInfoTopic -- -- pathname = fields.String() -- """Config file path name""" -- -- config_dir = fields.Nullable(fields.String()) -- """Value of config_dir in the defaults section. None if not set""" -- -- enable_foreign_exists = fields.Boolean(default=False) -- """True if enable_foreign is set in the defaults section""" -- -- invalid_regexes_exist = fields.Boolean(default=False) -- """True if any regular expressions have the value of "*" """ -- -- allow_usb_exists = fields.Boolean(default=False) -- """True if allow_usb_devices is set in the defaults section.""" -- -- --class MultipathConfFacts8to9(Model): -- """ -- Model representing information from multipath configuration files important for the 8>9 upgrade path. -- -- Note: This model is in the common repository due to the technical reasons -- (reusing parser code in a single actor), and it should not be emitted on -- non-8to9 upgrade paths. In the future, this model will likely be moved into -- el8toel9 repository. -- """ -- topic = SystemInfoTopic -- -- configs = fields.List(fields.Model(MultipathConfig8to9), default=[]) -- """List of multipath configuration files""" -diff --git a/repos/system_upgrade/common/actors/multipath/config_reader/actor.py b/repos/system_upgrade/el8toel9/actors/multipathconfread/actor.py -similarity index 68% -rename from repos/system_upgrade/common/actors/multipath/config_reader/actor.py -rename to repos/system_upgrade/el8toel9/actors/multipathconfread/actor.py -index a7238a25..2e326614 100644 ---- a/repos/system_upgrade/common/actors/multipath/config_reader/actor.py -+++ b/repos/system_upgrade/el8toel9/actors/multipathconfread/actor.py -@@ -4,7 +4,7 @@ from leapp.models import DistributionSignedRPM, MultipathConfFacts8to9, Multipat - from leapp.tags import FactsPhaseTag, IPUWorkflowTag - - --class MultipathConfRead(Actor): -+class MultipathConfRead8to9(Actor): - """ - Read multipath configuration files and extract the necessary information - -@@ -13,13 +13,11 @@ class MultipathConfRead(Actor): - - /etc/multipath/ - any files inside the directory - - /etc/xdrdevices.conf - -- Two kinds of messages are generated: -- - MultipathInfo - general information about multipath, version agnostic -- - upgrade-path-specific messages such as MultipathConfFacts8to9 (produced only -- when upgrading from 8 to 9) -+ Produces MultipathInfo with general information about multipath, and -+ MultipathConfFacts8to9 with details needed for the 8 to 9 upgrade. - """ - -- name = 'multipath_conf_read' -+ name = 'multipath_conf_read_8to9' - consumes = (DistributionSignedRPM,) - produces = (MultipathInfo, MultipathConfFacts8to9) - tags = (FactsPhaseTag, IPUWorkflowTag) -diff --git a/repos/system_upgrade/common/actors/multipath/config_reader/libraries/multipathconfread.py b/repos/system_upgrade/el8toel9/actors/multipathconfread/libraries/multipathconfread.py -similarity index 88% -rename from repos/system_upgrade/common/actors/multipath/config_reader/libraries/multipathconfread.py -rename to repos/system_upgrade/el8toel9/actors/multipathconfread/libraries/multipathconfread.py -index e733500b..f2f39293 100644 ---- a/repos/system_upgrade/common/actors/multipath/config_reader/libraries/multipathconfread.py -+++ b/repos/system_upgrade/el8toel9/actors/multipathconfread/libraries/multipathconfread.py -@@ -2,7 +2,6 @@ import errno - import os - - from leapp.libraries.common import multipathutil --from leapp.libraries.common.config.version import get_source_major_version - from leapp.libraries.common.rpms import has_package - from leapp.libraries.stdlib import api - from leapp.models import DistributionSignedRPM, MultipathConfFacts8to9, MultipathConfig8to9, MultipathInfo -@@ -103,10 +102,8 @@ def scan_and_emit_multipath_info(default_config_path='/etc/multipath.conf'): - ) - api.produce(multipath_info) - -- # Handle upgrade-path-specific config actions -- if get_source_major_version() == '8': -- secondary_configs = _parse_config_dir(multipath_info.config_dir) -- all_configs = [primary_config] + secondary_configs -+ secondary_configs = _parse_config_dir(multipath_info.config_dir) -+ all_configs = [primary_config] + secondary_configs - -- config_facts_for_8to9 = MultipathConfFacts8to9(configs=all_configs) -- api.produce(config_facts_for_8to9) -+ config_facts_for_8to9 = MultipathConfFacts8to9(configs=all_configs) -+ api.produce(config_facts_for_8to9) -diff --git a/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/all_the_things.conf b/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/all_the_things.conf -similarity index 100% -rename from repos/system_upgrade/common/actors/multipath/config_reader/tests/files/all_the_things.conf -rename to repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/all_the_things.conf -diff --git a/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/allow_usb.conf b/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/allow_usb.conf -similarity index 100% -rename from repos/system_upgrade/common/actors/multipath/config_reader/tests/files/allow_usb.conf -rename to repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/allow_usb.conf -diff --git a/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/complicated.conf b/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/complicated.conf -similarity index 100% -rename from repos/system_upgrade/common/actors/multipath/config_reader/tests/files/complicated.conf -rename to repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/complicated.conf -diff --git a/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/conf1.d/empty.conf b/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/conf1.d/empty.conf -similarity index 100% -rename from repos/system_upgrade/common/actors/multipath/config_reader/tests/files/conf1.d/empty.conf -rename to repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/conf1.d/empty.conf -diff --git a/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/conf1.d/nothing_important.conf b/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/conf1.d/nothing_important.conf -similarity index 100% -rename from repos/system_upgrade/common/actors/multipath/config_reader/tests/files/conf1.d/nothing_important.conf -rename to repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/conf1.d/nothing_important.conf -diff --git a/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/conf2.d/all_true.conf b/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/conf2.d/all_true.conf -similarity index 100% -rename from repos/system_upgrade/common/actors/multipath/config_reader/tests/files/conf2.d/all_true.conf -rename to repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/conf2.d/all_true.conf -diff --git a/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/conf3.d/README b/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/conf3.d/README -similarity index 100% -rename from repos/system_upgrade/common/actors/multipath/config_reader/tests/files/conf3.d/README -rename to repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/conf3.d/README -diff --git a/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/converted_the_things.conf b/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/converted_the_things.conf -similarity index 100% -rename from repos/system_upgrade/common/actors/multipath/config_reader/tests/files/converted_the_things.conf -rename to repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/converted_the_things.conf -diff --git a/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/default_rhel8.conf b/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/default_rhel8.conf -similarity index 100% -rename from repos/system_upgrade/common/actors/multipath/config_reader/tests/files/default_rhel8.conf -rename to repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/default_rhel8.conf -diff --git a/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/empty.conf b/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/empty.conf -similarity index 100% -rename from repos/system_upgrade/common/actors/multipath/config_reader/tests/files/empty.conf -rename to repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/empty.conf -diff --git a/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/empty_dir.conf b/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/empty_dir.conf -similarity index 100% -rename from repos/system_upgrade/common/actors/multipath/config_reader/tests/files/empty_dir.conf -rename to repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/empty_dir.conf -diff --git a/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/missing_dir.conf b/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/missing_dir.conf -similarity index 100% -rename from repos/system_upgrade/common/actors/multipath/config_reader/tests/files/missing_dir.conf -rename to repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/missing_dir.conf -diff --git a/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/no_defaults.conf b/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/no_defaults.conf -similarity index 100% -rename from repos/system_upgrade/common/actors/multipath/config_reader/tests/files/no_defaults.conf -rename to repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/no_defaults.conf -diff --git a/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/no_foreign.conf b/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/no_foreign.conf -similarity index 100% -rename from repos/system_upgrade/common/actors/multipath/config_reader/tests/files/no_foreign.conf -rename to repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/no_foreign.conf -diff --git a/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/not_set_dir.conf b/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/not_set_dir.conf -similarity index 100% -rename from repos/system_upgrade/common/actors/multipath/config_reader/tests/files/not_set_dir.conf -rename to repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/not_set_dir.conf -diff --git a/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/set_in_dir.conf b/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/set_in_dir.conf -similarity index 100% -rename from repos/system_upgrade/common/actors/multipath/config_reader/tests/files/set_in_dir.conf -rename to repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/set_in_dir.conf -diff --git a/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/two_defaults.conf b/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/two_defaults.conf -similarity index 100% -rename from repos/system_upgrade/common/actors/multipath/config_reader/tests/files/two_defaults.conf -rename to repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/two_defaults.conf -diff --git a/repos/system_upgrade/common/actors/multipath/config_reader/tests/test_multipath_conf_read_8to9.py b/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/test_multipath_conf_read_8to9.py -similarity index 83% -rename from repos/system_upgrade/common/actors/multipath/config_reader/tests/test_multipath_conf_read_8to9.py -rename to repos/system_upgrade/el8toel9/actors/multipathconfread/tests/test_multipath_conf_read_8to9.py -index e593a857..2da74927 100644 ---- a/repos/system_upgrade/common/actors/multipath/config_reader/tests/test_multipath_conf_read_8to9.py -+++ b/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/test_multipath_conf_read_8to9.py -@@ -142,33 +142,3 @@ def test_get_facts_missing_dir(monkeypatch, primary_config, expected_configs): - - for actual_config, expected_config in zip(actual_configs, expected_configs): - assert_config(actual_config, expected_config) -- -- --def test_only_general_info_is_produced_on_9to10(monkeypatch): -- default_config_path = '/etc/multipath.conf' -- -- def parse_config_mock(path): -- assert path == default_config_path -- return MultipathConfig8to9(pathname=path) -- -- monkeypatch.setattr(multipathconfread, '_parse_config', parse_config_mock) -- monkeypatch.setattr(multipathconfread, 'is_processable', lambda: True) -- -- produce_mock = produce_mocked() -- monkeypatch.setattr(api, 'produce', produce_mock) -- -- actor_mock = CurrentActorMocked(src_ver='9.6', dst_ver='10.0') -- monkeypatch.setattr(api, 'current_actor', actor_mock) -- -- multipathconfread.scan_and_emit_multipath_info(default_config_path) -- -- assert produce_mock.called -- -- general_info_msgs = [msg for msg in produce_mock.model_instances if isinstance(msg, MultipathInfo)] -- assert len(general_info_msgs) == 1 -- general_info = general_info_msgs[0] -- assert general_info.is_configured -- assert general_info.config_dir == '/etc/multipath/conf.d' -- -- msgs = [msg for msg in produce_mock.model_instances if isinstance(msg, MultipathConfFacts8to9)] -- assert not msgs -diff --git a/repos/system_upgrade/el8toel9/models/multipath8to9.py b/repos/system_upgrade/el8toel9/models/multipath8to9.py -new file mode 100644 -index 00000000..a02f45db ---- /dev/null -+++ b/repos/system_upgrade/el8toel9/models/multipath8to9.py -@@ -0,0 +1,34 @@ -+from leapp.models import fields, Model -+from leapp.topics import SystemInfoTopic -+ -+ -+class MultipathConfig8to9(Model): -+ """ -+ Model information about multipath configuration file important for the 8>9 upgrade path. -+ """ -+ topic = SystemInfoTopic -+ -+ pathname = fields.String() -+ """Config file path name""" -+ -+ config_dir = fields.Nullable(fields.String()) -+ """Value of config_dir in the defaults section. None if not set""" -+ -+ enable_foreign_exists = fields.Boolean(default=False) -+ """True if enable_foreign is set in the defaults section""" -+ -+ invalid_regexes_exist = fields.Boolean(default=False) -+ """True if any regular expressions have the value of "*" """ -+ -+ allow_usb_exists = fields.Boolean(default=False) -+ """True if allow_usb_devices is set in the defaults section.""" -+ -+ -+class MultipathConfFacts8to9(Model): -+ """ -+ Model representing information from multipath configuration files important for the 8>9 upgrade path. -+ """ -+ topic = SystemInfoTopic -+ -+ configs = fields.List(fields.Model(MultipathConfig8to9), default=[]) -+ """List of multipath configuration files""" --- -2.54.0 - diff --git a/0054-multipath-Add-MultipathConfig9to10-and-MultipathConf.patch b/0054-multipath-Add-MultipathConfig9to10-and-MultipathConf.patch deleted file mode 100644 index 71f0b49..0000000 --- a/0054-multipath-Add-MultipathConfig9to10-and-MultipathConf.patch +++ /dev/null @@ -1,83 +0,0 @@ -From b1bf3ce6175e007f304443830a49b641fe0aa96f Mon Sep 17 00:00:00 2001 -From: Benjamin Marzinski -Date: Wed, 18 Mar 2026 16:38:43 -0400 -Subject: [PATCH 054/105] multipath: Add MultipathConfig9to10 and - MultipathConfFacts9to10 - -Add el9toel10 models to track the necessary configuration changes for -the upgrade from RHEL-9 to RHEL-10 - -Jira: RHEL-151509 ---- - .../el9toel10/models/multipath9to10.py | 59 +++++++++++++++++++ - 1 file changed, 59 insertions(+) - create mode 100644 repos/system_upgrade/el9toel10/models/multipath9to10.py - -diff --git a/repos/system_upgrade/el9toel10/models/multipath9to10.py b/repos/system_upgrade/el9toel10/models/multipath9to10.py -new file mode 100644 -index 00000000..19ef24cf ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/models/multipath9to10.py -@@ -0,0 +1,59 @@ -+from leapp.models import fields, Model -+from leapp.topics import SystemInfoTopic -+ -+ -+class MultipathConfig9to10(Model): -+ """ -+ Model information about multipath configuration file important for the 9>10 upgrade path. -+ """ -+ topic = SystemInfoTopic -+ -+ pathname = fields.String() -+ """Config file path name""" -+ -+ config_dir = fields.Nullable(fields.String()) -+ """ -+ Value of config_dir in the defaults section. None if not set. -+ Used both to track config lines that need commenting out and -+ to determine the actual directory location. -+ """ -+ -+ bindings_file = fields.Nullable(fields.String()) -+ """ -+ Value of bindings_file in the defaults section. None if not set. -+ Used both to track config lines that need commenting out and -+ to determine the actual file location for copying. -+ """ -+ -+ wwids_file = fields.Nullable(fields.String()) -+ """ -+ Value of wwids_file in the defaults section. None if not set. -+ Used both to track config lines that need commenting out and -+ to determine the actual file location for copying. -+ """ -+ -+ prkeys_file = fields.Nullable(fields.String()) -+ """ -+ Value of prkeys_file in the defaults section. None if not set. -+ Used both to track config lines that need commenting out and -+ to determine the actual file location for copying. -+ """ -+ -+ has_socket_activation = fields.Boolean(default=True) -+ """True if multipathd socket activation is enabled""" -+ -+ has_dm_nvme_multipathing = fields.Boolean(default=False) -+ """True if DM NVMe multipathing is enabled""" -+ -+ has_getuid = fields.Boolean(default=False) -+ """True if the getuid option is set anywhere in the multipath config""" -+ -+ -+class MultipathConfFacts9to10(Model): -+ """ -+ Model representing information from multipath configuration files important for the 9>10 upgrade path. -+ """ -+ topic = SystemInfoTopic -+ -+ configs = fields.List(fields.Model(MultipathConfig9to10), default=[]) -+ """List of multipath configuration files""" --- -2.54.0 - diff --git a/0055-multipath-Add-MultipathConfRead9to10-config_reader-a.patch b/0055-multipath-Add-MultipathConfRead9to10-config_reader-a.patch deleted file mode 100644 index bfa85b0..0000000 --- a/0055-multipath-Add-MultipathConfRead9to10-config_reader-a.patch +++ /dev/null @@ -1,1897 +0,0 @@ -From 7ff626243366a4ffaaf972f12e8a4d7708c06040 Mon Sep 17 00:00:00 2001 -From: Benjamin Marzinski -Date: Wed, 18 Mar 2026 20:51:05 -0400 -Subject: [PATCH 055/105] multipath: Add MultipathConfRead9to10 config_reader - actor - -Add the el9toel10 config_reader actor that scans multipath configuration -and produces MultipathInfo and MultipathConfFacts9to10 messages. The actor -detects getuid_callout usage, socket activation, DM NVMe multipathing, -and tracks config_dir/bindings_file/wwids_file/prkeys_file settings. - -Also keep target_uspace_configs from automatically copying the old -config_dir, if it isn't in the default location in an el9toel10 upgrade. -the old config_dir will always be copied in an el8toel9 upgrade. - -Jira: RHEL-151509 - -Co-Authored-By: Claude Opus 4.6 ---- - .../target_uspace_multipath_configs.py | 2 +- - .../actors/multipath/config_reader/actor.py | 25 + - .../libraries/multipathconfread.py | 138 ++ - .../tests/files/all_options.conf | 9 + - .../tests/files/all_set_in_dir.conf | 5 + - .../tests/files/complicated.conf | 1136 +++++++++++++++++ - .../tests/files/conf1.d/bindings_set.conf | 3 + - .../tests/files/conf1.d/empty.conf | 1 + - .../tests/files/conf2.d/getuid_set.conf | 11 + - .../tests/files/conf2.d/set_files.conf | 5 + - .../config_reader/tests/files/conf3.d/README | 1 + - .../config_reader/tests/files/config_dir.conf | 5 + - .../tests/files/default_rhel9.conf | 21 + - .../config_reader/tests/files/empty.conf | 1 + - .../config_reader/tests/files/empty_dir.conf | 5 + - .../tests/files/extra_slash.conf | 5 + - .../tests/files/getuid_defaults.conf | 5 + - .../tests/files/getuid_devices.conf | 12 + - .../tests/files/getuid_overrides.conf | 8 + - .../tests/files/missing_dir.conf | 5 + - .../tests/files/no_defaults.conf | 7 + - .../tests/test_multipath_conf_read_9to10.py | 283 ++++ - 22 files changed, 1692 insertions(+), 1 deletion(-) - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/config_reader/actor.py - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/config_reader/libraries/multipathconfread.py - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/all_options.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/all_set_in_dir.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/complicated.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/conf1.d/bindings_set.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/conf1.d/empty.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/conf2.d/getuid_set.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/conf2.d/set_files.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/conf3.d/README - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/config_dir.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/default_rhel9.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/empty.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/empty_dir.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/extra_slash.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/getuid_defaults.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/getuid_devices.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/getuid_overrides.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/missing_dir.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/no_defaults.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/test_multipath_conf_read_9to10.py - -diff --git a/repos/system_upgrade/common/actors/multipath/target_uspace_configs/libraries/target_uspace_multipath_configs.py b/repos/system_upgrade/common/actors/multipath/target_uspace_configs/libraries/target_uspace_multipath_configs.py -index 72afc477..bc685fa2 100644 ---- a/repos/system_upgrade/common/actors/multipath/target_uspace_configs/libraries/target_uspace_multipath_configs.py -+++ b/repos/system_upgrade/common/actors/multipath/target_uspace_configs/libraries/target_uspace_multipath_configs.py -@@ -22,7 +22,7 @@ def request_mpath_confs(multipath_info): - '/etc/multipath.conf': '/etc/multipath.conf' # default config - } - -- if os.path.exists(multipath_info.config_dir): -+ if multipath_info.config_dir and os.path.exists(multipath_info.config_dir): - for filename in os.listdir(multipath_info.config_dir): - config_path = os.path.join(multipath_info.config_dir, filename) - if not config_path.endswith('.conf'): -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/actor.py b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/actor.py -new file mode 100644 -index 00000000..2b122fdd ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/actor.py -@@ -0,0 +1,25 @@ -+from leapp.actors import Actor -+from leapp.libraries.actor import multipathconfread -+from leapp.models import DistributionSignedRPM, MultipathConfFacts9to10, MultipathInfo -+from leapp.tags import FactsPhaseTag, IPUWorkflowTag -+ -+ -+class MultipathConfRead9to10(Actor): -+ """ -+ Read multipath configuration files and extract the necessary information -+ -+ Related files: -+ - /etc/multipath.conf -+ - /etc/multipath/ - any files inside the directory -+ -+ Produces MultipathInfo with general information about multipath, and -+ MultipathConfFacts9to10 with details needed for the 9 to 10 upgrade. -+ """ -+ -+ name = 'multipath_conf_read_9to10' -+ consumes = (DistributionSignedRPM,) -+ produces = (MultipathInfo, MultipathConfFacts9to10) -+ tags = (FactsPhaseTag, IPUWorkflowTag) -+ -+ def process(self): -+ multipathconfread.scan_and_emit_multipath_info() -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/libraries/multipathconfread.py b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/libraries/multipathconfread.py -new file mode 100644 -index 00000000..50c87d86 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/libraries/multipathconfread.py -@@ -0,0 +1,138 @@ -+import errno -+import os -+ -+from leapp.libraries.common import multipathutil -+from leapp.libraries.common.rpms import has_package -+from leapp.libraries.stdlib import api -+from leapp.models import DistributionSignedRPM, MultipathConfFacts9to10, MultipathConfig9to10, MultipathInfo -+ -+ -+def _parse_config(path): -+ contents = multipathutil.read_config(path) -+ if contents is None: -+ return None -+ conf = MultipathConfig9to10(pathname=path) -+ section = None -+ in_subsection = False -+ for line in contents.split('\n'): -+ try: -+ data = multipathutil.LineData(line, section, in_subsection) -+ except ValueError: -+ continue -+ if data.type == data.TYPE_BLANK: -+ continue -+ if data.type == data.TYPE_SECTION_END: -+ if in_subsection: -+ in_subsection = False -+ elif section: -+ section = None -+ continue -+ if data.type == data.TYPE_SECTION_START: -+ if not section: -+ section = data.section -+ elif not in_subsection: -+ in_subsection = True -+ continue -+ if data.type != data.TYPE_OPTION: -+ continue -+ if section == 'defaults': -+ if data.option == 'config_dir': -+ conf.config_dir = data.value -+ elif data.option == 'bindings_file': -+ conf.bindings_file = data.value -+ elif data.option == 'wwids_file': -+ conf.wwids_file = data.value -+ elif data.option == 'prkeys_file': -+ conf.prkeys_file = data.value -+ if data.option == 'getuid_callout': -+ conf.has_getuid = True -+ return conf -+ -+ -+def _parse_config_dir(config_dir): -+ res = [] -+ try: -+ for config_file in sorted(os.listdir(config_dir)): -+ path = os.path.join(config_dir, config_file) -+ if not path.endswith('.conf'): -+ continue -+ conf = _parse_config(path) -+ if conf: -+ res.append(conf) -+ except OSError as e: -+ if e.errno == errno.ENOENT: -+ api.current_logger().debug( -+ 'Multipath conf directory "%s" doesn\'t exist', -+ config_dir -+ ) -+ else: -+ api.current_logger().warning( -+ 'Failed to read multipath config directory "%s": %s', -+ config_dir, -+ e -+ ) -+ return res -+ -+ -+def _check_socket_activation(): -+ return os.path.exists( -+ '/etc/systemd/system/sockets.target.wants/multipathd.socket' -+ ) -+ -+ -+def _check_dm_nvme_multipathing(): -+ if not os.path.isdir('/sys/module/nvme_core'): -+ return False -+ try: -+ with open('/sys/module/nvme_core/parameters/multipath', 'r') as f: -+ content = f.read().strip() -+ except IOError: -+ return False -+ return content == 'N' -+ -+ -+def is_processable(): -+ res = has_package(DistributionSignedRPM, 'device-mapper-multipath') -+ if not res: -+ api.current_logger().debug('device-mapper-multipath is not installed.') -+ return res -+ -+ -+def scan_and_emit_multipath_info(default_config_path='/etc/multipath.conf'): -+ if not is_processable(): -+ return -+ -+ primary_config = _parse_config(default_config_path) -+ if not primary_config: -+ api.current_logger().debug( -+ 'Primary multipath config /etc/multipath.conf is not present - multipath ' -+ 'is not used.' -+ ) -+ mpath_info = MultipathInfo(is_configured=False) -+ api.produce(mpath_info) -+ return -+ -+ multipath_info = MultipathInfo(is_configured=True) -+ # Do not set multipath_info.config_dir to a non-default directory, -+ # otherwise any files that exist there will be copied to that directory -+ # during the upgrade. Also don't set it to the default directory if you -+ # aren't reading config files from there. The directory may exist and have -+ # config files which are not used in the existing config, and should not -+ # be copied. -+ if ( -+ not primary_config.config_dir or -+ os.path.normpath(primary_config.config_dir) == '/etc/multipath/conf.d' -+ ): -+ multipath_info.config_dir = '/etc/multipath/conf.d' -+ api.produce(multipath_info) -+ -+ primary_config.has_socket_activation = _check_socket_activation() -+ primary_config.has_dm_nvme_multipathing = _check_dm_nvme_multipathing() -+ -+ secondary_configs = _parse_config_dir( -+ primary_config.config_dir or '/etc/multipath/conf.d' -+ ) -+ all_configs = [primary_config] + secondary_configs -+ -+ config_facts_for_9to10 = MultipathConfFacts9to10(configs=all_configs) -+ api.produce(config_facts_for_9to10) -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/all_options.conf b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/all_options.conf -new file mode 100644 -index 00000000..cf72bf24 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/all_options.conf -@@ -0,0 +1,9 @@ -+defaults { -+ user_friendly_names yes -+ find_multipaths yes -+ config_dir "/etc/multipath/conf.d" -+ bindings_file "/etc/multipath/bindings" -+ wwids_file "/etc/multipath/wwids" -+ prkeys_file "/etc/multipath/prkeys" -+ getuid_callout "/lib/udev/scsi_id --whitelisted --device=/dev/%n" -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/all_set_in_dir.conf b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/all_set_in_dir.conf -new file mode 100644 -index 00000000..d9698c0a ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/all_set_in_dir.conf -@@ -0,0 +1,5 @@ -+defaults { -+ user_friendly_names yes -+ find_multipaths yes -+ config_dir "conf2.d" -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/complicated.conf b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/complicated.conf -new file mode 100644 -index 00000000..368e7d91 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/complicated.conf -@@ -0,0 +1,1136 @@ -+defaults { -+ verbosity 2 -+ polling_interval 5 -+ max_polling_interval 20 -+ reassign_maps "no" -+ multipath_dir "/lib64/multipath" -+ path_selector "service-time 0" -+ path_grouping_policy "failover" -+ uid_attribute "ID_SERIAL" -+ prio "const" -+ prio_args "" -+ features "0" -+ path_checker "tur" -+ alias_prefix "mpath" -+ failback "manual" -+ rr_min_io 1000 -+ rr_min_io_rq 1 -+ max_fds "max" -+ rr_weight "uniform" -+ queue_without_daemon "no" -+ allow_usb_devices "no" -+ flush_on_last_del "unused" -+ user_friendly_names "yes" -+ fast_io_fail_tmo 5 -+ bindings_file "/etc/multipath/bindings" -+ wwids_file "/etc/multipath/wwids" -+ prkeys_file "/etc/multipath/prkeys" -+ log_checker_err "always" -+ reservation_key "file" -+ all_tg_pt "no" -+ retain_attached_hw_handler "yes" -+ detect_prio "yes" -+ detect_checker "yes" -+ force_sync "yes" -+ strict_timing "no" -+ deferred_remove "no" -+ config_dir "/etc/multipath/conf.d" -+ delay_watch_checks "no" -+ delay_wait_checks "no" -+ san_path_err_threshold "no" -+ san_path_err_forget_rate "no" -+ san_path_err_recovery_time "no" -+ marginal_path_err_sample_time "no" -+ marginal_path_err_rate_threshold "no" -+ marginal_path_err_recheck_gap_time "no" -+ marginal_path_double_failed_time "no" -+ find_multipaths "on" -+ uxsock_timeout 4000 -+ retrigger_tries 0 -+ retrigger_delay 10 -+ missing_uev_wait_timeout 30 -+ skip_kpartx "no" -+ purge_disconnected "no" -+ disable_changed_wwids "ignored" -+ remove_retries 0 -+ ghost_delay "no" -+ auto_resize "never" -+ find_multipaths_timeout -10 -+ enable_foreign "NONE" -+ marginal_pathgroups "off" -+ recheck_wwid "no" -+} -+blacklist { -+ devnode ".*" -+ device { -+ vendor "SGI" -+ product "Universal Xport" -+ } -+ device { -+ vendor "^DGC" -+ product "LUNZ" -+ } -+ device { -+ vendor "EMC" -+ product "LUNZ" -+ } -+ device { -+ vendor "DELL" -+ product "Universal Xport" -+ } -+ device { -+ vendor "FUJITSU" -+ product "Universal Xport" -+ } -+ device { -+ vendor "IBM" -+ product "Universal Xport" -+ } -+ device { -+ vendor "IBM" -+ product "S/390" -+ } -+ device { -+ vendor "LENOVO" -+ product "Universal Xport" -+ } -+ device { -+ vendor "(NETAPP|LSI|ENGENIO)" -+ product "Universal Xport" -+ } -+ device { -+ vendor "STK" -+ product "Universal Xport" -+ } -+ device { -+ vendor "SUN" -+ product "Universal Xport" -+ } -+ wwid ".*" -+ device { -+ vendor "(Intel|INTEL)" -+ product "VTrak V-LUN" -+ } -+ device { -+ vendor "Promise" -+ product "VTrak V-LUN" -+ } -+ device { -+ vendor "Promise" -+ product "Vess V-LUN" -+ } -+ protocol ".*" -+} -+blacklist_exceptions { -+ devnode "^sd[a-z]" -+ wwid "^3" -+ protocol "scsi" -+} -+devices { -+ device { -+ vendor "NVME" -+ product ".*" -+ uid_attribute "ID_WWN" -+ path_checker "none" -+ retain_attached_hw_handler "no" -+ } -+ device { -+ vendor "APPLE" -+ product "Xserve RAID" -+ path_grouping_policy "multibus" -+ } -+ device { -+ vendor "3PARdata" -+ product "VV" -+ path_grouping_policy "group_by_prio" -+ hardware_handler "1 alua" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 18 -+ fast_io_fail_tmo 10 -+ dev_loss_tmo "infinity" -+ vpd_vendor "hp3par" -+ } -+ device { -+ vendor "NVME" -+ product "HPE Alletra" -+ path_grouping_policy "group_by_prio" -+ failback "immediate" -+ no_path_retry "queue" -+ } -+ device { -+ vendor "DEC" -+ product "HSG80" -+ path_grouping_policy "group_by_prio" -+ path_checker "hp_sw" -+ hardware_handler "1 hp_sw" -+ prio "hp_sw" -+ no_path_retry "queue" -+ } -+ device { -+ vendor "HP" -+ product "A6189A" -+ path_grouping_policy "multibus" -+ no_path_retry 12 -+ } -+ device { -+ vendor "(COMPAQ|HP)" -+ product "(MSA|HSV)1[01]0" -+ path_grouping_policy "group_by_prio" -+ path_checker "hp_sw" -+ hardware_handler "1 hp_sw" -+ prio "hp_sw" -+ no_path_retry 12 -+ } -+ device { -+ vendor "(COMPAQ|HP)" -+ product "MSA VOLUME" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 12 -+ } -+ device { -+ vendor "(COMPAQ|HP)" -+ product "(HSV1[01]1|HSV2[01]0|HSV3[046]0|HSV4[05]0)" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 12 -+ } -+ device { -+ vendor "HP" -+ product "(MSA2[02]12fc|MSA2012i)" -+ path_grouping_policy "multibus" -+ no_path_retry 18 -+ } -+ device { -+ vendor "HP" -+ product "(MSA2012sa|MSA23(12|24)(fc|i|sa)|MSA2000s VOLUME)" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 18 -+ } -+ device { -+ vendor "(HP|HPE)" -+ product "MSA [12]0[4567]0 (SAN|SAS|FC|iSCSI)" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 18 -+ } -+ device { -+ vendor "HP" -+ product "(HSVX700|HSVX740)" -+ path_grouping_policy "group_by_prio" -+ hardware_handler "1 alua" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 12 -+ } -+ device { -+ vendor "HP" -+ product "LOGICAL VOLUME" -+ path_grouping_policy "multibus" -+ no_path_retry 12 -+ } -+ device { -+ vendor "HP" -+ product "(P2000 G3 FC|P2000G3 FC/iSCSI|P2000 G3 SAS|P2000 G3 iSCSI)" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 18 -+ } -+ device { -+ vendor "LEFTHAND" -+ product "(P4000|iSCSIDisk|FCDISK)" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 18 -+ } -+ device { -+ vendor "Nimble" -+ product "Server" -+ path_grouping_policy "group_by_prio" -+ hardware_handler "1 alua" -+ prio "alua" -+ failback "immediate" -+ no_path_retry "queue" -+ } -+ device { -+ vendor "SGI" -+ product "TP9100" -+ path_grouping_policy "multibus" -+ } -+ device { -+ vendor "SGI" -+ product "TP9[3457]00" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "SGI" -+ product "IS" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "SGI" -+ product "^DD[46]A-" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "DDN" -+ product "SAN DataDirector" -+ path_grouping_policy "multibus" -+ } -+ device { -+ vendor "DDN" -+ product "^EF3010" -+ path_grouping_policy "multibus" -+ no_path_retry 30 -+ } -+ device { -+ vendor "DDN" -+ product "^(EF3015|S2A|SFA)" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "NEXENTA" -+ product "COMSTAR" -+ path_grouping_policy "group_by_serial" -+ no_path_retry 30 -+ } -+ device { -+ vendor "TEGILE" -+ product "(ZEBI-(FC|ISCSI)|INTELLIFLASH)" -+ path_grouping_policy "group_by_prio" -+ hardware_handler "1 alua" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 10 -+ } -+ device { -+ vendor "EMC" -+ product "SYMMETRIX" -+ path_grouping_policy "multibus" -+ no_path_retry 6 -+ } -+ device { -+ vendor "^DGC" -+ product "^(RAID|DISK|VRAID)" -+ product_blacklist "LUNZ" -+ path_grouping_policy "group_by_prio" -+ path_checker "emc_clariion" -+ hardware_handler "1 emc" -+ prio "emc" -+ failback "immediate" -+ no_path_retry 60 -+ detect_checker "no" -+ } -+ device { -+ vendor "EMC" -+ product "Invista" -+ product_blacklist "LUNZ" -+ path_grouping_policy "multibus" -+ no_path_retry 5 -+ } -+ device { -+ vendor "XtremIO" -+ product "XtremApp" -+ path_grouping_policy "multibus" -+ } -+ device { -+ vendor "COMPELNT" -+ product "Compellent Vol" -+ path_grouping_policy "group_by_prio" -+ failback "immediate" -+ no_path_retry "queue" -+ } -+ device { -+ vendor "DELL" -+ product "^MD3" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "NVME" -+ product "^EMC PowerMax_" -+ path_grouping_policy "multibus" -+ } -+ device { -+ vendor "DellEMC" -+ product "PowerStore" -+ path_grouping_policy "group_by_prio" -+ hardware_handler "1 alua" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 3 -+ fast_io_fail_tmo 15 -+ } -+ device { -+ vendor "DellEMC" -+ product "^ME" -+ path_grouping_policy "group_by_prio" -+ hardware_handler "1 alua" -+ prio "alua" -+ failback "immediate" -+ } -+ device { -+ vendor "FSC" -+ product "CentricStor" -+ path_grouping_policy "group_by_serial" -+ } -+ device { -+ vendor "FUJITSU" -+ product "ETERNUS_DX(H|L|M|400|8000)" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 10 -+ } -+ device { -+ vendor "(EUROLOGC|EuroLogc)" -+ product "FC2502" -+ path_grouping_policy "multibus" -+ } -+ device { -+ vendor "FUJITSU" -+ product "E[234]000" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 10 -+ } -+ device { -+ vendor "FUJITSU" -+ product "E[68]000" -+ path_grouping_policy "multibus" -+ no_path_retry 10 -+ } -+ device { -+ vendor "FUJITSU" -+ product "ETERNUS_AHB" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "(HITACHI|HP|HPE)" -+ product "^OPEN-" -+ path_grouping_policy "multibus" -+ } -+ device { -+ vendor "HITACHI" -+ product "^DF" -+ path_grouping_policy "group_by_prio" -+ prio "hds" -+ failback "immediate" -+ no_path_retry "queue" -+ } -+ device { -+ vendor "HITACHI" -+ product "^DF600F" -+ path_grouping_policy "multibus" -+ } -+ device { -+ vendor "IBM" -+ product "ProFibre 4000R" -+ path_grouping_policy "multibus" -+ } -+ device { -+ vendor "IBM" -+ product "^1722-600" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "IBM" -+ product "^1724" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "IBM" -+ product "^1726" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "IBM" -+ product "^1742" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "IBM" -+ product "^1746" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "IBM" -+ product "^1813" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "IBM" -+ product "^1814" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "IBM" -+ product "^1815" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "IBM" -+ product "^1818" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "IBM" -+ product "^3526" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "IBM" -+ product "^(3542|3552)" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "IBM" -+ product "^2105" -+ path_grouping_policy "multibus" -+ no_path_retry "queue" -+ } -+ device { -+ vendor "IBM" -+ product "^1750500" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry "queue" -+ } -+ device { -+ vendor "IBM" -+ product "^2107900" -+ path_grouping_policy "group_by_prio" -+ failback "immediate" -+ no_path_retry "queue" -+ } -+ device { -+ vendor "IBM" -+ product "^2145" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry "queue" -+ } -+ device { -+ vendor "IBM" -+ product "S/390 DASD ECKD" -+ product_blacklist "S/390" -+ path_grouping_policy "multibus" -+ uid_attribute "ID_UID" -+ path_checker "directio" -+ no_path_retry "queue" -+ } -+ device { -+ vendor "IBM" -+ product "S/390 DASD FBA" -+ product_blacklist "S/390" -+ path_grouping_policy "multibus" -+ uid_attribute "ID_UID" -+ path_checker "directio" -+ no_path_retry "queue" -+ } -+ device { -+ vendor "IBM" -+ product "^IPR" -+ path_grouping_policy "group_by_prio" -+ hardware_handler "1 alua" -+ prio "alua" -+ failback "immediate" -+ no_path_retry "queue" -+ } -+ device { -+ vendor "IBM" -+ product "1820N00" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry "queue" -+ } -+ device { -+ vendor "(XIV|IBM)" -+ product "(NEXTRA|2810XIV)" -+ path_grouping_policy "group_by_prio" -+ failback 15 -+ no_path_retry "queue" -+ } -+ device { -+ vendor "(TMS|IBM)" -+ product "(RamSan|FlashSystem)" -+ path_grouping_policy "multibus" -+ } -+ device { -+ vendor "IBM" -+ product "^(DCS9900|2851)" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "AIX" -+ product "VDASD" -+ path_grouping_policy "multibus" -+ no_path_retry 60 -+ } -+ device { -+ vendor "IBM" -+ product "3303[ ]+NVDISK" -+ no_path_retry 60 -+ } -+ device { -+ vendor "AIX" -+ product "NVDISK" -+ path_grouping_policy "group_by_prio" -+ hardware_handler "1 alua" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 60 -+ } -+ device { -+ vendor "LENOVO" -+ product "DE_Series" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "NETAPP" -+ product "LUN" -+ path_grouping_policy "group_by_prio" -+ features "2 pg_init_retries 50" -+ prio "ontap" -+ failback "immediate" -+ no_path_retry "queue" -+ flush_on_last_del "always" -+ dev_loss_tmo "infinity" -+ user_friendly_names "no" -+ } -+ device { -+ vendor "(NETAPP|LSI|ENGENIO)" -+ product "INF-01-00" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "SolidFir" -+ product "SSD SAN" -+ path_grouping_policy "multibus" -+ no_path_retry 24 -+ } -+ device { -+ vendor "NVME" -+ product "^NetApp ONTAP Controller" -+ path_grouping_policy "multibus" -+ no_path_retry "queue" -+ } -+ device { -+ vendor "NEC" -+ product "DISK ARRAY" -+ path_grouping_policy "group_by_prio" -+ hardware_handler "1 alua" -+ prio "alua" -+ failback "immediate" -+ } -+ device { -+ vendor "^Pillar" -+ product "^Axiom" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ } -+ device { -+ vendor "^Oracle" -+ product "^Oracle FS" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ } -+ device { -+ vendor "STK" -+ product "BladeCtlr" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "STK" -+ product "OPENstorage" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "STK" -+ product "FLEXLINE 380" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "SUN" -+ product "StorEdge 3" -+ path_grouping_policy "multibus" -+ } -+ device { -+ vendor "SUN" -+ product "STK6580_6780" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "SUN" -+ product "CSM[12]00_R" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "SUN" -+ product "LCSM100_[IEFS]" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "SUN" -+ product "SUN_6180" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "SUN" -+ product "ArrayStorage" -+ product_blacklist "Universal Xport" -+ path_grouping_policy "group_by_prio" -+ path_checker "rdac" -+ features "2 pg_init_retries 50" -+ hardware_handler "1 rdac" -+ prio "rdac" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "SUN" -+ product "(Sun Storage|ZFS Storage|COMSTAR)" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "PIVOT3" -+ product "RAIGE VOLUME" -+ path_grouping_policy "multibus" -+ no_path_retry "queue" -+ } -+ device { -+ vendor "(NexGen|Pivot3)" -+ product "(TierStore|vSTAC)" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry "queue" -+ } -+ device { -+ vendor "(Intel|INTEL)" -+ product "Multi-Flex" -+ product_blacklist "VTrak V-LUN" -+ path_grouping_policy "group_by_prio" -+ hardware_handler "1 alua" -+ prio "alua" -+ failback "immediate" -+ no_path_retry "queue" -+ } -+ device { -+ vendor "(LIO-ORG|SUSE)" -+ product "RBD" -+ path_grouping_policy "group_by_prio" -+ hardware_handler "1 alua" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 12 -+ } -+ device { -+ vendor "DataCore" -+ product "SANmelody" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry "queue" -+ } -+ device { -+ vendor "DataCore" -+ product "Virtual Disk" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry "queue" -+ } -+ device { -+ vendor "PURE" -+ product "FlashArray" -+ path_grouping_policy "group_by_prio" -+ hardware_handler "1 alua" -+ prio "alua" -+ failback "immediate" -+ fast_io_fail_tmo 10 -+ } -+ device { -+ vendor "HUAWEI" -+ product "XSG1" -+ path_grouping_policy "group_by_prio" -+ failback "immediate" -+ no_path_retry 15 -+ } -+ device { -+ vendor "KOVE" -+ product "XPD" -+ path_grouping_policy "multibus" -+ } -+ device { -+ vendor "NFINIDAT" -+ product "InfiniBox" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry "queue" -+ flush_on_last_del "always" -+ fast_io_fail_tmo 15 -+ dev_loss_tmo "infinity" -+ detect_prio "no" -+ } -+ device { -+ vendor "KMNRIO" -+ product "K2" -+ path_grouping_policy "multibus" -+ } -+ device { -+ vendor "NEXSAN" -+ product "NXS-B0" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 15 -+ } -+ device { -+ vendor "NEXSAN" -+ product "SATAB" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 15 -+ } -+ device { -+ vendor "Nexsan" -+ product "(NestOS|NST5000)" -+ path_grouping_policy "group_by_prio" -+ hardware_handler "1 alua" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "VIOLIN" -+ product "SAN ARRAY$" -+ path_grouping_policy "group_by_serial" -+ no_path_retry 30 -+ } -+ device { -+ vendor "VIOLIN" -+ product "SAN ARRAY ALUA" -+ path_grouping_policy "group_by_prio" -+ hardware_handler "1 alua" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "VIOLIN" -+ product "CONCERTO ARRAY" -+ path_grouping_policy "multibus" -+ no_path_retry 30 -+ } -+ device { -+ vendor "(XIOTECH|XIOtech)" -+ product "ISE" -+ path_grouping_policy "multibus" -+ no_path_retry 12 -+ } -+ device { -+ vendor "(XIOTECH|XIOtech)" -+ product "IGLU DISK" -+ path_grouping_policy "multibus" -+ no_path_retry 30 -+ } -+ device { -+ vendor "(XIOTECH|XIOtech)" -+ product "Magnitude" -+ path_grouping_policy "multibus" -+ no_path_retry 30 -+ } -+ device { -+ vendor "Vexata" -+ product "VX" -+ path_grouping_policy "multibus" -+ no_path_retry 30 -+ } -+ device { -+ vendor "Promise" -+ product "VTrak" -+ product_blacklist "VTrak V-LUN" -+ path_grouping_policy "group_by_prio" -+ hardware_handler "1 alua" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "Promise" -+ product "Vess" -+ product_blacklist "Vess V-LUN" -+ path_grouping_policy "group_by_prio" -+ hardware_handler "1 alua" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "^IFT" -+ product ".*" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "DotHill" -+ product "SANnet" -+ path_grouping_policy "multibus" -+ no_path_retry 30 -+ } -+ device { -+ vendor "DotHill" -+ product "R/Evo" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "DotHill" -+ product "^DH" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 30 -+ } -+ device { -+ vendor "AStor" -+ product "NeoSapphire" -+ path_grouping_policy "multibus" -+ no_path_retry 30 -+ } -+ device { -+ vendor "INSPUR" -+ product "MCS" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ } -+ device { -+ vendor "MacroSAN" -+ product "LU" -+ path_grouping_policy "group_by_prio" -+ prio "alua" -+ failback "immediate" -+ no_path_retry 30 -+ } -+} -+ -+overrides { -+ path_grouping_policy "multibus" -+ protocol { -+ type scsi:fcp -+ fast_io_fail_tmo 5 -+ } -+} -+ -+multipaths { -+ multipath { -+ wwid "333333330000007d0" -+ alias "test" -+ } -+ multipath { -+ wwid "33333333000001388" -+ alias "foo" -+ } -+} -+ -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/conf1.d/bindings_set.conf b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/conf1.d/bindings_set.conf -new file mode 100644 -index 00000000..9b5120f9 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/conf1.d/bindings_set.conf -@@ -0,0 +1,3 @@ -+defaults { -+ bindings_file "/etc/multipath/bindings" -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/conf1.d/empty.conf b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/conf1.d/empty.conf -new file mode 100644 -index 00000000..8b137891 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/conf1.d/empty.conf -@@ -0,0 +1 @@ -+ -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/conf2.d/getuid_set.conf b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/conf2.d/getuid_set.conf -new file mode 100644 -index 00000000..da0f9677 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/conf2.d/getuid_set.conf -@@ -0,0 +1,11 @@ -+defaults { -+ user_friendly_names yes -+} -+ -+devices { -+ device { -+ vendor "foo" -+ product "bar" -+ getuid_callout "/lib/udev/scsi_id --whitelisted --device=/dev/%n" -+ } -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/conf2.d/set_files.conf b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/conf2.d/set_files.conf -new file mode 100644 -index 00000000..26c250b0 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/conf2.d/set_files.conf -@@ -0,0 +1,5 @@ -+defaults { -+ bindings_file "/etc/multipath/bindings" -+ wwids_file "/etc/multipath/wwids" -+ prkeys_file "/etc/multipath/prkeys" -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/conf3.d/README b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/conf3.d/README -new file mode 100644 -index 00000000..d846e286 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/conf3.d/README -@@ -0,0 +1 @@ -+This directory is intentionally left without .conf files. -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/config_dir.conf b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/config_dir.conf -new file mode 100644 -index 00000000..6b56d7f6 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/config_dir.conf -@@ -0,0 +1,5 @@ -+defaults { -+ user_friendly_names yes -+ find_multipaths yes -+ config_dir "conf1.d" -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/default_rhel9.conf b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/default_rhel9.conf -new file mode 100644 -index 00000000..c1076198 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/default_rhel9.conf -@@ -0,0 +1,21 @@ -+# device-mapper-multipath configuration file -+ -+# For a complete list of the default configuration values, run either: -+# # multipath -t -+# or -+# # multipathd show config -+ -+# For a list of configuration options with descriptions, see the -+# multipath.conf man page. -+ -+defaults { -+ user_friendly_names yes -+ find_multipaths yes -+} -+ -+blacklist_exceptions { -+ property "(SCSI_IDENT_|ID_WWN)" -+} -+ -+blacklist { -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/empty.conf b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/empty.conf -new file mode 100644 -index 00000000..8b137891 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/empty.conf -@@ -0,0 +1 @@ -+ -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/empty_dir.conf b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/empty_dir.conf -new file mode 100644 -index 00000000..a163ed7e ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/empty_dir.conf -@@ -0,0 +1,5 @@ -+defaults { -+ user_friendly_names yes -+ find_multipaths yes -+ config_dir "conf3.d" -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/extra_slash.conf b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/extra_slash.conf -new file mode 100644 -index 00000000..ce7ea02a ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/extra_slash.conf -@@ -0,0 +1,5 @@ -+defaults { -+ user_friendly_names yes -+ find_multipaths yes -+ config_dir "/etc/multipath/conf.d/" -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/getuid_defaults.conf b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/getuid_defaults.conf -new file mode 100644 -index 00000000..326d677a ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/getuid_defaults.conf -@@ -0,0 +1,5 @@ -+defaults { -+ user_friendly_names yes -+ find_multipaths yes -+ getuid_callout "/lib/udev/scsi_id --whitelisted --device=/dev/%n" -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/getuid_devices.conf b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/getuid_devices.conf -new file mode 100644 -index 00000000..75abb4cb ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/getuid_devices.conf -@@ -0,0 +1,12 @@ -+defaults { -+ user_friendly_names yes -+ find_multipaths yes -+} -+ -+devices { -+ device { -+ vendor "foo" -+ product "bar" -+ getuid_callout "/lib/udev/scsi_id --whitelisted --device=/dev/%n" -+ } -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/getuid_overrides.conf b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/getuid_overrides.conf -new file mode 100644 -index 00000000..8861a2d7 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/getuid_overrides.conf -@@ -0,0 +1,8 @@ -+defaults { -+ user_friendly_names yes -+ find_multipaths yes -+} -+ -+overrides { -+ getuid_callout "/lib/udev/scsi_id --whitelisted --device=/dev/%n" -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/missing_dir.conf b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/missing_dir.conf -new file mode 100644 -index 00000000..6a4bf49f ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/missing_dir.conf -@@ -0,0 +1,5 @@ -+defaults { -+ user_friendly_names yes -+ find_multipaths yes -+ config_dir "missing" -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/no_defaults.conf b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/no_defaults.conf -new file mode 100644 -index 00000000..f8d54d09 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/files/no_defaults.conf -@@ -0,0 +1,7 @@ -+devices { -+ device { -+ vendor "foo" -+ product "bar" -+ getuid_callout "/lib/udev/scsi_id --whitelisted --device=/dev/%n" -+ } -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/test_multipath_conf_read_9to10.py b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/test_multipath_conf_read_9to10.py -new file mode 100644 -index 00000000..cb90fcc3 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/test_multipath_conf_read_9to10.py -@@ -0,0 +1,283 @@ -+import os -+ -+import pytest -+ -+from leapp.libraries.actor import multipathconfread -+from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked -+from leapp.libraries.stdlib import api -+from leapp.models import MultipathConfFacts9to10, MultipathConfig9to10, MultipathInfo -+ -+TEST_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'files') -+ -+ -+def build_config(pathname, config_dir=None, bindings_file=None, wwids_file=None, -+ prkeys_file=None, has_getuid=False): -+ return MultipathConfig9to10( -+ pathname=pathname, -+ config_dir=config_dir, -+ bindings_file=bindings_file, -+ wwids_file=wwids_file, -+ prkeys_file=prkeys_file, -+ has_getuid=has_getuid, -+ ) -+ -+ -+def assert_config(config, expected): -+ assert config.pathname == expected.pathname -+ assert config.config_dir == expected.config_dir -+ assert config.bindings_file == expected.bindings_file -+ assert config.wwids_file == expected.wwids_file -+ assert config.prkeys_file == expected.prkeys_file -+ assert config.has_getuid == expected.has_getuid -+ -+ -+default_rhel9_conf = build_config( -+ os.path.join(TEST_DIR, 'default_rhel9.conf')) -+ -+empty_conf = build_config( -+ os.path.join(TEST_DIR, 'empty.conf')) -+ -+getuid_defaults_conf = build_config( -+ os.path.join(TEST_DIR, 'getuid_defaults.conf'), has_getuid=True) -+ -+getuid_devices_conf = build_config( -+ os.path.join(TEST_DIR, 'getuid_devices.conf'), has_getuid=True) -+ -+getuid_overrides_conf = build_config( -+ os.path.join(TEST_DIR, 'getuid_overrides.conf'), has_getuid=True) -+ -+all_options_conf = build_config( -+ os.path.join(TEST_DIR, 'all_options.conf'), -+ config_dir='/etc/multipath/conf.d', -+ bindings_file='/etc/multipath/bindings', -+ wwids_file='/etc/multipath/wwids', -+ prkeys_file='/etc/multipath/prkeys', -+ has_getuid=True) -+ -+no_defaults_conf = build_config( -+ os.path.join(TEST_DIR, 'no_defaults.conf'), has_getuid=True) -+ -+complicated_conf = build_config( -+ os.path.join(TEST_DIR, 'complicated.conf'), -+ config_dir='/etc/multipath/conf.d', -+ bindings_file='/etc/multipath/bindings', -+ wwids_file='/etc/multipath/wwids', -+ prkeys_file='/etc/multipath/prkeys') -+ -+ -+def test_parse_config(): -+ test_map = {'default_rhel9.conf': default_rhel9_conf, -+ 'empty.conf': empty_conf, -+ 'getuid_defaults.conf': getuid_defaults_conf, -+ 'getuid_devices.conf': getuid_devices_conf, -+ 'getuid_overrides.conf': getuid_overrides_conf, -+ 'all_options.conf': all_options_conf, -+ 'no_defaults.conf': no_defaults_conf, -+ 'complicated.conf': complicated_conf} -+ for config_name, expected_data in test_map.items(): -+ config = multipathconfread._parse_config(os.path.join(TEST_DIR, config_name)) -+ assert config -+ assert_config(config, expected_data) -+ -+ -+extra_slash_conf = build_config( -+ os.path.join(TEST_DIR, 'extra_slash.conf'), -+ config_dir=os.path.join(TEST_DIR, '/etc/multipath/conf.d/')) -+ -+missing_dir_conf = build_config( -+ os.path.join(TEST_DIR, 'missing_dir.conf'), -+ config_dir=os.path.join(TEST_DIR, 'missing')) -+ -+empty_dir_conf = build_config( -+ os.path.join(TEST_DIR, 'empty_dir.conf'), -+ config_dir=os.path.join(TEST_DIR, 'conf3.d')) -+ -+config_dir_conf = build_config( -+ os.path.join(TEST_DIR, 'config_dir.conf'), -+ config_dir=os.path.join(TEST_DIR, 'conf1.d')) -+ -+bindings_set_conf = build_config( -+ os.path.join(TEST_DIR, 'conf1.d/bindings_set.conf'), -+ bindings_file='/etc/multipath/bindings') -+ -+empty1_conf = build_config( -+ os.path.join(TEST_DIR, 'conf1.d/empty.conf')) -+ -+all_set_in_dir_conf = build_config( -+ os.path.join(TEST_DIR, 'all_set_in_dir.conf'), -+ config_dir=os.path.join(TEST_DIR, 'conf2.d')) -+ -+getuid_set_conf = build_config( -+ os.path.join(TEST_DIR, 'conf2.d/getuid_set.conf'), -+ has_getuid=True) -+ -+set_files_conf = build_config( -+ os.path.join(TEST_DIR, 'conf2.d/set_files.conf'), -+ bindings_file='/etc/multipath/bindings', -+ wwids_file='/etc/multipath/wwids', -+ prkeys_file='/etc/multipath/prkeys') -+ -+ -+def mock_parse_config(path): -+ """Convert config_dir into full pathname""" -+ conf = multipathconfread._parse_config_orig(path) -+ if not conf: -+ return None -+ if conf.config_dir: -+ conf.config_dir = os.path.join(TEST_DIR, conf.config_dir) -+ return conf -+ -+ -+def mock_parse_config_dir(path): -+ assert os.path.normpath(path) == '/etc/multipath/conf.d' -+ return [] -+ -+ -+@pytest.mark.parametrize( -+ ('primary_config', 'expected_config'), -+ [ -+ ('all_options.conf', all_options_conf), -+ ('default_rhel9.conf', default_rhel9_conf), -+ ('extra_slash.conf', extra_slash_conf), -+ ] -+) -+def test_get_primary_facts_default_config_dir(monkeypatch, primary_config, expected_config): -+ monkeypatch.setattr(multipathconfread, 'is_processable', lambda: True) -+ monkeypatch.setattr(multipathconfread, '_check_socket_activation', lambda: True) -+ monkeypatch.setattr(multipathconfread, '_check_dm_nvme_multipathing', lambda: True) -+ monkeypatch.setattr(multipathconfread, '_parse_config_dir', mock_parse_config_dir) -+ -+ produce_mock = produce_mocked() -+ monkeypatch.setattr(api, 'produce', produce_mock) -+ -+ actor_mock = CurrentActorMocked(src_ver='9.6', dst_ver='10.0') -+ monkeypatch.setattr(api, 'current_actor', actor_mock) -+ -+ config_to_use = os.path.join(TEST_DIR, primary_config) -+ multipathconfread.scan_and_emit_multipath_info(config_to_use) -+ -+ assert produce_mock.called -+ -+ general_info = [msg for msg in produce_mock.model_instances if isinstance(msg, MultipathInfo)] -+ assert len(general_info) == 1 -+ assert general_info[0].is_configured -+ assert general_info[0].config_dir == '/etc/multipath/conf.d' -+ -+ msgs = [msg for msg in produce_mock.model_instances if isinstance(msg, MultipathConfFacts9to10)] -+ assert len(msgs) == 1 -+ -+ actual_configs = msgs[0].configs -+ assert len(actual_configs) == 1 -+ -+ assert_config(actual_configs[0], expected_config) -+ -+ -+@pytest.mark.parametrize( -+ ('primary_config', 'expected_configs'), -+ [ -+ ('missing_dir.conf', [missing_dir_conf]), -+ ('empty_dir.conf', [empty_dir_conf]), -+ ('config_dir.conf', [config_dir_conf, bindings_set_conf, empty1_conf]), -+ ('all_set_in_dir.conf', [all_set_in_dir_conf, getuid_set_conf, set_files_conf]), -+ ] -+) -+def test_get_facts_with_config_dir(monkeypatch, primary_config, expected_configs): -+ monkeypatch.setattr(multipathconfread, '_parse_config_orig', multipathconfread._parse_config, raising=False) -+ monkeypatch.setattr(multipathconfread, '_parse_config', mock_parse_config) -+ monkeypatch.setattr(multipathconfread, 'is_processable', lambda: True) -+ monkeypatch.setattr(multipathconfread, '_check_socket_activation', lambda: True) -+ monkeypatch.setattr(multipathconfread, '_check_dm_nvme_multipathing', lambda: False) -+ -+ produce_mock = produce_mocked() -+ monkeypatch.setattr(api, 'produce', produce_mock) -+ -+ actor_mock = CurrentActorMocked(src_ver='9.6', dst_ver='10.0') -+ monkeypatch.setattr(api, 'current_actor', actor_mock) -+ -+ config_to_use = os.path.join(TEST_DIR, primary_config) -+ multipathconfread.scan_and_emit_multipath_info(config_to_use) -+ -+ assert produce_mock.called -+ -+ general_info = [msg for msg in produce_mock.model_instances if isinstance(msg, MultipathInfo)] -+ assert len(general_info) == 1 -+ assert general_info[0].is_configured -+ assert not general_info[0].config_dir -+ -+ msgs = [msg for msg in produce_mock.model_instances if isinstance(msg, MultipathConfFacts9to10)] -+ assert len(msgs) == 1 -+ -+ actual_configs = msgs[0].configs -+ assert len(actual_configs) == len(expected_configs) -+ -+ for actual_config, expected_config in zip(actual_configs, expected_configs): -+ assert_config(actual_config, expected_config) -+ -+ -+def test_check_socket_activation(monkeypatch): -+ monkeypatch.setattr(os.path, 'exists', lambda path: True) -+ assert multipathconfread._check_socket_activation() is True -+ -+ monkeypatch.setattr(os.path, 'exists', lambda path: False) -+ assert multipathconfread._check_socket_activation() is False -+ -+ -+def test_check_dm_nvme_multipathing_no_module(monkeypatch): -+ monkeypatch.setattr(os.path, 'isdir', lambda path: False) -+ assert multipathconfread._check_dm_nvme_multipathing() is False -+ -+ -+def test_check_dm_nvme_multipathing_disabled(monkeypatch): -+ monkeypatch.setattr(os.path, 'isdir', lambda path: True) -+ monkeypatch.setattr(multipathconfread, 'open', -+ lambda path, mode='r': _mock_open('N'), raising=False) -+ assert multipathconfread._check_dm_nvme_multipathing() is True -+ -+ -+def test_check_dm_nvme_multipathing_enabled(monkeypatch): -+ monkeypatch.setattr(os.path, 'isdir', lambda path: True) -+ monkeypatch.setattr(multipathconfread, 'open', -+ lambda path, mode='r': _mock_open('Y'), raising=False) -+ assert multipathconfread._check_dm_nvme_multipathing() is False -+ -+ -+class _mock_open: -+ def __init__(self, content): -+ self._content = content -+ -+ def __enter__(self): -+ return self -+ -+ def __exit__(self, *args): -+ pass -+ -+ def read(self): -+ return self._content -+ -+ -+def test_system_fields_on_primary(monkeypatch): -+ monkeypatch.setattr(multipathconfread, 'is_processable', lambda: True) -+ monkeypatch.setattr(multipathconfread, '_check_socket_activation', lambda: True) -+ monkeypatch.setattr(multipathconfread, '_check_dm_nvme_multipathing', lambda: True) -+ monkeypatch.setattr(multipathconfread, '_parse_config_dir', lambda config_dir: []) -+ -+ produce_mock = produce_mocked() -+ monkeypatch.setattr(api, 'produce', produce_mock) -+ -+ actor_mock = CurrentActorMocked(src_ver='9.6', dst_ver='10.0') -+ monkeypatch.setattr(api, 'current_actor', actor_mock) -+ -+ config_to_use = os.path.join(TEST_DIR, 'default_rhel9.conf') -+ multipathconfread.scan_and_emit_multipath_info(config_to_use) -+ -+ assert produce_mock.called -+ -+ msgs = [msg for msg in produce_mock.model_instances if isinstance(msg, MultipathConfFacts9to10)] -+ assert len(msgs) == 1 -+ -+ configs = msgs[0].configs -+ assert len(configs) == 1 -+ -+ primary = configs[0] -+ assert primary.has_socket_activation is True -+ assert primary.has_dm_nvme_multipathing is True --- -2.54.0 - diff --git a/0056-multipath-Add-MultipathConfCheck9to10-mpath_conf_che.patch b/0056-multipath-Add-MultipathConfCheck9to10-mpath_conf_che.patch deleted file mode 100644 index aa112fb..0000000 --- a/0056-multipath-Add-MultipathConfCheck9to10-mpath_conf_che.patch +++ /dev/null @@ -1,522 +0,0 @@ -From 29de17c95fe77ebd4da83f55d12ef294ecd29544 Mon Sep 17 00:00:00 2001 -From: Benjamin Marzinski -Date: Tue, 24 Mar 2026 17:31:26 -0400 -Subject: [PATCH 056/105] multipath: Add MultipathConfCheck9to10 - mpath_conf_check actor - -Add the el9toel10 mpath_conf_check actor that consumes -MultipathConfFacts9to10 and reports on multipath configuration changes -needed for the RHEL 9 to RHEL 10 upgrade: deprecated config_dir, -deprecated file paths (bindings/wwids/prkeys), socket activation -disabled by default, unsupported DM NVMe multipathing, and -getuid_callout as an upgrade inhibitor. - -Also, if config_dir is not /etc/multipath/conf.d, the upgrade will be -inhibited if /etc/multipath/conf.d already exists and has config files -in it. Otherwise they would become part of the new config, after the -upgrade. - -Jira: RHEL-151509 - -Co-Authored-By: Claude Opus 4.6 ---- - .../multipath/mpath_conf_check/actor.py | 25 ++ - .../libraries/mpath_conf_check.py | 188 +++++++++++++ - .../tests/test_multipath_conf_check_9to10.py | 258 ++++++++++++++++++ - 3 files changed, 471 insertions(+) - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/actor.py - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/libraries/mpath_conf_check.py - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/tests/test_multipath_conf_check_9to10.py - -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/actor.py b/repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/actor.py -new file mode 100644 -index 00000000..20feb767 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/actor.py -@@ -0,0 +1,25 @@ -+from leapp.actors import Actor -+from leapp.libraries.actor import mpath_conf_check -+from leapp.models import MultipathConfFacts9to10 -+from leapp.reporting import Report -+from leapp.tags import ChecksPhaseTag, IPUWorkflowTag -+ -+ -+class MultipathConfCheck9to10(Actor): -+ """ -+ Checks if changes to the multipath configuration files are necessary -+ for upgrading to RHEL10, and reports the results. -+ """ -+ -+ name = 'multipath_conf_check_9to10' -+ consumes = (MultipathConfFacts9to10,) -+ produces = (Report,) -+ tags = (ChecksPhaseTag, IPUWorkflowTag) -+ -+ def process(self): -+ facts = next(self.consume(MultipathConfFacts9to10), None) -+ if facts is None: -+ self.log.debug('Skipping execution. No MultipathConfFacts9to10 has ' -+ 'been produced') -+ return -+ mpath_conf_check.check_configs(facts) -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/libraries/mpath_conf_check.py b/repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/libraries/mpath_conf_check.py -new file mode 100644 -index 00000000..49aefc5a ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/libraries/mpath_conf_check.py -@@ -0,0 +1,188 @@ -+import os -+ -+from leapp import reporting -+from leapp.reporting import create_report -+ -+_DEFAULT_CONFIG_DIR = '/etc/multipath/conf.d' -+_DEFAULT_BINDINGS_FILE = '/etc/multipath/bindings' -+_DEFAULT_WWIDS_FILE = '/etc/multipath/wwids' -+_DEFAULT_PRKEYS_FILE = '/etc/multipath/prkeys' -+ -+ -+def _default_config_dir_has_conf_files(): -+ if not os.path.exists(_DEFAULT_CONFIG_DIR): -+ return False -+ for filename in os.listdir(_DEFAULT_CONFIG_DIR): -+ if filename.endswith('.conf'): -+ return True -+ return False -+ -+ -+def _report_config_dir(config_dir): -+ create_report([ -+ reporting.Title( -+ 'device-mapper-multipath custom config_dir is deprecated' -+ ), -+ reporting.Summary( -+ 'The multipath configuration option "config_dir" is set to ' -+ '"{cfg_dir}". In RHEL-10, this option is deprecated and unused. ' -+ 'The only valid configuration directory is "{def_cfg_dir}". Any ' -+ 'configuration files in "{cfg_dir}" will be moved to ' -+ '"{def_cfg_dir}".'.format( -+ cfg_dir=config_dir, def_cfg_dir=_DEFAULT_CONFIG_DIR)), -+ reporting.Severity(reporting.Severity.INFO), -+ reporting.Groups([reporting.Groups.SERVICES]), -+ reporting.RelatedResource('package', 'device-mapper-multipath') -+ ]) -+ -+ -+def _report_config_dir_conflict(config_dir): -+ create_report([ -+ reporting.Title( -+ 'device-mapper-multipath config_dir conflict' -+ ), -+ reporting.Summary( -+ 'The multipath configuration option "config_dir" is set to ' -+ '"{cfg_dir}". During the upgrade, configuration files from ' -+ '"{cfg_dir}" will be moved to "{def_cfg_dir}". However, ' -+ '"{def_cfg_dir}" already contains .conf files. These existing ' -+ 'files would be added to the multipath configuration after the ' -+ 'upgrade. Please remove or relocate the files in "{def_cfg_dir}" ' -+ 'before upgrading.'.format( -+ cfg_dir=config_dir, def_cfg_dir=_DEFAULT_CONFIG_DIR)), -+ reporting.Severity(reporting.Severity.HIGH), -+ reporting.Groups([reporting.Groups.INHIBITOR]), -+ reporting.RelatedResource('package', 'device-mapper-multipath') -+ ]) -+ -+ -+def _report_files(file_list): -+ details = ', '.join( -+ '{} (currently "{}") will be moved to "{}"'.format(name, current, default) -+ for name, current, default in file_list -+ ) -+ create_report([ -+ reporting.Title( -+ 'device-mapper-multipath configuration files will be moved' -+ ), -+ reporting.Summary( -+ 'The following multipath configuration file locations are ' -+ 'deprecated and unused in RHEL-10. The files will be moved ' -+ 'to their default locations: {}.'.format(details)), -+ reporting.Severity(reporting.Severity.INFO), -+ reporting.Groups([reporting.Groups.SERVICES]), -+ reporting.RelatedResource('package', 'device-mapper-multipath') -+ ]) -+ -+ -+def _report_socket_activation(): -+ create_report([ -+ reporting.Title( -+ 'device-mapper-multipath socket activation is disabled by default' -+ ), -+ reporting.Summary( -+ 'In RHEL-10, multipathd socket activation is disabled by ' -+ 'default. If you wish to re-enable it, uncomment ' -+ '"WantedBy=sockets.target" in ' -+ '/lib/systemd/system/multipathd.socket'), -+ reporting.Severity(reporting.Severity.INFO), -+ reporting.Groups([reporting.Groups.SERVICES]), -+ reporting.RelatedResource('package', 'device-mapper-multipath') -+ ]) -+ -+ -+def _report_dm_nvme_multipathing(): -+ create_report([ -+ reporting.Title( -+ 'device-mapper-multipath NVMe multipathing is no longer supported' -+ ), -+ reporting.Summary( -+ 'Only Native NVMe multipathing is supported in RHEL-10. Any ' -+ 'multipath NVMe devices will still work, but they will no ' -+ 'longer be managed by dm-multipath.'), -+ reporting.Severity(reporting.Severity.INFO), -+ reporting.Groups([reporting.Groups.SERVICES]), -+ reporting.RelatedResource('package', 'device-mapper-multipath') -+ ]) -+ -+ -+def _create_paths_str(paths): -+ if len(paths) < 2: -+ return paths[0] -+ return '{} and {}'.format(', '.join(paths[0:-1]), paths[-1]) -+ -+ -+def _report_getuid(paths): -+ paths_str = _create_paths_str(paths) -+ create_report([ -+ reporting.Title( -+ 'device-mapper-multipath configuration contains getuid_callout' -+ ), -+ reporting.Summary( -+ 'The "getuid_callout" option is no longer supported in ' -+ 'RHEL-10. It must be removed from the multipath ' -+ 'configuration before upgrading. The option was found in ' -+ '{}.'.format(paths_str)), -+ reporting.Severity(reporting.Severity.HIGH), -+ reporting.Groups([reporting.Groups.INHIBITOR]), -+ reporting.RelatedResource('package', 'device-mapper-multipath') -+ ]) -+ -+ -+def check_configs(facts): -+ if not facts.configs: -+ return -+ -+ primary = facts.configs[0] -+ -+ # config_dir: only valid in primary config -+ config_dir = primary.config_dir -+ if ( -+ config_dir is not None and -+ os.path.normpath(config_dir) != _DEFAULT_CONFIG_DIR -+ ): -+ _report_config_dir(config_dir) -+ if _default_config_dir_has_conf_files(): -+ _report_config_dir_conflict(config_dir) -+ -+ # bindings_file, wwids_file, prkeys_file: last non-None value wins -+ bindings_file = None -+ wwids_file = None -+ prkeys_file = None -+ for conf in facts.configs: -+ if conf.bindings_file is not None: -+ bindings_file = conf.bindings_file -+ if conf.wwids_file is not None: -+ wwids_file = conf.wwids_file -+ if conf.prkeys_file is not None: -+ prkeys_file = conf.prkeys_file -+ -+ file_list = [] -+ if ( -+ bindings_file is not None and -+ os.path.normpath(bindings_file) != _DEFAULT_BINDINGS_FILE -+ ): -+ file_list.append(('bindings_file', bindings_file, _DEFAULT_BINDINGS_FILE)) -+ if ( -+ wwids_file is not None and -+ os.path.normpath(wwids_file) != _DEFAULT_WWIDS_FILE -+ ): -+ file_list.append(('wwids_file', wwids_file, _DEFAULT_WWIDS_FILE)) -+ if ( -+ prkeys_file is not None and -+ os.path.normpath(prkeys_file) != _DEFAULT_PRKEYS_FILE -+ ): -+ file_list.append(('prkeys_file', prkeys_file, _DEFAULT_PRKEYS_FILE)) -+ if file_list: -+ _report_files(file_list) -+ -+ # socket activation and dm nvme multipathing: system-level, primary only -+ if primary.has_socket_activation: -+ _report_socket_activation() -+ if primary.has_dm_nvme_multipathing: -+ _report_dm_nvme_multipathing() -+ -+ # getuid: per-file, report if any config has it -+ getuid_paths = [conf.pathname for conf in facts.configs if conf.has_getuid] -+ if getuid_paths: -+ _report_getuid(getuid_paths) -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/tests/test_multipath_conf_check_9to10.py b/repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/tests/test_multipath_conf_check_9to10.py -new file mode 100644 -index 00000000..bb03e5dd ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/tests/test_multipath_conf_check_9to10.py -@@ -0,0 +1,258 @@ -+from leapp.libraries.actor import mpath_conf_check -+from leapp.models import MultipathConfFacts9to10, MultipathConfig9to10 -+from leapp.reporting import Report -+ -+ -+def _assert_config_dir_report(report): -+ assert report['title'] == \ -+ 'device-mapper-multipath custom config_dir is deprecated' -+ assert report['severity'] == 'info' -+ -+ -+def _assert_config_dir_conflict_report(report): -+ assert report['title'] == \ -+ 'device-mapper-multipath config_dir conflict' -+ assert report['severity'] == 'high' -+ -+ -+def _assert_files_report(report): -+ assert report['title'] == \ -+ 'device-mapper-multipath configuration files will be moved' -+ assert report['severity'] == 'info' -+ -+ -+def _assert_socket_activation_report(report): -+ assert report['title'] == \ -+ 'device-mapper-multipath socket activation is disabled by default' -+ assert report['severity'] == 'info' -+ -+ -+def _assert_dm_nvme_report(report): -+ assert report['title'] == \ -+ 'device-mapper-multipath NVMe multipathing is no longer supported' -+ assert report['severity'] == 'info' -+ -+ -+def _assert_getuid_report(report, paths_str): -+ assert report['title'] == \ -+ 'device-mapper-multipath configuration contains getuid_callout' -+ assert report['severity'] == 'high' -+ assert paths_str in report['summary'] -+ -+ -+def _build_config(pathname, config_dir=None, bindings_file=None, -+ wwids_file=None, prkeys_file=None, -+ has_socket_activation=False, has_dm_nvme_multipathing=False, -+ has_getuid=False): -+ return MultipathConfig9to10( -+ pathname=pathname, -+ config_dir=config_dir, -+ bindings_file=bindings_file, -+ wwids_file=wwids_file, -+ prkeys_file=prkeys_file, -+ has_socket_activation=has_socket_activation, -+ has_dm_nvme_multipathing=has_dm_nvme_multipathing, -+ has_getuid=has_getuid, -+ ) -+ -+ -+def _build_facts(confs): -+ return MultipathConfFacts9to10(configs=confs) -+ -+ -+def test_no_issues(current_actor_context): -+ config = _build_config('no_issues.conf') -+ facts = _build_facts([config]) -+ current_actor_context.feed(facts) -+ current_actor_context.run() -+ reports = current_actor_context.consume(Report) -+ assert not reports -+ -+ -+def test_all_issues(current_actor_context, monkeypatch): -+ monkeypatch.setattr(mpath_conf_check, '_default_config_dir_has_conf_files', lambda: False) -+ config = _build_config( -+ 'all_issues.conf', -+ config_dir='/etc/multipath/foo.d', -+ bindings_file='/tmp/bindings', -+ has_socket_activation=True, -+ has_dm_nvme_multipathing=True, -+ has_getuid=True) -+ facts = _build_facts([config]) -+ current_actor_context.feed(facts) -+ current_actor_context.run() -+ reports = list(current_actor_context.consume(Report)) -+ assert reports and len(reports) == 5 -+ _assert_config_dir_report(reports[0].report) -+ _assert_files_report(reports[1].report) -+ _assert_socket_activation_report(reports[2].report) -+ _assert_dm_nvme_report(reports[3].report) -+ _assert_getuid_report(reports[4].report, 'all_issues.conf') -+ -+ -+def test_config_dir_default(current_actor_context): -+ config = _build_config('default_dir.conf', -+ config_dir='/etc/multipath/conf.d') -+ facts = _build_facts([config]) -+ current_actor_context.feed(facts) -+ current_actor_context.run() -+ reports = current_actor_context.consume(Report) -+ assert not reports -+ -+ -+def test_config_dir_nondefault(current_actor_context, monkeypatch): -+ monkeypatch.setattr(mpath_conf_check, '_default_config_dir_has_conf_files', lambda: False) -+ config = _build_config('custom_dir.conf', -+ config_dir='/etc/multipath/foo.d') -+ facts = _build_facts([config]) -+ current_actor_context.feed(facts) -+ current_actor_context.run() -+ reports = list(current_actor_context.consume(Report)) -+ assert reports and len(reports) == 1 -+ _assert_config_dir_report(reports[0].report) -+ -+ -+def test_files_nondefault(current_actor_context): -+ config = _build_config('custom_files.conf', -+ bindings_file='/tmp/bindings', -+ wwids_file='/tmp/wwids') -+ facts = _build_facts([config]) -+ current_actor_context.feed(facts) -+ current_actor_context.run() -+ reports = list(current_actor_context.consume(Report)) -+ assert reports and len(reports) == 1 -+ _assert_files_report(reports[0].report) -+ assert '/tmp/bindings' in reports[0].report['summary'] -+ assert '/tmp/wwids' in reports[0].report['summary'] -+ -+ -+def test_files_overridden_by_secondary(current_actor_context): -+ primary = _build_config('primary.conf', -+ bindings_file='/tmp/bindings') -+ secondary = _build_config('secondary.conf', -+ bindings_file='/etc/multipath/bindings') -+ facts = _build_facts([primary, secondary]) -+ current_actor_context.feed(facts) -+ current_actor_context.run() -+ reports = current_actor_context.consume(Report) -+ assert not reports -+ -+ -+def test_files_secondary_overrides_to_nondefault(current_actor_context): -+ primary = _build_config('primary.conf') -+ secondary = _build_config('secondary.conf', -+ bindings_file='/tmp/bindings') -+ facts = _build_facts([primary, secondary]) -+ current_actor_context.feed(facts) -+ current_actor_context.run() -+ reports = list(current_actor_context.consume(Report)) -+ assert reports and len(reports) == 1 -+ _assert_files_report(reports[0].report) -+ -+ -+def test_socket_activation(current_actor_context): -+ config = _build_config('socket.conf', has_socket_activation=True) -+ facts = _build_facts([config]) -+ current_actor_context.feed(facts) -+ current_actor_context.run() -+ reports = list(current_actor_context.consume(Report)) -+ assert reports and len(reports) == 1 -+ _assert_socket_activation_report(reports[0].report) -+ -+ -+def test_no_socket_activation(current_actor_context): -+ config = _build_config('no_socket.conf', has_socket_activation=False) -+ facts = _build_facts([config]) -+ current_actor_context.feed(facts) -+ current_actor_context.run() -+ reports = current_actor_context.consume(Report) -+ assert not reports -+ -+ -+def test_dm_nvme(current_actor_context): -+ config = _build_config('nvme.conf', has_dm_nvme_multipathing=True) -+ facts = _build_facts([config]) -+ current_actor_context.feed(facts) -+ current_actor_context.run() -+ reports = list(current_actor_context.consume(Report)) -+ assert reports and len(reports) == 1 -+ _assert_dm_nvme_report(reports[0].report) -+ -+ -+def test_getuid_inhibitor(current_actor_context): -+ config = _build_config('getuid.conf', has_getuid=True) -+ facts = _build_facts([config]) -+ current_actor_context.feed(facts) -+ current_actor_context.run() -+ reports = list(current_actor_context.consume(Report)) -+ assert reports and len(reports) == 1 -+ _assert_getuid_report(reports[0].report, 'getuid.conf') -+ -+ -+def test_getuid_in_secondary(current_actor_context): -+ primary = _build_config('primary.conf') -+ secondary = _build_config('secondary.conf', has_getuid=True) -+ facts = _build_facts([primary, secondary]) -+ current_actor_context.feed(facts) -+ current_actor_context.run() -+ reports = list(current_actor_context.consume(Report)) -+ assert reports and len(reports) == 1 -+ _assert_getuid_report(reports[0].report, 'secondary.conf') -+ -+ -+def test_multiple_secondaries(current_actor_context): -+ # primary: non-default bindings, no getuid -+ primary = _build_config('primary.conf', -+ bindings_file='/tmp/bindings') -+ # second1: overrides bindings back to default, sets non-default wwids, -+ # has getuid -+ second1 = _build_config('second1.conf', -+ bindings_file='/etc/multipath/bindings', -+ wwids_file='/tmp/wwids', -+ has_getuid=True) -+ # second2: overrides wwids back to default, sets non-default prkeys, -+ # no getuid -+ second2 = _build_config('second2.conf', -+ wwids_file='/etc/multipath/wwids', -+ prkeys_file='/tmp/prkeys') -+ # second3: has getuid only -+ second3 = _build_config('second3.conf', has_getuid=True) -+ facts = _build_facts([primary, second1, second2, second3]) -+ current_actor_context.feed(facts) -+ current_actor_context.run() -+ reports = list(current_actor_context.consume(Report)) -+ # Expect: files report (only prkeys non-default) + getuid inhibitor -+ assert reports and len(reports) == 2 -+ _assert_files_report(reports[0].report) -+ # bindings was overridden to default, wwids was overridden to default, -+ # only prkeys should appear -+ assert '/tmp/prkeys' in reports[0].report['summary'] -+ assert '/tmp/bindings' not in reports[0].report['summary'] -+ assert '/tmp/wwids' not in reports[0].report['summary'] -+ # getuid found in second1 and second3 -+ _assert_getuid_report(reports[1].report, 'second1.conf and second3.conf') -+ -+ -+def test_config_dir_conflict_inhibitor(current_actor_context, monkeypatch): -+ monkeypatch.setattr(mpath_conf_check, '_default_config_dir_has_conf_files', lambda: True) -+ config = _build_config('custom_dir.conf', -+ config_dir='/etc/multipath/foo.d') -+ facts = _build_facts([config]) -+ current_actor_context.feed(facts) -+ current_actor_context.run() -+ reports = list(current_actor_context.consume(Report)) -+ assert reports and len(reports) == 2 -+ _assert_config_dir_report(reports[0].report) -+ _assert_config_dir_conflict_report(reports[1].report) -+ -+ -+def test_config_dir_no_conflict(current_actor_context, monkeypatch): -+ monkeypatch.setattr(mpath_conf_check, '_default_config_dir_has_conf_files', lambda: False) -+ config = _build_config('custom_dir.conf', -+ config_dir='/etc/multipath/foo.d') -+ facts = _build_facts([config]) -+ current_actor_context.feed(facts) -+ current_actor_context.run() -+ reports = list(current_actor_context.consume(Report)) -+ assert reports and len(reports) == 1 -+ _assert_config_dir_report(reports[0].report) --- -2.54.0 - diff --git a/0057-multipath-Add-MultipathUpgradeConfUpdate9to10-mpath_.patch b/0057-multipath-Add-MultipathUpgradeConfUpdate9to10-mpath_.patch deleted file mode 100644 index 574a836..0000000 --- a/0057-multipath-Add-MultipathUpgradeConfUpdate9to10-mpath_.patch +++ /dev/null @@ -1,659 +0,0 @@ -From beffa9b5decef2639af93adf014ca63071900e3e Mon Sep 17 00:00:00 2001 -From: Benjamin Marzinski -Date: Mon, 30 Mar 2026 18:39:57 -0400 -Subject: [PATCH 057/105] multipath: Add MultipathUpgradeConfUpdate9to10 - mpath_upgrade_conf_patcher actor - -Removes deprecated defaults section options (config_dir, bindings_file, -wwids_file, prkeys_file) from multipath configuration files during the -RHEL 9 to RHEL 10 upgrade. Relocates secondary configs to -/etc/multipath/conf.d/ when config_dir is non-default, and creates -entries to move bindings, wwids, and prkeys files to their default -RHEL-10 locations. - -Also make multipath_system_config_patcher create directories if they -don't already exist when copying files, to deal with the case where -config files need to be copied to /etc/multipath/conf.d, but it doesn't -exist. - -Jira: RHEL-151509 - -Co-Authored-By: Claude Opus 4.6 ---- - .../libraries/system_config_patcher.py | 5 + - .../mpath_upgrade_conf_patcher/actor.py | 29 ++ - .../libraries/mpathconfupdate.py | 143 ++++++++++ - .../tests/files/after/all_deprecated.conf | 7 + - .../tests/files/after/has_config_dir.conf | 4 + - .../tests/files/after/has_files.conf | 6 + - .../after/secondary_with_deprecated.conf | 9 + - .../tests/files/before/all_deprecated.conf | 7 + - .../tests/files/before/empty.conf | 1 + - .../tests/files/before/has_config_dir.conf | 4 + - .../tests/files/before/has_files.conf | 6 + - .../tests/files/before/no_defaults.conf | 3 + - .../tests/files/before/no_deprecated.conf | 3 + - .../tests/files/before/secondary_simple.conf | 6 + - .../before/secondary_with_deprecated.conf | 9 + - .../tests/test_mpath_conf_update_9to10.py | 256 ++++++++++++++++++ - 16 files changed, 498 insertions(+) - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/actor.py - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/libraries/mpathconfupdate.py - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/all_deprecated.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/has_config_dir.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/has_files.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/secondary_with_deprecated.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/all_deprecated.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/empty.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/has_config_dir.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/has_files.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/no_defaults.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/no_deprecated.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/secondary_simple.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/secondary_with_deprecated.conf - create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/test_mpath_conf_update_9to10.py - -diff --git a/repos/system_upgrade/common/actors/multipath/system_conf_patcher/libraries/system_config_patcher.py b/repos/system_upgrade/common/actors/multipath/system_conf_patcher/libraries/system_config_patcher.py -index 0d873322..0d1fd070 100644 ---- a/repos/system_upgrade/common/actors/multipath/system_conf_patcher/libraries/system_config_patcher.py -+++ b/repos/system_upgrade/common/actors/multipath/system_conf_patcher/libraries/system_config_patcher.py -@@ -1,3 +1,4 @@ -+import os - import shutil - - from leapp.libraries.stdlib import api -@@ -14,4 +15,8 @@ def patch_system_configs(): - ) - ) - -+ os.makedirs( -+ os.path.dirname(modified_config.target_path), -+ exist_ok=True -+ ) - shutil.copy(modified_config.updated_config_location, modified_config.target_path) -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/actor.py b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/actor.py -new file mode 100644 -index 00000000..f72d2936 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/actor.py -@@ -0,0 +1,29 @@ -+from leapp.actors import Actor -+from leapp.libraries.actor import mpathconfupdate -+from leapp.models import MultipathConfFacts9to10, MultipathConfigUpdatesInfo -+from leapp.tags import IPUWorkflowTag, TargetTransactionChecksPhaseTag -+ -+ -+class MultipathUpgradeConfUpdate9to10(Actor): -+ """ -+ Modifies multipath configuration files for the RHEL-10 upgrade. -+ -+ Removes deprecated options (config_dir, bindings_file, wwids_file, -+ prkeys_file) from multipath configuration files. If config_dir is -+ set to a non-default directory, ensures all secondary configs are -+ moved to /etc/multipath/conf.d/. Creates entries to relocate -+ bindings, wwids, and prkeys files to their default RHEL-10 -+ locations if necessary. -+ """ -+ -+ name = 'multipath_upgrade_conf_update_9to10' -+ consumes = (MultipathConfFacts9to10,) -+ produces = (MultipathConfigUpdatesInfo,) -+ tags = (TargetTransactionChecksPhaseTag, IPUWorkflowTag) -+ -+ def process(self): -+ facts = next(self.consume(MultipathConfFacts9to10), None) -+ if facts is None: -+ self.log.debug('Skipping execution. No MultipathConfFacts9to10 has been produced') -+ return -+ mpathconfupdate.update_configs(facts) -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/libraries/mpathconfupdate.py b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/libraries/mpathconfupdate.py -new file mode 100644 -index 00000000..a2e3bafc ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/libraries/mpathconfupdate.py -@@ -0,0 +1,143 @@ -+import os -+import shutil -+ -+from leapp.libraries.common import multipathutil -+from leapp.libraries.stdlib import api -+from leapp.models import MultipathConfigUpdatesInfo, UpdatedMultipathConfig -+ -+MODIFICATIONS_STORE_PATH = '/var/lib/leapp/proposed_modifications' -+ -+_DEFAULT_CONFIG_DIR = '/etc/multipath/conf.d' -+_DEFAULT_BINDINGS_FILE = '/etc/multipath/bindings' -+_DEFAULT_WWIDS_FILE = '/etc/multipath/wwids' -+_DEFAULT_PRKEYS_FILE = '/etc/multipath/prkeys' -+ -+_deprecated_options = ('config_dir', 'bindings_file', 'wwids_file', 'prkeys_file') -+ -+ -+def _update_config(config): -+ contents = multipathutil.read_config(config.pathname) -+ if contents is None: -+ return None -+ lines = contents.split('\n') -+ -+ section = None -+ in_subsection = False -+ updated_file = False -+ comment_lines = [] -+ for i, line in enumerate(lines): -+ try: -+ data = multipathutil.LineData(line, section, in_subsection) -+ except ValueError: -+ continue -+ if data.type == data.TYPE_SECTION_END: -+ if in_subsection: -+ in_subsection = False -+ elif section is not None: -+ section = None -+ elif data.type == data.TYPE_SECTION_START: -+ if section is None: -+ section = data.section -+ elif not in_subsection: -+ in_subsection = True -+ elif data.type == data.TYPE_OPTION: -+ if section == 'defaults' and data.option in _deprecated_options: -+ comment_lines.append(i) -+ updated_file = True -+ -+ if not updated_file: -+ return None -+ -+ for i in reversed(comment_lines): -+ lines[i] = '#{} # line commented out by leapp'.format(lines[i]) -+ -+ return '\n'.join(lines) -+ -+ -+def _get_file_locations(facts): -+ bindings_file = None -+ wwids_file = None -+ prkeys_file = None -+ for conf in facts.configs: -+ if conf.bindings_file is not None: -+ bindings_file = os.path.normpath(conf.bindings_file) -+ if conf.wwids_file is not None: -+ wwids_file = os.path.normpath(conf.wwids_file) -+ if conf.prkeys_file is not None: -+ prkeys_file = os.path.normpath(conf.prkeys_file) -+ -+ file_updates = [] -+ if bindings_file is not None and bindings_file != _DEFAULT_BINDINGS_FILE: -+ file_updates.append((bindings_file, _DEFAULT_BINDINGS_FILE)) -+ if wwids_file is not None and wwids_file != _DEFAULT_WWIDS_FILE: -+ file_updates.append((wwids_file, _DEFAULT_WWIDS_FILE)) -+ if prkeys_file is not None and prkeys_file != _DEFAULT_PRKEYS_FILE: -+ file_updates.append((prkeys_file, _DEFAULT_PRKEYS_FILE)) -+ return file_updates -+ -+ -+def prepare_destination_for_file(file_path): -+ dirname = os.path.dirname(file_path) -+ os.makedirs(dirname, exist_ok=True) -+ -+ -+def prepare_place_for_config_modifications(workspace_path=MODIFICATIONS_STORE_PATH): -+ if os.path.exists(workspace_path): -+ shutil.rmtree(workspace_path) -+ os.mkdir(workspace_path) -+ -+ -+def update_configs(facts): -+ if not facts.configs: -+ return -+ -+ config_updates = [] -+ prepare_place_for_config_modifications() -+ -+ primary = facts.configs[0] -+ non_default_config_dir = ( -+ primary.config_dir is not None -+ and os.path.normpath(primary.config_dir) != _DEFAULT_CONFIG_DIR -+ ) -+ -+ for idx, config in enumerate(facts.configs): -+ is_secondary = idx > 0 -+ -+ if is_secondary: -+ target_path = os.path.join( -+ _DEFAULT_CONFIG_DIR, os.path.basename(config.pathname) -+ ) -+ else: -+ target_path = config.pathname -+ -+ contents = _update_config(config) -+ -+ if contents is not None: -+ rootless_path = config.pathname.lstrip('/') -+ updated_config_location = os.path.join( -+ MODIFICATIONS_STORE_PATH, rootless_path -+ ) -+ api.current_logger().debug( -+ 'Instead of modifying {}, preparing modified config at {}'.format( -+ config.pathname, updated_config_location -+ ) -+ ) -+ prepare_destination_for_file(updated_config_location) -+ multipathutil.write_config(updated_config_location, contents) -+ config_updates.append(UpdatedMultipathConfig( -+ updated_config_location=updated_config_location, -+ target_path=target_path -+ )) -+ elif is_secondary and non_default_config_dir: -+ config_updates.append(UpdatedMultipathConfig( -+ updated_config_location=config.pathname, -+ target_path=target_path -+ )) -+ -+ for source_path, default_path in _get_file_locations(facts): -+ config_updates.append(UpdatedMultipathConfig( -+ updated_config_location=source_path, -+ target_path=default_path -+ )) -+ -+ api.produce(MultipathConfigUpdatesInfo(updates=config_updates)) -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/all_deprecated.conf b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/all_deprecated.conf -new file mode 100644 -index 00000000..7489ed9e ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/all_deprecated.conf -@@ -0,0 +1,7 @@ -+defaults { -+ polling_interval 5 -+# config_dir "/etc/multipath/custom.d" # line commented out by leapp -+# bindings_file "/tmp/bindings" # line commented out by leapp -+# wwids_file "/tmp/wwids" # line commented out by leapp -+# prkeys_file "/tmp/prkeys" # line commented out by leapp -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/has_config_dir.conf b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/has_config_dir.conf -new file mode 100644 -index 00000000..94c54ded ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/has_config_dir.conf -@@ -0,0 +1,4 @@ -+defaults { -+ polling_interval 5 -+# config_dir "/etc/multipath/custom.d" # line commented out by leapp -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/has_files.conf b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/has_files.conf -new file mode 100644 -index 00000000..fb74a86e ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/has_files.conf -@@ -0,0 +1,6 @@ -+defaults { -+ polling_interval 5 -+# bindings_file "/tmp/bindings" # line commented out by leapp -+# wwids_file "/tmp/wwids" # line commented out by leapp -+# prkeys_file "/tmp/prkeys" # line commented out by leapp -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/secondary_with_deprecated.conf b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/secondary_with_deprecated.conf -new file mode 100644 -index 00000000..68bfcfca ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/secondary_with_deprecated.conf -@@ -0,0 +1,9 @@ -+defaults { -+# bindings_file "/tmp/bindings" # line commented out by leapp -+} -+devices { -+ device { -+ vendor "VENDOR" -+ product "PRODUCT" -+ } -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/all_deprecated.conf b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/all_deprecated.conf -new file mode 100644 -index 00000000..15e39d34 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/all_deprecated.conf -@@ -0,0 +1,7 @@ -+defaults { -+ polling_interval 5 -+ config_dir "/etc/multipath/custom.d" -+ bindings_file "/tmp/bindings" -+ wwids_file "/tmp/wwids" -+ prkeys_file "/tmp/prkeys" -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/empty.conf b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/empty.conf -new file mode 100644 -index 00000000..8b137891 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/empty.conf -@@ -0,0 +1 @@ -+ -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/has_config_dir.conf b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/has_config_dir.conf -new file mode 100644 -index 00000000..7e8295ee ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/has_config_dir.conf -@@ -0,0 +1,4 @@ -+defaults { -+ polling_interval 5 -+ config_dir "/etc/multipath/custom.d" -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/has_files.conf b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/has_files.conf -new file mode 100644 -index 00000000..9404312b ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/has_files.conf -@@ -0,0 +1,6 @@ -+defaults { -+ polling_interval 5 -+ bindings_file "/tmp/bindings" -+ wwids_file "/tmp/wwids" -+ prkeys_file "/tmp/prkeys" -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/no_defaults.conf b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/no_defaults.conf -new file mode 100644 -index 00000000..dfc28f43 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/no_defaults.conf -@@ -0,0 +1,3 @@ -+blacklist { -+ devnode "^sd[a-z]" -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/no_deprecated.conf b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/no_deprecated.conf -new file mode 100644 -index 00000000..8c0e1510 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/no_deprecated.conf -@@ -0,0 +1,3 @@ -+defaults { -+ polling_interval 5 -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/secondary_simple.conf b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/secondary_simple.conf -new file mode 100644 -index 00000000..092e7cdc ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/secondary_simple.conf -@@ -0,0 +1,6 @@ -+devices { -+ device { -+ vendor "VENDOR" -+ product "PRODUCT" -+ } -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/secondary_with_deprecated.conf b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/secondary_with_deprecated.conf -new file mode 100644 -index 00000000..534c6f07 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/secondary_with_deprecated.conf -@@ -0,0 +1,9 @@ -+defaults { -+ bindings_file "/tmp/bindings" -+} -+devices { -+ device { -+ vendor "VENDOR" -+ product "PRODUCT" -+ } -+} -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/test_mpath_conf_update_9to10.py b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/test_mpath_conf_update_9to10.py -new file mode 100644 -index 00000000..ad6668b9 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/test_mpath_conf_update_9to10.py -@@ -0,0 +1,256 @@ -+import os -+ -+import pytest -+ -+from leapp.libraries.actor import mpathconfupdate -+from leapp.libraries.common import multipathutil -+from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked -+from leapp.libraries.stdlib import api -+from leapp.models import MultipathConfFacts9to10, MultipathConfig9to10 -+ -+BEFORE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'files/before') -+AFTER_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'files/after') -+ -+ -+def build_config(pathname, config_dir=None, bindings_file=None, -+ wwids_file=None, prkeys_file=None): -+ return MultipathConfig9to10( -+ pathname=pathname, -+ config_dir=config_dir, -+ bindings_file=bindings_file, -+ wwids_file=wwids_file, -+ prkeys_file=prkeys_file, -+ ) -+ -+ -+def build_facts(confs): -+ return MultipathConfFacts9to10(configs=confs) -+ -+ -+def mock_read_config(path): -+ return multipathutil.read_config_orig(os.path.join(BEFORE_DIR, path)) -+ -+ -+# Configs with no changes needed -+no_deprecated_conf = build_config('no_deprecated.conf') -+empty_conf = build_config('empty.conf') -+no_defaults_conf = build_config('no_defaults.conf') -+ -+# Configs with deprecated options -+has_config_dir_conf = build_config( -+ 'has_config_dir.conf', config_dir='/etc/multipath/custom.d') -+has_files_conf = build_config( -+ 'has_files.conf', bindings_file='/tmp/bindings', -+ wwids_file='/tmp/wwids', prkeys_file='/tmp/prkeys') -+all_deprecated_conf = build_config( -+ 'all_deprecated.conf', config_dir='/etc/multipath/custom.d', -+ bindings_file='/tmp/bindings', wwids_file='/tmp/wwids', -+ prkeys_file='/tmp/prkeys') -+ -+# Secondary configs -+secondary_simple_conf = build_config('secondary_simple.conf') -+secondary_with_deprecated_conf = build_config( -+ 'secondary_with_deprecated.conf', bindings_file='/tmp/bindings') -+ -+ -+@pytest.mark.parametrize( -+ 'config_facts', -+ [ -+ build_facts([no_deprecated_conf]), -+ build_facts([empty_conf]), -+ build_facts([no_defaults_conf]), -+ build_facts([has_config_dir_conf]), -+ build_facts([has_files_conf]), -+ build_facts([all_deprecated_conf]), -+ build_facts([has_config_dir_conf, secondary_simple_conf]), -+ build_facts([has_config_dir_conf, secondary_with_deprecated_conf]), -+ build_facts([no_deprecated_conf, secondary_simple_conf]), -+ build_facts([no_deprecated_conf, secondary_with_deprecated_conf]), -+ ] -+) -+def test_all_facts(monkeypatch, config_facts): -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) -+ -+ produce_mock = produce_mocked() -+ monkeypatch.setattr(api, 'produce', produce_mock) -+ -+ config_writes = {} -+ -+ def write_config_mock(location, contents): -+ config_writes[location] = contents -+ -+ monkeypatch.setattr(multipathutil, 'read_config_orig', multipathutil.read_config, raising=False) -+ monkeypatch.setattr(multipathutil, 'read_config', mock_read_config) -+ monkeypatch.setattr(multipathutil, 'write_config', write_config_mock) -+ monkeypatch.setattr(mpathconfupdate, 'prepare_destination_for_file', lambda file_path: None) -+ monkeypatch.setattr(mpathconfupdate, 'prepare_place_for_config_modifications', lambda: None) -+ -+ mpathconfupdate.update_configs(config_facts) -+ -+ config_updates = {} -+ for config_updates_msg in produce_mock.model_instances: -+ for update in config_updates_msg.updates: -+ config_updates[update.target_path] = update.updated_config_location -+ -+ primary = config_facts.configs[0] -+ non_default_config_dir = ( -+ primary.config_dir is not None -+ and os.path.normpath(primary.config_dir) != '/etc/multipath/conf.d' -+ ) -+ -+ for idx, config in enumerate(config_facts.configs): -+ is_secondary = idx > 0 -+ -+ if is_secondary: -+ target_path = os.path.join( -+ '/etc/multipath/conf.d', os.path.basename(config.pathname) -+ ) -+ else: -+ target_path = config.pathname -+ -+ expected_conf_location = os.path.join(AFTER_DIR, config.pathname) -+ -+ if target_path not in config_updates: -+ # No update for this config - verify no expected after file exists -+ # and it's not a secondary that should have been relocated -+ assert not os.path.exists(expected_conf_location) -+ assert not (is_secondary and non_default_config_dir) -+ continue -+ -+ updated_config_location = config_updates[target_path] -+ -+ if os.path.exists(expected_conf_location): -+ # Config was modified - check contents -+ assert updated_config_location in config_writes -+ actual_contents = config_writes[updated_config_location] -+ -+ updated_config_expected_location = os.path.join( -+ mpathconfupdate.MODIFICATIONS_STORE_PATH, -+ config.pathname.lstrip('/') -+ ) -+ assert updated_config_location == updated_config_expected_location -+ -+ expected_contents = multipathutil.read_config_orig(expected_conf_location) -+ assert actual_contents == expected_contents -+ else: -+ # Unmodified secondary relocated - source is original path -+ assert updated_config_location == config.pathname -+ -+ -+def test_file_relocation(monkeypatch): -+ """Check that non-default file locations produce UpdatedMultipathConfig entries.""" -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) -+ -+ produce_mock = produce_mocked() -+ monkeypatch.setattr(api, 'produce', produce_mock) -+ -+ monkeypatch.setattr(multipathutil, 'read_config_orig', multipathutil.read_config, raising=False) -+ monkeypatch.setattr(multipathutil, 'read_config', mock_read_config) -+ monkeypatch.setattr(multipathutil, 'write_config', lambda loc, contents: None) -+ monkeypatch.setattr(mpathconfupdate, 'prepare_destination_for_file', lambda file_path: None) -+ monkeypatch.setattr(mpathconfupdate, 'prepare_place_for_config_modifications', lambda: None) -+ -+ facts = build_facts([has_files_conf]) -+ mpathconfupdate.update_configs(facts) -+ -+ file_updates = {} -+ for config_updates_msg in produce_mock.model_instances: -+ for update in config_updates_msg.updates: -+ file_updates[update.target_path] = update.updated_config_location -+ -+ assert file_updates['/etc/multipath/bindings'] == '/tmp/bindings' -+ assert file_updates['/etc/multipath/wwids'] == '/tmp/wwids' -+ assert file_updates['/etc/multipath/prkeys'] == '/tmp/prkeys' -+ -+ -+def test_default_file_locations_no_relocation(monkeypatch): -+ """Check that default file locations don't produce relocation entries.""" -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) -+ -+ produce_mock = produce_mocked() -+ monkeypatch.setattr(api, 'produce', produce_mock) -+ -+ conf = build_config( -+ 'has_files.conf', -+ bindings_file='/etc/multipath/bindings', -+ wwids_file='/etc/multipath/wwids', -+ prkeys_file='/etc/multipath/prkeys', -+ ) -+ -+ monkeypatch.setattr(multipathutil, 'read_config_orig', multipathutil.read_config, raising=False) -+ monkeypatch.setattr(multipathutil, 'read_config', mock_read_config) -+ monkeypatch.setattr(multipathutil, 'write_config', lambda loc, contents: None) -+ monkeypatch.setattr(mpathconfupdate, 'prepare_destination_for_file', lambda file_path: None) -+ monkeypatch.setattr(mpathconfupdate, 'prepare_place_for_config_modifications', lambda: None) -+ -+ facts = build_facts([conf]) -+ mpathconfupdate.update_configs(facts) -+ -+ file_targets = set() -+ for config_updates_msg in produce_mock.model_instances: -+ for update in config_updates_msg.updates: -+ file_targets.add(update.target_path) -+ -+ # Config itself is modified (has deprecated options in before file), but no file relocations -+ assert '/etc/multipath/bindings' not in file_targets -+ assert '/etc/multipath/wwids' not in file_targets -+ assert '/etc/multipath/prkeys' not in file_targets -+ -+ -+def test_last_value_wins_for_files(monkeypatch): -+ """Check that the last non-None value wins for file locations.""" -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) -+ -+ produce_mock = produce_mocked() -+ monkeypatch.setattr(api, 'produce', produce_mock) -+ -+ primary = build_config( -+ 'no_deprecated.conf', bindings_file='/first/bindings') -+ secondary = build_config( -+ 'secondary_simple.conf', bindings_file='/second/bindings') -+ -+ monkeypatch.setattr(multipathutil, 'read_config_orig', multipathutil.read_config, raising=False) -+ monkeypatch.setattr(multipathutil, 'read_config', mock_read_config) -+ monkeypatch.setattr(multipathutil, 'write_config', lambda loc, contents: None) -+ monkeypatch.setattr(mpathconfupdate, 'prepare_destination_for_file', lambda file_path: None) -+ monkeypatch.setattr(mpathconfupdate, 'prepare_place_for_config_modifications', lambda: None) -+ -+ facts = build_facts([primary, secondary]) -+ mpathconfupdate.update_configs(facts) -+ -+ file_updates = {} -+ for config_updates_msg in produce_mock.model_instances: -+ for update in config_updates_msg.updates: -+ file_updates[update.target_path] = update.updated_config_location -+ -+ # Last value (/second/bindings) should win -+ assert file_updates['/etc/multipath/bindings'] == '/second/bindings' -+ -+ -+def test_proposed_config_updates_store(monkeypatch): -+ """Check whether configs are being stored in the expected path.""" -+ config = MultipathConfig9to10( -+ pathname='/etc/multipath.conf.d/xy.conf', -+ config_dir='', -+ ) -+ -+ produce_mock = produce_mocked() -+ monkeypatch.setattr(api, 'produce', produce_mock) -+ -+ monkeypatch.setattr(multipathutil, 'write_config', lambda loc, contents: None) -+ monkeypatch.setattr(mpathconfupdate, '_update_config', lambda *args: 'new config content') -+ monkeypatch.setattr(mpathconfupdate, 'prepare_destination_for_file', lambda file_path: None) -+ monkeypatch.setattr(mpathconfupdate, 'prepare_place_for_config_modifications', lambda: None) -+ -+ mpathconfupdate.update_configs(MultipathConfFacts9to10(configs=[config])) -+ -+ expected_updated_config_path = os.path.join( -+ mpathconfupdate.MODIFICATIONS_STORE_PATH, -+ 'etc/multipath.conf.d/xy.conf' -+ ) -+ found = False -+ for config_updates_msg in produce_mock.model_instances: -+ for update in config_updates_msg.updates: -+ if update.updated_config_location == expected_updated_config_path: -+ found = True -+ assert found --- -2.54.0 - diff --git a/0058-multipath-Add-bindings-wwids-prkeys-file-copying-to-.patch b/0058-multipath-Add-bindings-wwids-prkeys-file-copying-to-.patch deleted file mode 100644 index 14e05dd..0000000 --- a/0058-multipath-Add-bindings-wwids-prkeys-file-copying-to-.patch +++ /dev/null @@ -1,567 +0,0 @@ -From 3c45b90ec3a053bae88f6bf829317c36bb914b71 Mon Sep 17 00:00:00 2001 -From: Benjamin Marzinski -Date: Wed, 1 Apr 2026 14:24:42 -0400 -Subject: [PATCH 058/105] multipath: Add bindings/wwids/prkeys file copying to - target userspace - -The request_multipath_conf_in_target_userspace actor was not copying -bindings, wwids, and prkeys files into the target userspace. Add -bindings_file, wwids_file, and prkeys_file fields to MultipathInfo so -the config reader actors can communicate the file locations to the -target_uspace_configs actor. - -For el8toel9, MultipathInfo always gets all three file fields set to -whatever the effective location is (default or configured), so the -files are always copied at their current location. - -For el9toel10, MultipathInfo file fields are only set when the -effective value is at the default location (or unset). Non-default -file locations are not set on MultipathInfo, since the patcher actor -handles relocating them to the default location via -UpdatedMultipathConfig entries. - -Also normalize config_dir paths in the el8toel9 config reader with -os.path.normpath() for consistency with el9toel10. - -Jira: RHEL-151509 - -Tests-Authored-By: Claude Opus 4.6 ---- - .../target_uspace_multipath_configs.py | 7 ++ - .../tests/test_target_uspace_configs.py | 117 ++++++++++++++++++ - .../system_upgrade/common/models/multipath.py | 27 +++- - .../libraries/multipathconfread.py | 38 ++++-- - .../tests/test_multipath_conf_read_8to9.py | 42 ++++++- - .../libraries/multipathconfread.py | 36 +++++- - .../tests/test_multipath_conf_read_9to10.py | 99 +++++++++++++++ - 7 files changed, 354 insertions(+), 12 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/multipath/target_uspace_configs/libraries/target_uspace_multipath_configs.py b/repos/system_upgrade/common/actors/multipath/target_uspace_configs/libraries/target_uspace_multipath_configs.py -index bc685fa2..f294a15e 100644 ---- a/repos/system_upgrade/common/actors/multipath/target_uspace_configs/libraries/target_uspace_multipath_configs.py -+++ b/repos/system_upgrade/common/actors/multipath/target_uspace_configs/libraries/target_uspace_multipath_configs.py -@@ -22,6 +22,13 @@ def request_mpath_confs(multipath_info): - '/etc/multipath.conf': '/etc/multipath.conf' # default config - } - -+ for conf_file in ( -+ multipath_info.bindings_file, -+ multipath_info.wwids_file, -+ multipath_info.prkeys_file -+ ): -+ if conf_file and os.path.exists(conf_file): -+ files_to_put_into_uspace[conf_file] = conf_file - if multipath_info.config_dir and os.path.exists(multipath_info.config_dir): - for filename in os.listdir(multipath_info.config_dir): - config_path = os.path.join(multipath_info.config_dir, filename) -diff --git a/repos/system_upgrade/common/actors/multipath/target_uspace_configs/tests/test_target_uspace_configs.py b/repos/system_upgrade/common/actors/multipath/target_uspace_configs/tests/test_target_uspace_configs.py -index ffb63322..ee199df4 100644 ---- a/repos/system_upgrade/common/actors/multipath/target_uspace_configs/tests/test_target_uspace_configs.py -+++ b/repos/system_upgrade/common/actors/multipath/target_uspace_configs/tests/test_target_uspace_configs.py -@@ -84,3 +84,120 @@ def test_production_conditions(monkeypatch, multipath_info, should_produce): - assert dracut_modules == ['multipath'] - else: - assert not produce_mock.called -+ -+ -+def test_file_locations_copied(monkeypatch): -+ """Test that bindings/wwids/prkeys files are copied to target userspace.""" -+ produce_mock = produce_mocked() -+ monkeypatch.setattr(api, 'produce', produce_mock) -+ -+ multipath_info = MultipathInfo( -+ is_configured=True, -+ config_dir='/etc/multipath/conf.d', -+ bindings_file='/etc/multipath/bindings', -+ wwids_file='/etc/multipath/wwids', -+ prkeys_file='/etc/multipath/prkeys', -+ ) -+ msgs = [multipath_info, MultipathConfigUpdatesInfo(updates=[])] -+ actor_mock = CurrentActorMocked(msgs=msgs) -+ monkeypatch.setattr(api, 'current_actor', actor_mock) -+ -+ existing_files = { -+ '/etc/multipath/conf.d', -+ '/etc/multipath/bindings', -+ '/etc/multipath/wwids', -+ '/etc/multipath/prkeys', -+ } -+ -+ def exists_mock(path): -+ return path in existing_files -+ -+ monkeypatch.setattr(os.path, 'exists', exists_mock) -+ monkeypatch.setattr(os, 'listdir', lambda path: []) -+ -+ actor_lib.process() -+ -+ _target_uspace_tasks = [ -+ msg for msg in produce_mock.model_instances if isinstance(msg, TargetUserSpaceUpgradeTasks) -+ ] -+ assert len(_target_uspace_tasks) == 1 -+ -+ copies = {(copy.src, copy.dst) for copy in _target_uspace_tasks[0].copy_files} -+ assert ('/etc/multipath/bindings', '/etc/multipath/bindings') in copies -+ assert ('/etc/multipath/wwids', '/etc/multipath/wwids') in copies -+ assert ('/etc/multipath/prkeys', '/etc/multipath/prkeys') in copies -+ -+ -+def test_file_locations_not_copied_when_missing(monkeypatch): -+ """Test that non-existent files are not copied.""" -+ produce_mock = produce_mocked() -+ monkeypatch.setattr(api, 'produce', produce_mock) -+ -+ multipath_info = MultipathInfo( -+ is_configured=True, -+ config_dir='/etc/multipath/conf.d', -+ bindings_file='/etc/multipath/bindings', -+ ) -+ msgs = [multipath_info, MultipathConfigUpdatesInfo(updates=[])] -+ actor_mock = CurrentActorMocked(msgs=msgs) -+ monkeypatch.setattr(api, 'current_actor', actor_mock) -+ -+ def exists_mock(path): -+ return path == '/etc/multipath/conf.d' -+ -+ monkeypatch.setattr(os.path, 'exists', exists_mock) -+ monkeypatch.setattr(os, 'listdir', lambda path: []) -+ -+ actor_lib.process() -+ -+ _target_uspace_tasks = [ -+ msg for msg in produce_mock.model_instances if isinstance(msg, TargetUserSpaceUpgradeTasks) -+ ] -+ assert len(_target_uspace_tasks) == 1 -+ -+ copies = {copy.src for copy in _target_uspace_tasks[0].copy_files} -+ # bindings_file doesn't exist on disk, so should not be copied -+ assert '/etc/multipath/bindings' not in copies -+ -+ -+def test_file_locations_overridden_by_updates(monkeypatch): -+ """Test that UpdatedMultipathConfig entries override file copy sources.""" -+ produce_mock = produce_mocked() -+ monkeypatch.setattr(api, 'produce', produce_mock) -+ -+ multipath_info = MultipathInfo( -+ is_configured=True, -+ config_dir='/etc/multipath/conf.d', -+ bindings_file='/etc/multipath/bindings', -+ ) -+ # The patcher says to move /tmp/bindings -> /etc/multipath/bindings -+ update = UpdatedMultipathConfig( -+ updated_config_location='/tmp/bindings', -+ target_path='/etc/multipath/bindings' -+ ) -+ msgs = [multipath_info, MultipathConfigUpdatesInfo(updates=[update])] -+ actor_mock = CurrentActorMocked(msgs=msgs) -+ monkeypatch.setattr(api, 'current_actor', actor_mock) -+ -+ existing_files = { -+ '/etc/multipath/conf.d', -+ '/etc/multipath/bindings', -+ } -+ -+ def exists_mock(path): -+ return path in existing_files -+ -+ monkeypatch.setattr(os.path, 'exists', exists_mock) -+ monkeypatch.setattr(os, 'listdir', lambda path: []) -+ -+ actor_lib.process() -+ -+ _target_uspace_tasks = [ -+ msg for msg in produce_mock.model_instances if isinstance(msg, TargetUserSpaceUpgradeTasks) -+ ] -+ assert len(_target_uspace_tasks) == 1 -+ -+ copies = {(copy.src, copy.dst) for copy in _target_uspace_tasks[0].copy_files} -+ # Original bindings file should be removed and replaced by the update source -+ assert ('/etc/multipath/bindings', '/etc/multipath/bindings') not in copies -+ assert ('/tmp/bindings', '/etc/multipath/bindings') in copies -diff --git a/repos/system_upgrade/common/models/multipath.py b/repos/system_upgrade/common/models/multipath.py -index 9ccaa290..aaa11801 100644 ---- a/repos/system_upgrade/common/models/multipath.py -+++ b/repos/system_upgrade/common/models/multipath.py -@@ -14,7 +14,32 @@ class MultipathInfo(Model): - """ - - config_dir = fields.Nullable(fields.String()) -- """ Value of config_dir in the defaults section. None if not set. """ -+ """ -+ The location of config_dir, if it should be copied to the same location -+ in the target userspace. None if it should be copied to a different -+ location (handled by upgrade-specific models). -+ """ -+ -+ bindings_file = fields.Nullable(fields.String()) -+ """ -+ The location of bindings_file, if it should be copied to the same location -+ in the target userspace. None if it should be copied to a different -+ location (handled by upgrade-specific models). -+ """ -+ -+ wwids_file = fields.Nullable(fields.String()) -+ """ -+ The location of wwids_file, if it should be copied to the same location -+ in the target userspace. None if it should be copied to a different -+ location (handled by upgrade-specific models). -+ """ -+ -+ prkeys_file = fields.Nullable(fields.String()) -+ """ -+ The location of prkeys_file, if it should be copied to the same location -+ in the target userspace. None if it should be copied to a different -+ location (handled by upgrade-specific models). -+ """ - - - class UpdatedMultipathConfig(Model): -diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfread/libraries/multipathconfread.py b/repos/system_upgrade/el8toel9/actors/multipathconfread/libraries/multipathconfread.py -index f2f39293..2c81ed52 100644 ---- a/repos/system_upgrade/el8toel9/actors/multipathconfread/libraries/multipathconfread.py -+++ b/repos/system_upgrade/el8toel9/actors/multipathconfread/libraries/multipathconfread.py -@@ -9,8 +9,12 @@ from leapp.models import DistributionSignedRPM, MultipathConfFacts8to9, Multipat - _regexes = ('vendor', 'product', 'revision', 'product_blacklist', 'devnode', - 'wwid', 'property', 'protocol') - -+_DEFAULT_BINDINGS_FILE = '/etc/multipath/bindings' -+_DEFAULT_WWIDS_FILE = '/etc/multipath/wwids' -+_DEFAULT_PRKEYS_FILE = '/etc/multipath/prkeys' - --def _parse_config(path): -+ -+def _parse_config(path, file_locs): - contents = multipathutil.read_config(path) - if contents is None: - return None -@@ -44,20 +48,22 @@ def _parse_config(path): - elif data.option == 'allow_usb_devices': - conf.allow_usb_exists = True - elif data.option == 'config_dir': -- conf.config_dir = data.value -+ conf.config_dir = os.path.normpath(data.value) -+ elif data.option in ('bindings_file', 'wwids_file', 'prkeys_file'): -+ file_locs[data.option] = os.path.normpath(data.value) - if data.option in _regexes and data.value == '*': - conf.invalid_regexes_exist = True - return conf - - --def _parse_config_dir(config_dir): -+def _parse_config_dir(config_dir, file_locs): - res = [] - try: - for config_file in sorted(os.listdir(config_dir)): - path = os.path.join(config_dir, config_file) - if not path.endswith('.conf'): - continue -- conf = _parse_config(path) -+ conf = _parse_config(path, file_locs) - if conf: - res.append(conf) - except OSError as e: -@@ -82,11 +88,22 @@ def is_processable(): - return res - - -+def setup_default_file_locations(): -+ file_locs = { -+ "bindings_file": _DEFAULT_BINDINGS_FILE, -+ "wwids_file": _DEFAULT_WWIDS_FILE, -+ "prkeys_file": _DEFAULT_PRKEYS_FILE, -+ } -+ return file_locs -+ -+ - def scan_and_emit_multipath_info(default_config_path='/etc/multipath.conf'): - if not is_processable(): - return - -- primary_config = _parse_config(default_config_path) -+ file_locs = setup_default_file_locations() -+ -+ primary_config = _parse_config(default_config_path, file_locs) - if not primary_config: - api.current_logger().debug( - 'Primary multipath config /etc/multipath.conf is not present - multipath ' -@@ -100,10 +117,17 @@ def scan_and_emit_multipath_info(default_config_path='/etc/multipath.conf'): - is_configured=True, - config_dir=primary_config.config_dir or '/etc/multipath/conf.d' - ) -+ -+ secondary_configs = _parse_config_dir( -+ multipath_info.config_dir, -+ file_locs -+ ) -+ -+ multipath_info.bindings_file = file_locs['bindings_file'] -+ multipath_info.wwids_file = file_locs['wwids_file'] -+ multipath_info.prkeys_file = file_locs['prkeys_file'] - api.produce(multipath_info) - -- secondary_configs = _parse_config_dir(multipath_info.config_dir) - all_configs = [primary_config] + secondary_configs -- - config_facts_for_8to9 = MultipathConfFacts8to9(configs=all_configs) - api.produce(config_facts_for_8to9) -diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/test_multipath_conf_read_8to9.py b/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/test_multipath_conf_read_8to9.py -index 2da74927..cd8036ec 100644 ---- a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/test_multipath_conf_read_8to9.py -+++ b/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/test_multipath_conf_read_8to9.py -@@ -78,9 +78,9 @@ two_defaults_conf = build_config( - os.path.join(TEST_DIR, 'two_defaults.conf'), None, True, False, False) - - --def mock_parse_config(path): -+def mock_parse_config(path, file_locs): - """Convert config_dir into full pathname""" -- conf = multipathconfread._parse_config_orig(path) -+ conf = multipathconfread._parse_config_orig(path, file_locs) - if not conf: - return None - if conf.config_dir: -@@ -99,7 +99,9 @@ def test_parse_config(): - 'two_defaults.conf': two_defaults_conf, - 'empty.conf': empty_conf} - for config_name, expected_data in test_map.items(): -- config = multipathconfread._parse_config(os.path.join(TEST_DIR, config_name)) -+ file_locs = multipathconfread.setup_default_file_locations() -+ config = multipathconfread._parse_config( -+ os.path.join(TEST_DIR, config_name), file_locs) - assert config - assert_config(config, expected_data) - -@@ -133,6 +135,9 @@ def test_get_facts_missing_dir(monkeypatch, primary_config, expected_configs): - assert len(general_info) == 1 - assert general_info[0].is_configured - # general_info[0].config_dir is with the MultipathConfFacts8to9 messages below -+ assert general_info[0].bindings_file == '/etc/multipath/bindings' -+ assert general_info[0].wwids_file == '/etc/multipath/wwids' -+ assert general_info[0].prkeys_file == '/etc/multipath/prkeys' - - msgs = [msg for msg in produce_mock.model_instances if isinstance(msg, MultipathConfFacts8to9)] - assert len(msgs) == 1 -@@ -142,3 +147,34 @@ def test_get_facts_missing_dir(monkeypatch, primary_config, expected_configs): - - for actual_config, expected_config in zip(actual_configs, expected_configs): - assert_config(actual_config, expected_config) -+ -+ -+def test_file_locations_nondefault(monkeypatch): -+ """Check that non-default file locations are tracked correctly.""" -+ monkeypatch.setattr(multipathconfread, 'is_processable', lambda: True) -+ -+ produce_mock = produce_mocked() -+ monkeypatch.setattr(api, 'produce', produce_mock) -+ -+ actor_mock = CurrentActorMocked(src_ver='8.10', dst_ver='9.6') -+ monkeypatch.setattr(api, 'current_actor', actor_mock) -+ -+ # Create a test config with non-default file locations -+ file_locs = multipathconfread.setup_default_file_locations() -+ file_locs['bindings_file'] = '/tmp/bindings' -+ file_locs['wwids_file'] = '/tmp/wwids' -+ -+ def mock_parse(path, fl): -+ fl.update(file_locs) -+ return MultipathConfig8to9(pathname=path) -+ -+ monkeypatch.setattr(multipathconfread, '_parse_config', mock_parse) -+ monkeypatch.setattr(multipathconfread, '_parse_config_dir', lambda d, fl: []) -+ -+ multipathconfread.scan_and_emit_multipath_info('/etc/multipath.conf') -+ -+ general_info = [msg for msg in produce_mock.model_instances if isinstance(msg, MultipathInfo)] -+ assert len(general_info) == 1 -+ assert general_info[0].bindings_file == '/tmp/bindings' -+ assert general_info[0].wwids_file == '/tmp/wwids' -+ assert general_info[0].prkeys_file == '/etc/multipath/prkeys' -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/libraries/multipathconfread.py b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/libraries/multipathconfread.py -index 50c87d86..7d85bdbc 100644 ---- a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/libraries/multipathconfread.py -+++ b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/libraries/multipathconfread.py -@@ -6,6 +6,10 @@ from leapp.libraries.common.rpms import has_package - from leapp.libraries.stdlib import api - from leapp.models import DistributionSignedRPM, MultipathConfFacts9to10, MultipathConfig9to10, MultipathInfo - -+_DEFAULT_BINDINGS_FILE = '/etc/multipath/bindings' -+_DEFAULT_WWIDS_FILE = '/etc/multipath/wwids' -+_DEFAULT_PRKEYS_FILE = '/etc/multipath/prkeys' -+ - - def _parse_config(path): - contents = multipathutil.read_config(path) -@@ -98,6 +102,35 @@ def is_processable(): - return res - - -+def _add_file_locations(configs, mpath_info): -+ bindings_file = None -+ wwids_file = None -+ prkeys_file = None -+ for conf in configs: -+ if conf.bindings_file is not None: -+ bindings_file = conf.bindings_file -+ if conf.wwids_file is not None: -+ wwids_file = conf.wwids_file -+ if conf.prkeys_file is not None: -+ prkeys_file = conf.prkeys_file -+ -+ if ( -+ bindings_file is None or -+ os.path.normpath(bindings_file) == _DEFAULT_BINDINGS_FILE -+ ): -+ mpath_info.bindings_file = _DEFAULT_BINDINGS_FILE -+ if ( -+ wwids_file is None or -+ os.path.normpath(wwids_file) == _DEFAULT_WWIDS_FILE -+ ): -+ mpath_info.wwids_file = _DEFAULT_WWIDS_FILE -+ if ( -+ prkeys_file is None or -+ os.path.normpath(prkeys_file) == _DEFAULT_PRKEYS_FILE -+ ): -+ mpath_info.prkeys_file = _DEFAULT_PRKEYS_FILE -+ -+ - def scan_and_emit_multipath_info(default_config_path='/etc/multipath.conf'): - if not is_processable(): - return -@@ -124,7 +157,6 @@ def scan_and_emit_multipath_info(default_config_path='/etc/multipath.conf'): - os.path.normpath(primary_config.config_dir) == '/etc/multipath/conf.d' - ): - multipath_info.config_dir = '/etc/multipath/conf.d' -- api.produce(multipath_info) - - primary_config.has_socket_activation = _check_socket_activation() - primary_config.has_dm_nvme_multipathing = _check_dm_nvme_multipathing() -@@ -133,6 +165,8 @@ def scan_and_emit_multipath_info(default_config_path='/etc/multipath.conf'): - primary_config.config_dir or '/etc/multipath/conf.d' - ) - all_configs = [primary_config] + secondary_configs -+ _add_file_locations(all_configs, multipath_info) -+ api.produce(multipath_info) - - config_facts_for_9to10 = MultipathConfFacts9to10(configs=all_configs) - api.produce(config_facts_for_9to10) -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/test_multipath_conf_read_9to10.py b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/test_multipath_conf_read_9to10.py -index cb90fcc3..f8f039a0 100644 ---- a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/test_multipath_conf_read_9to10.py -+++ b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/tests/test_multipath_conf_read_9to10.py -@@ -162,6 +162,9 @@ def test_get_primary_facts_default_config_dir(monkeypatch, primary_config, expec - assert len(general_info) == 1 - assert general_info[0].is_configured - assert general_info[0].config_dir == '/etc/multipath/conf.d' -+ assert general_info[0].bindings_file == '/etc/multipath/bindings' -+ assert general_info[0].wwids_file == '/etc/multipath/wwids' -+ assert general_info[0].prkeys_file == '/etc/multipath/prkeys' - - msgs = [msg for msg in produce_mock.model_instances if isinstance(msg, MultipathConfFacts9to10)] - assert len(msgs) == 1 -@@ -214,6 +217,102 @@ def test_get_facts_with_config_dir(monkeypatch, primary_config, expected_configs - assert_config(actual_config, expected_config) - - -+def test_file_locations_default(monkeypatch): -+ """Default or unset file locations should be set on MultipathInfo.""" -+ monkeypatch.setattr(multipathconfread, 'is_processable', lambda: True) -+ monkeypatch.setattr(multipathconfread, '_check_socket_activation', lambda: False) -+ monkeypatch.setattr(multipathconfread, '_check_dm_nvme_multipathing', lambda: False) -+ monkeypatch.setattr(multipathconfread, '_parse_config_dir', mock_parse_config_dir) -+ -+ produce_mock = produce_mocked() -+ monkeypatch.setattr(api, 'produce', produce_mock) -+ -+ actor_mock = CurrentActorMocked(src_ver='9.6', dst_ver='10.0') -+ monkeypatch.setattr(api, 'current_actor', actor_mock) -+ -+ config_to_use = os.path.join(TEST_DIR, 'all_options.conf') -+ multipathconfread.scan_and_emit_multipath_info(config_to_use) -+ -+ general_info = [msg for msg in produce_mock.model_instances if isinstance(msg, MultipathInfo)] -+ assert len(general_info) == 1 -+ assert general_info[0].bindings_file == '/etc/multipath/bindings' -+ assert general_info[0].wwids_file == '/etc/multipath/wwids' -+ assert general_info[0].prkeys_file == '/etc/multipath/prkeys' -+ -+ -+def test_file_locations_nondefault(monkeypatch): -+ """Non-default file locations should NOT be set on MultipathInfo.""" -+ monkeypatch.setattr(multipathconfread, 'is_processable', lambda: True) -+ monkeypatch.setattr(multipathconfread, '_check_socket_activation', lambda: False) -+ monkeypatch.setattr(multipathconfread, '_check_dm_nvme_multipathing', lambda: False) -+ monkeypatch.setattr(multipathconfread, '_parse_config_dir', mock_parse_config_dir) -+ -+ produce_mock = produce_mocked() -+ monkeypatch.setattr(api, 'produce', produce_mock) -+ -+ actor_mock = CurrentActorMocked(src_ver='9.6', dst_ver='10.0') -+ monkeypatch.setattr(api, 'current_actor', actor_mock) -+ -+ # Use a config that sets files to non-default paths -+ def mock_parse(path): -+ return MultipathConfig9to10( -+ pathname=path, -+ bindings_file='/tmp/bindings', -+ wwids_file='/tmp/wwids', -+ prkeys_file='/tmp/prkeys', -+ ) -+ -+ monkeypatch.setattr(multipathconfread, '_parse_config', mock_parse) -+ -+ multipathconfread.scan_and_emit_multipath_info('/etc/multipath.conf') -+ -+ general_info = [msg for msg in produce_mock.model_instances if isinstance(msg, MultipathInfo)] -+ assert len(general_info) == 1 -+ # Non-default file locations should not be set on MultipathInfo -+ assert general_info[0].bindings_file is None -+ assert general_info[0].wwids_file is None -+ assert general_info[0].prkeys_file is None -+ -+ -+def test_file_locations_secondary_overrides(monkeypatch): -+ """Last non-None value wins for file locations across configs.""" -+ monkeypatch.setattr(multipathconfread, 'is_processable', lambda: True) -+ monkeypatch.setattr(multipathconfread, '_check_socket_activation', lambda: False) -+ monkeypatch.setattr(multipathconfread, '_check_dm_nvme_multipathing', lambda: False) -+ -+ produce_mock = produce_mocked() -+ monkeypatch.setattr(api, 'produce', produce_mock) -+ -+ actor_mock = CurrentActorMocked(src_ver='9.6', dst_ver='10.0') -+ monkeypatch.setattr(api, 'current_actor', actor_mock) -+ -+ primary = MultipathConfig9to10( -+ pathname='/etc/multipath.conf', -+ bindings_file='/tmp/bindings', -+ ) -+ -+ secondary = MultipathConfig9to10( -+ pathname='/etc/multipath/conf.d/secondary.conf', -+ bindings_file='/etc/multipath/bindings', -+ ) -+ -+ def mock_parse(path): -+ return primary -+ -+ def mock_parse_dir(config_dir): -+ return [secondary] -+ -+ monkeypatch.setattr(multipathconfread, '_parse_config', mock_parse) -+ monkeypatch.setattr(multipathconfread, '_parse_config_dir', mock_parse_dir) -+ -+ multipathconfread.scan_and_emit_multipath_info('/etc/multipath.conf') -+ -+ general_info = [msg for msg in produce_mock.model_instances if isinstance(msg, MultipathInfo)] -+ assert len(general_info) == 1 -+ # Secondary overrides primary to default, so it should be set -+ assert general_info[0].bindings_file == '/etc/multipath/bindings' -+ -+ - def test_check_socket_activation(monkeypatch): - monkeypatch.setattr(os.path, 'exists', lambda path: True) - assert multipathconfread._check_socket_activation() is True --- -2.54.0 - diff --git a/0059-multipath-Add-mpathfiles-library-and-use-it-in-el9to.patch b/0059-multipath-Add-mpathfiles-library-and-use-it-in-el9to.patch deleted file mode 100644 index 7104ec2..0000000 --- a/0059-multipath-Add-mpathfiles-library-and-use-it-in-el9to.patch +++ /dev/null @@ -1,228 +0,0 @@ -From 6fc76ee600c896b92f06cb65d9f1bbc774e4c9f8 Mon Sep 17 00:00:00 2001 -From: Benjamin Marzinski -Date: Wed, 15 Apr 2026 02:30:37 -0400 -Subject: [PATCH 059/105] multipath: Add mpathfiles library and use it in - el9toel10 actors - -All of the el9toel10 actors need to read through all the configs to -determine the value of bindings_file, wwids_file, and prkeys_file for -the overall configuration. Split this code out into a library function -and call it from all of the actors. Also move the code to use these -files into a loop, since the same action needs to be done on all three -files. ---- - .../libraries/multipathconfread.py | 47 ++++++++----------- - .../libraries/mpath_conf_check.py | 41 ++++++---------- - .../libraries/mpathconfupdate.py | 31 ++++++------ - .../el9toel10/libraries/mpathfiles.py | 22 +++++++++ - 4 files changed, 71 insertions(+), 70 deletions(-) - create mode 100644 repos/system_upgrade/el9toel10/libraries/mpathfiles.py - -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/libraries/multipathconfread.py b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/libraries/multipathconfread.py -index 7d85bdbc..0f070c1a 100644 ---- a/repos/system_upgrade/el9toel10/actors/multipath/config_reader/libraries/multipathconfread.py -+++ b/repos/system_upgrade/el9toel10/actors/multipath/config_reader/libraries/multipathconfread.py -@@ -1,7 +1,7 @@ - import errno - import os - --from leapp.libraries.common import multipathutil -+from leapp.libraries.common import mpathfiles, multipathutil - from leapp.libraries.common.rpms import has_package - from leapp.libraries.stdlib import api - from leapp.models import DistributionSignedRPM, MultipathConfFacts9to10, MultipathConfig9to10, MultipathInfo -@@ -103,32 +103,25 @@ def is_processable(): - - - def _add_file_locations(configs, mpath_info): -- bindings_file = None -- wwids_file = None -- prkeys_file = None -- for conf in configs: -- if conf.bindings_file is not None: -- bindings_file = conf.bindings_file -- if conf.wwids_file is not None: -- wwids_file = conf.wwids_file -- if conf.prkeys_file is not None: -- prkeys_file = conf.prkeys_file -- -- if ( -- bindings_file is None or -- os.path.normpath(bindings_file) == _DEFAULT_BINDINGS_FILE -- ): -- mpath_info.bindings_file = _DEFAULT_BINDINGS_FILE -- if ( -- wwids_file is None or -- os.path.normpath(wwids_file) == _DEFAULT_WWIDS_FILE -- ): -- mpath_info.wwids_file = _DEFAULT_WWIDS_FILE -- if ( -- prkeys_file is None or -- os.path.normpath(prkeys_file) == _DEFAULT_PRKEYS_FILE -- ): -- mpath_info.prkeys_file = _DEFAULT_PRKEYS_FILE -+ bindings_file, wwids_file, prkeys_file = mpathfiles.mpath_file_locations(configs) -+ -+ attrs_to_populate = [ -+ ('bindings_file', bindings_file, _DEFAULT_BINDINGS_FILE), -+ ('wwids_file', wwids_file, _DEFAULT_WWIDS_FILE), -+ ('prkeys_file', prkeys_file, _DEFAULT_PRKEYS_FILE), -+ ] -+ -+ # Set the attribute value only if it is not configured or equal to default. -+ # Setting mpath_info.X = Y would cause the config at Y to be copied into -+ # target_uspace/Y. However, we want to place Y at the default location due -+ # to 9>10 deprecations and there is no easy way to remove target_uspace/Y -+ # once created. -+ for mpath_info_attr, configured_value, default_value in attrs_to_populate: -+ if ( -+ configured_value is None or -+ os.path.normpath(configured_value) == default_value -+ ): -+ setattr(mpath_info, mpath_info_attr, default_value) - - - def scan_and_emit_multipath_info(default_config_path='/etc/multipath.conf'): -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/libraries/mpath_conf_check.py b/repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/libraries/mpath_conf_check.py -index 49aefc5a..8c4dc531 100644 ---- a/repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/libraries/mpath_conf_check.py -+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/libraries/mpath_conf_check.py -@@ -1,6 +1,7 @@ - import os - - from leapp import reporting -+from leapp.libraries.common import mpathfiles - from leapp.reporting import create_report - - _DEFAULT_CONFIG_DIR = '/etc/multipath/conf.d' -@@ -145,34 +146,22 @@ def check_configs(facts): - if _default_config_dir_has_conf_files(): - _report_config_dir_conflict(config_dir) - -- # bindings_file, wwids_file, prkeys_file: last non-None value wins -- bindings_file = None -- wwids_file = None -- prkeys_file = None -- for conf in facts.configs: -- if conf.bindings_file is not None: -- bindings_file = conf.bindings_file -- if conf.wwids_file is not None: -- wwids_file = conf.wwids_file -- if conf.prkeys_file is not None: -- prkeys_file = conf.prkeys_file -+ bindings_file, wwids_file, prkeys_file = mpathfiles.mpath_file_locations(facts.configs) - - file_list = [] -- if ( -- bindings_file is not None and -- os.path.normpath(bindings_file) != _DEFAULT_BINDINGS_FILE -- ): -- file_list.append(('bindings_file', bindings_file, _DEFAULT_BINDINGS_FILE)) -- if ( -- wwids_file is not None and -- os.path.normpath(wwids_file) != _DEFAULT_WWIDS_FILE -- ): -- file_list.append(('wwids_file', wwids_file, _DEFAULT_WWIDS_FILE)) -- if ( -- prkeys_file is not None and -- os.path.normpath(prkeys_file) != _DEFAULT_PRKEYS_FILE -- ): -- file_list.append(('prkeys_file', prkeys_file, _DEFAULT_PRKEYS_FILE)) -+ files_to_report = [ -+ ('bindings_file', bindings_file, _DEFAULT_BINDINGS_FILE), -+ ('wwids_file', wwids_file, _DEFAULT_WWIDS_FILE), -+ ('prkeys_file', prkeys_file, _DEFAULT_PRKEYS_FILE), -+ ] -+ -+ for file_name, configured_path, default_path in files_to_report: -+ if ( -+ configured_path is not None and -+ os.path.normpath(configured_path) != default_path -+ ): -+ file_list.append((file_name, configured_path, default_path)) -+ - if file_list: - _report_files(file_list) - -diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/libraries/mpathconfupdate.py b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/libraries/mpathconfupdate.py -index a2e3bafc..d5618018 100644 ---- a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/libraries/mpathconfupdate.py -+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/libraries/mpathconfupdate.py -@@ -1,7 +1,7 @@ - import os - import shutil - --from leapp.libraries.common import multipathutil -+from leapp.libraries.common import mpathfiles, multipathutil - from leapp.libraries.stdlib import api - from leapp.models import MultipathConfigUpdatesInfo, UpdatedMultipathConfig - -@@ -55,24 +55,21 @@ def _update_config(config): - - - def _get_file_locations(facts): -- bindings_file = None -- wwids_file = None -- prkeys_file = None -- for conf in facts.configs: -- if conf.bindings_file is not None: -- bindings_file = os.path.normpath(conf.bindings_file) -- if conf.wwids_file is not None: -- wwids_file = os.path.normpath(conf.wwids_file) -- if conf.prkeys_file is not None: -- prkeys_file = os.path.normpath(conf.prkeys_file) -+ bindings_file, wwids_file, prkeys_file = mpathfiles.mpath_file_locations(facts.configs) - - file_updates = [] -- if bindings_file is not None and bindings_file != _DEFAULT_BINDINGS_FILE: -- file_updates.append((bindings_file, _DEFAULT_BINDINGS_FILE)) -- if wwids_file is not None and wwids_file != _DEFAULT_WWIDS_FILE: -- file_updates.append((wwids_file, _DEFAULT_WWIDS_FILE)) -- if prkeys_file is not None and prkeys_file != _DEFAULT_PRKEYS_FILE: -- file_updates.append((prkeys_file, _DEFAULT_PRKEYS_FILE)) -+ files_to_move = [ -+ (bindings_file, _DEFAULT_BINDINGS_FILE), -+ (wwids_file, _DEFAULT_WWIDS_FILE), -+ (prkeys_file, _DEFAULT_PRKEYS_FILE), -+ ] -+ -+ for configured_path, default_path in files_to_move: -+ if configured_path is not None: -+ configured_path = os.path.normpath(configured_path) -+ if configured_path != default_path: -+ file_updates.append((configured_path, default_path)) -+ - return file_updates - - -diff --git a/repos/system_upgrade/el9toel10/libraries/mpathfiles.py b/repos/system_upgrade/el9toel10/libraries/mpathfiles.py -new file mode 100644 -index 00000000..ada276a3 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/libraries/mpathfiles.py -@@ -0,0 +1,22 @@ -+def mpath_file_locations(configs): -+ """ -+ Returns the configured location of the bindings_file, wwids_file, and -+ prkeys_file multipath files, handling cases where the location is set -+ multiple times (the last set value is used) or not set at all (None is -+ used). -+ -+ :param configs: The ordered list of all multipath config files data -+ :type configs: List[MultipathConfig9to10] -+ :return: The locations of bindings_file, wwids_file, and prkeys_file -+ :rtype: Tuple[Optional[str], Optional[str], Optional[str]] -+ -+ """ -+ -+ bindings_file = None -+ wwids_file = None -+ prkeys_file = None -+ for conf in configs: -+ bindings_file = conf.bindings_file or bindings_file -+ wwids_file = conf.wwids_file or wwids_file -+ prkeys_file = conf.prkeys_file or prkeys_file -+ return (bindings_file, wwids_file, prkeys_file) --- -2.54.0 - diff --git a/0060-model-upgrade_cmdline-add-to_remove-field.patch b/0060-model-upgrade_cmdline-add-to_remove-field.patch deleted file mode 100644 index c843d65..0000000 --- a/0060-model-upgrade_cmdline-add-to_remove-field.patch +++ /dev/null @@ -1,96 +0,0 @@ -From ecb06c6fd4ad365ecfe0808a616cd3b3b9cf18d0 Mon Sep 17 00:00:00 2001 -From: Michal Hecko -Date: Wed, 29 Apr 2026 21:05:52 +0200 -Subject: [PATCH 060/105] model(upgrade_cmdline): add to_remove field - -Extend the UpgradeKernelCmdlineArgTasks model to have a `to_remove` -field that list kernel cmdline arguments that should be removed from the -upgrade boot entry. Args listed in the field have precedence over -arguments listed in to_add, i.e., if 'X' is in both `to_remove` and -`to_add`, it is guaranteed to be removed. - -Jira-ref: RHEL-145136 ---- - .../libraries/addupgradebootentry.py | 4 ++- - .../tests/unit_test_addupgradebootentry.py | 26 ++++++++++++++++++- - .../common/models/kernelcmdlineargs.py | 4 +++ - 3 files changed, 32 insertions(+), 2 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py b/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py -index 5b635a83..8fb2e404 100644 ---- a/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py -+++ b/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py -@@ -50,7 +50,9 @@ def collect_undesired_args(livemode_enabled): - args = dict(zip(('ro', 'rhgb', 'quiet'), itertools.repeat(None))) - args['rd.lvm.lv'] = _get_rdlvm_arg_values() - -- return set(args.items()) -+ undesired = set(args.items()) -+ undesired |= collect_set_of_kernel_args_from_msgs(UpgradeKernelCmdlineArgTasks, 'to_remove') -+ return undesired - - - def format_grubby_args_from_args_set(args_dict): -diff --git a/repos/system_upgrade/common/actors/addupgradebootentry/tests/unit_test_addupgradebootentry.py b/repos/system_upgrade/common/actors/addupgradebootentry/tests/unit_test_addupgradebootentry.py -index 79c05a5d..09632c0c 100644 ---- a/repos/system_upgrade/common/actors/addupgradebootentry/tests/unit_test_addupgradebootentry.py -+++ b/repos/system_upgrade/common/actors/addupgradebootentry/tests/unit_test_addupgradebootentry.py -@@ -18,7 +18,8 @@ from leapp.models import ( - LateTargetKernelCmdlineArgTasks, - LiveModeArtifacts, - LiveModeConfig, -- TargetKernelCmdlineArgTasks -+ TargetKernelCmdlineArgTasks, -+ UpgradeKernelCmdlineArgTasks - ) - - CUR_DIR = os.path.dirname(os.path.abspath(__file__)) -@@ -395,3 +396,26 @@ def test_modify_grubenv_to_have_separate_blsdir(monkeypatch, has_separate_boot): - monkeypatch.setattr(addupgradebootentry, 'run', run_mocked) - - addupgradebootentry.modify_our_grubenv_to_have_separate_blsdir(efi_info) -+ -+ -+def test_collect_undesired_args_includes_upgrade_to_remove(monkeypatch): -+ msgs = [ -+ UpgradeKernelCmdlineArgTasks(to_remove=[ -+ KernelCmdlineArg(key='rd.luks.uuid', value='luks-aaa'), -+ KernelCmdlineArg(key='rd.luks.uuid', value='luks-bbb'), -+ ]), -+ ] -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) -+ -+ undesired = addupgradebootentry.collect_undesired_args(livemode_enabled=False) -+ -+ assert ('rd.luks.uuid', 'luks-aaa') in undesired -+ assert ('rd.luks.uuid', 'luks-bbb') in undesired -+ -+ -+def test_collect_undesired_args_no_upgrade_to_remove(monkeypatch): -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[])) -+ -+ undesired = addupgradebootentry.collect_undesired_args(livemode_enabled=False) -+ -+ assert undesired == set() -diff --git a/repos/system_upgrade/common/models/kernelcmdlineargs.py b/repos/system_upgrade/common/models/kernelcmdlineargs.py -index fafd2853..2f163c34 100644 ---- a/repos/system_upgrade/common/models/kernelcmdlineargs.py -+++ b/repos/system_upgrade/common/models/kernelcmdlineargs.py -@@ -39,10 +39,14 @@ class LateTargetKernelCmdlineArgTasks(Model): - class UpgradeKernelCmdlineArgTasks(Model): - """ - Modifications of the upgrade kernel cmdline. -+ -+ The arguments in to_remove have precedence over argument in to_add. That is, if 'ARG' -+ is in to_remove, it is guaranteed to be removed (even if it is also in to_add). - """ - topic = SystemInfoTopic - - to_add = fields.List(fields.Model(KernelCmdlineArg), default=[]) -+ to_remove = fields.List(fields.Model(KernelCmdlineArg), default=[]) - - - class KernelCmdline(Model): --- -2.54.0 - diff --git a/0061-checkluks-emit-messages-only-once.patch b/0061-checkluks-emit-messages-only-once.patch deleted file mode 100644 index aadbb7a..0000000 --- a/0061-checkluks-emit-messages-only-once.patch +++ /dev/null @@ -1,81 +0,0 @@ -From bb81f96120a8c8b2ecba4bb9db7ecb86da3867fb Mon Sep 17 00:00:00 2001 -From: Michal Hecko -Date: Thu, 7 May 2026 10:39:12 +0200 -Subject: [PATCH 061/105] checkluks: emit messages only once - -Currently, the checkluks actor emits messages for target userspace and -initramfs in a loop, i.e., a single message is emitted for every OK luks -partition. This patch changes the behavior, so that messages are emitted -only once, if there are any OK partitions present. ---- - .../actors/checkluks/libraries/checkluks.py | 50 +++++++++++-------- - 1 file changed, 28 insertions(+), 22 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/checkluks/libraries/checkluks.py b/repos/system_upgrade/common/actors/checkluks/libraries/checkluks.py -index f3e45b47..cdf86d08 100644 ---- a/repos/system_upgrade/common/actors/checkluks/libraries/checkluks.py -+++ b/repos/system_upgrade/common/actors/checkluks/libraries/checkluks.py -@@ -111,6 +111,7 @@ def check_invalid_luks_devices(): - - luks1_partitions = [] - no_tpm2_partitions = [] -+ ok_partitions = [] - ceph_vol = _get_ceph_volumes() - for luks_dump in luks_dumps.dumps: - # if the device is managed by ceph, don't inhibit -@@ -126,25 +127,30 @@ def check_invalid_luks_devices(): - if luks1_partitions or no_tpm2_partitions: - report_inhibitor(luks1_partitions, no_tpm2_partitions) - else: -- required_crypt_rpms = [ -- 'clevis', -- 'clevis-dracut', -- 'clevis-systemd', -- 'clevis-udisks2', -- 'clevis-luks', -- 'cryptsetup', -- 'tpm2-tss', -- 'tpm2-tools', -- 'tpm2-abrmd' -- ] -- api.produce(TargetUserSpaceUpgradeTasks( -- copy_files=[CopyFile(src="/etc/crypttab")], -- install_rpms=required_crypt_rpms) -- ) -- api.produce(UpgradeInitramfsTasks( -- include_files=['/etc/crypttab'], -- include_dracut_modules=[ -- DracutModule(name='clevis'), -- DracutModule(name='clevis-pin-tpm2') -- ]) -- ) -+ ok_partitions.append(luks_dump.device_name) -+ -+ if luks1_partitions or no_tpm2_partitions: -+ report_inhibitor(luks1_partitions, no_tpm2_partitions) -+ elif ok_partitions: -+ required_crypt_rpms = [ -+ 'clevis', -+ 'clevis-dracut', -+ 'clevis-systemd', -+ 'clevis-udisks2', -+ 'clevis-luks', -+ 'cryptsetup', -+ 'tpm2-tss', -+ 'tpm2-tools', -+ 'tpm2-abrmd' -+ ] -+ api.produce(TargetUserSpaceUpgradeTasks( -+ copy_files=[CopyFile(src="/etc/crypttab")], -+ install_rpms=required_crypt_rpms) -+ ) -+ api.produce(UpgradeInitramfsTasks( -+ include_files=['/etc/crypttab'], -+ include_dracut_modules=[ -+ DracutModule(name='clevis'), -+ DracutModule(name='clevis-pin-tpm2') -+ ]) -+ ) --- -2.54.0 - diff --git a/0062-checkluks-remove-rd.luks.uuid-from-the-upgrade-entry.patch b/0062-checkluks-remove-rd.luks.uuid-from-the-upgrade-entry.patch deleted file mode 100644 index 1ba2fbb..0000000 --- a/0062-checkluks-remove-rd.luks.uuid-from-the-upgrade-entry.patch +++ /dev/null @@ -1,256 +0,0 @@ -From 5e4cba2a334b7ff2f3413f0d67f316b86a945fe5 Mon Sep 17 00:00:00 2001 -From: Michal Hecko -Date: Thu, 7 May 2026 10:41:20 +0200 -Subject: [PATCH 062/105] checkluks: remove rd.luks.uuid from the upgrade entry - -Request removal of the rd.luks.uuid cmdline args from the upgrade boot -entry. These args filter which devices in /etc/cryptab can be unlocked -during early stages of the boot process. As we want to mount more -devices (essentially everything in /etc/cryptab), presence of these args -is undesired. - -Jira-ref: RHEL-145136 ---- - .../common/actors/checkluks/actor.py | 21 +++++- - .../actors/checkluks/libraries/checkluks.py | 44 ++++++++++-- - .../actors/checkluks/tests/test_checkluks.py | 67 +++++++++++++++++-- - 3 files changed, 118 insertions(+), 14 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/checkluks/actor.py b/repos/system_upgrade/common/actors/checkluks/actor.py -index 2ea16985..e382be98 100644 ---- a/repos/system_upgrade/common/actors/checkluks/actor.py -+++ b/repos/system_upgrade/common/actors/checkluks/actor.py -@@ -1,6 +1,15 @@ - from leapp.actors import Actor - from leapp.libraries.actor.checkluks import check_invalid_luks_devices --from leapp.models import CephInfo, LuksDumps, StorageInfo, TargetUserSpaceUpgradeTasks, UpgradeInitramfsTasks -+from leapp.models import ( -+ CephInfo, -+ KernelCmdline, -+ LuksDumps, -+ StorageInfo, -+ TargetKernelCmdlineArgTasks, -+ TargetUserSpaceUpgradeTasks, -+ UpgradeInitramfsTasks, -+ UpgradeKernelCmdlineArgTasks -+) - from leapp.reporting import Report - from leapp.tags import ChecksPhaseTag, IPUWorkflowTag - -@@ -15,8 +24,14 @@ class CheckLuks(Actor): - """ - - name = 'check_luks' -- consumes = (CephInfo, LuksDumps, StorageInfo) -- produces = (Report, TargetUserSpaceUpgradeTasks, UpgradeInitramfsTasks) -+ consumes = (CephInfo, KernelCmdline, LuksDumps, StorageInfo) -+ produces = ( -+ Report, -+ TargetKernelCmdlineArgTasks, -+ TargetUserSpaceUpgradeTasks, -+ UpgradeInitramfsTasks, -+ UpgradeKernelCmdlineArgTasks -+ ) - tags = (ChecksPhaseTag, IPUWorkflowTag) - - def process(self): -diff --git a/repos/system_upgrade/common/actors/checkluks/libraries/checkluks.py b/repos/system_upgrade/common/actors/checkluks/libraries/checkluks.py -index cdf86d08..8db1db19 100644 ---- a/repos/system_upgrade/common/actors/checkluks/libraries/checkluks.py -+++ b/repos/system_upgrade/common/actors/checkluks/libraries/checkluks.py -@@ -6,9 +6,12 @@ from leapp.models import ( - CephInfo, - CopyFile, - DracutModule, -+ KernelCmdline, - LuksDumps, -+ TargetKernelCmdlineArgTasks, - TargetUserSpaceUpgradeTasks, -- UpgradeInitramfsTasks -+ UpgradeInitramfsTasks, -+ UpgradeKernelCmdlineArgTasks - ) - from leapp.reporting import create_report - -@@ -103,6 +106,41 @@ def report_inhibitor(luks1_partitions, no_tpm2_partitions): - ] + report_hints) - - -+def _emit_rdluks_undesired_for_upgrade_cmdline(): -+ """ -+ Identify rd.luks.uuid args on the source kernel cmdline and request their -+ removal from the upgrade boot entry. -+ -+ The upgrade initramfs uses clevis-tpm2 to unlock LUKS devices. Dracut's -+ rd.luks.uuid mechanism is not needed and can cause problems if left in. -+ This mechanism limits what devices will be unlocked during boot, i.e., only -+ devices named in the rd.luks.uuid args will be unlocked. However, we want -+ to unlock all LUKS devices (at least those in /etc/crypttab). Therefore, we -+ request that rd.luks.uuid args be removed from the upgrade kernel cmdline. -+ """ -+ cmdline = next(api.consume(KernelCmdline), None) -+ if not cmdline: -+ api.current_logger().debug('No KernelCmdline message received, nothing to do.') -+ return -+ -+ rdluks_args = [arg for arg in cmdline.parameters if arg.key == 'rd.luks.uuid'] -+ -+ if not rdluks_args: -+ api.current_logger().debug('No rd.luks.uuid args found on the source kernel cmdline.') -+ return -+ -+ api.current_logger().debug( -+ 'Requesting removal of rd.luks.uuid args from the upgrade kernel cmdline: %s', -+ ['{}={}'.format(a.key, a.value) for a in rdluks_args] -+ ) -+ api.produce(UpgradeKernelCmdlineArgTasks(to_remove=rdluks_args)) -+ -+ # When installing the target kernel RPM, the cmdline is copied from the booted system. -+ # As we remove the rd.luks.uuid args, we would accidentally remove them also from the -+ # target entry. Therefore, we add them back in here. -+ api.produce(TargetKernelCmdlineArgTasks(to_add=rdluks_args)) -+ -+ - def check_invalid_luks_devices(): - luks_dumps = next(api.consume(LuksDumps), None) - if not luks_dumps: -@@ -123,9 +161,6 @@ def check_invalid_luks_devices(): - luks1_partitions.append(luks_dump.device_name) - elif luks_dump.version == 2 and not _at_least_one_tpm_token(luks_dump): - no_tpm2_partitions.append(luks_dump.device_name) -- -- if luks1_partitions or no_tpm2_partitions: -- report_inhibitor(luks1_partitions, no_tpm2_partitions) - else: - ok_partitions.append(luks_dump.device_name) - -@@ -154,3 +189,4 @@ def check_invalid_luks_devices(): - DracutModule(name='clevis-pin-tpm2') - ]) - ) -+ _emit_rdluks_undesired_for_upgrade_cmdline() -diff --git a/repos/system_upgrade/common/actors/checkluks/tests/test_checkluks.py b/repos/system_upgrade/common/actors/checkluks/tests/test_checkluks.py -index 0147c4f8..0e13e09f 100644 ---- a/repos/system_upgrade/common/actors/checkluks/tests/test_checkluks.py -+++ b/repos/system_upgrade/common/actors/checkluks/tests/test_checkluks.py -@@ -1,13 +1,19 @@ -+from leapp.libraries.actor import checkluks - from leapp.libraries.common import distro --from leapp.libraries.common.config import version -+from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked -+from leapp.libraries.stdlib import api - from leapp.models import ( - CephInfo, -+ KernelCmdline, -+ KernelCmdlineArg, - LsblkEntry, - LuksDump, - LuksDumps, - LuksToken, -+ TargetKernelCmdlineArgTasks, - TargetUserSpaceUpgradeTasks, -- UpgradeInitramfsTasks -+ UpgradeInitramfsTasks, -+ UpgradeKernelCmdlineArgTasks - ) - from leapp.reporting import Report - from leapp.snactor.fixture import current_actor_context -@@ -17,7 +23,7 @@ _REPORT_TITLE_UNSUITABLE = 'Detected LUKS devices unsuitable for in-place upgrad - - - def test_actor_with_luks1_notpm(monkeypatch, current_actor_context): -- monkeypatch.setattr(version, 'get_source_major_version', lambda: '8') -+ monkeypatch.setattr(checkluks, 'get_source_major_version', lambda: '8') - monkeypatch.setattr(distro, 'get_source_distro_id', lambda: 'rhel') - monkeypatch.setattr(distro, 'get_target_distro_id', lambda: 'rhel') - -@@ -41,7 +47,7 @@ def test_actor_with_luks1_notpm(monkeypatch, current_actor_context): - - - def test_actor_with_luks2_notpm(monkeypatch, current_actor_context): -- monkeypatch.setattr(version, 'get_source_major_version', lambda: '8') -+ monkeypatch.setattr(checkluks, 'get_source_major_version', lambda: '8') - luks_dump = LuksDump( - version=2, - uuid='27b57c75-9adf-4744-ab04-9eb99726a301', -@@ -62,7 +68,7 @@ def test_actor_with_luks2_notpm(monkeypatch, current_actor_context): - - - def test_actor_with_luks2_invalid_token(monkeypatch, current_actor_context): -- monkeypatch.setattr(version, 'get_source_major_version', lambda: '8') -+ monkeypatch.setattr(checkluks, 'get_source_major_version', lambda: '8') - luks_dump = LuksDump( - version=2, - uuid='dc1dbe37-6644-4094-9839-8fc5dcbec0c6', -@@ -84,7 +90,7 @@ def test_actor_with_luks2_invalid_token(monkeypatch, current_actor_context): - - - def test_actor_with_luks2_clevis_tpm_token(monkeypatch, current_actor_context): -- monkeypatch.setattr(version, 'get_source_major_version', lambda: '8') -+ monkeypatch.setattr(checkluks, 'get_source_major_version', lambda: '8') - luks_dump = LuksDump( - version=2, - uuid='83050bd9-61c6-4ff0-846f-bfd3ac9bfc67', -@@ -113,7 +119,7 @@ def test_actor_with_luks2_clevis_tpm_token(monkeypatch, current_actor_context): - - - def test_actor_with_luks2_ceph(monkeypatch, current_actor_context): -- monkeypatch.setattr(version, 'get_source_major_version', lambda: '8') -+ monkeypatch.setattr(checkluks, 'get_source_major_version', lambda: '8') - ceph_volume = ['sda'] - current_actor_context.feed(CephInfo(encrypted_volumes=ceph_volume)) - luks_dump = LuksDump( -@@ -143,3 +149,50 @@ LSBLK_ENTRY = LsblkEntry( - parent_name="", - parent_path="" - ) -+ -+ -+def test_rdluks_uuid_args_removed_from_upgrade_cmdline(monkeypatch): -+ cmdline = KernelCmdline(parameters=[ -+ KernelCmdlineArg(key='root', value='/dev/mapper/rhel-root'), -+ KernelCmdlineArg(key='rd.luks.uuid', value='luks-aaa-bbb'), -+ KernelCmdlineArg(key='rd.luks.uuid', value='luks-ccc-ddd'), -+ KernelCmdlineArg(key='ro', value=None), -+ ]) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[cmdline])) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ checkluks._emit_rdluks_undesired_for_upgrade_cmdline() -+ -+ upgrade_msgs = [m for m in api.produce.model_instances if isinstance(m, UpgradeKernelCmdlineArgTasks)] -+ assert len(upgrade_msgs) == 1 -+ -+ removed_keys_values = {(arg.key, arg.value) for arg in upgrade_msgs[0].to_remove} -+ assert removed_keys_values == { -+ ('rd.luks.uuid', 'luks-aaa-bbb'), -+ ('rd.luks.uuid', 'luks-ccc-ddd'), -+ } -+ -+ target_msgs = [m for m in api.produce.model_instances if isinstance(m, TargetKernelCmdlineArgTasks)] -+ assert len(target_msgs) == 1 -+ -+ readded_keys_values = {(arg.key, arg.value) for arg in target_msgs[0].to_add} -+ assert readded_keys_values == { -+ ('rd.luks.uuid', 'luks-aaa-bbb'), -+ ('rd.luks.uuid', 'luks-ccc-ddd'), -+ } -+ -+ -+def test_no_rdluks_uuid_no_message(monkeypatch): -+ cmdline = KernelCmdline(parameters=[ -+ KernelCmdlineArg(key='root', value='/dev/mapper/rhel-root'), -+ KernelCmdlineArg(key='ro', value=None), -+ ]) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[cmdline])) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ checkluks._emit_rdluks_undesired_for_upgrade_cmdline() -+ -+ upgrade_msgs = [m for m in api.produce.model_instances if isinstance(m, UpgradeKernelCmdlineArgTasks)] -+ assert not upgrade_msgs -+ target_msgs = [m for m in api.produce.model_instances if isinstance(m, TargetKernelCmdlineArgTasks)] -+ assert not target_msgs --- -2.54.0 - diff --git a/0063-conftest-exit-actor-context-when-switching-repos.patch b/0063-conftest-exit-actor-context-when-switching-repos.patch deleted file mode 100644 index d805893..0000000 --- a/0063-conftest-exit-actor-context-when-switching-repos.patch +++ /dev/null @@ -1,114 +0,0 @@ -From 7d0bf3eed770e3cbaf48bfacbc1c11943f3a63c8 Mon Sep 17 00:00:00 2001 -From: Michal Hecko -Date: Mon, 11 May 2026 18:28:32 +0200 -Subject: [PATCH 063/105] conftest: exit actor context when switching repos - -Prior to this patch, the current_actor_context fixture was always exited -when executing a new actor. Exiting the fixture causes sys.meta_path to -be restored to a state when the fixture was first entered. Therefore, if -we are running tests across multiple repositories, say common and el9toel10, -then when we are executing the first actor from el9toel10 we execute -__exit__ of the last executed actor from the common repository. So the -following would happen: -1) Finish running the last actor named 'X' from the common repository. -2) Load el9toel10 repository, appending to sys.meta_path loaders for - leapp.libraries.common, etc. -3) Discovery of the tests of the first actor from el9toel10. -4) Detect that we are executing a different actor than 'X', so call - cleanup, which restores the state of sys.meta_path. Therefore, this - effectively removes all loaders for el9toel10 repository from - sys.meta_path. -5) Execute tests for the first actor from el9toel10. - -This patch ensures that we call current_actor_context.__exit__ whenever -we are about to start loading a new repository, preventing us from -unwanted removal of the loaders in sys.meta_path (added by the loaded -repository). ---- - conftest.py | 47 ++++++++++++++++++++++++++++++++++++----------- - 1 file changed, 36 insertions(+), 11 deletions(-) - -diff --git a/conftest.py b/conftest.py -index 5da5cc5f..542e22cb 100644 ---- a/conftest.py -+++ b/conftest.py -@@ -12,40 +12,65 @@ logging.getLogger("parso").setLevel(logging.INFO) - - - def _load_and_add_repo(manager, repo_path): -- repo = find_and_scan_repositories( -+ manager_with_new_repos = find_and_scan_repositories( - repo_path, - include_locals=True - ) -- unloaded = set() -- loaded = {r.repo_id for r in manager.repos} -- if hasattr(repo, 'repos'): -- for repo in repo.repos: -+ -+ newly_discovered_repo_ids = set() -+ already_known_repoids = {r.repo_id for r in manager.repos} -+ if hasattr(manager_with_new_repos, 'repos'): -+ for repo in manager_with_new_repos.repos: - if not manager.repo_by_id(repo.repo_id): - manager.add_repo(repo) -- unloaded.add(repo.repo_id) -+ newly_discovered_repo_ids.add(repo.repo_id) - else: - manager.add_repo(repo) -- if not loaded: -+ if not already_known_repoids: - manager.load(skip_actors_discovery=True) - else: -- for repo_id in unloaded: -+ for repo_id in newly_discovered_repo_ids: - manager.repo_by_id(repo_id).load(skip_actors_discovery=True) - - -+def _cleanup_actor_context_from_session(collector): -+ is_actor_context_attached = hasattr(collector.session, "current_actor_path") -+ -+ if not is_actor_context_attached: -+ return # Nothing to clean -+ -+ try: -+ collector.session.current_actor_context.__exit__( -+ None, None, None -+ ) -+ delattr(collector.session, 'current_actor_path') -+ collector.session.current_actor_context = None -+ collector.session.current_actor = None -+ except AttributeError: -+ pass -+ -+ - def pytest_collectstart(collector): - if collector.nodeid: - current_repo_basedir = find_repository_basedir(str(collector.fspath)) - if not current_repo_basedir: - # This is not a repository - return -+ - if not hasattr(collector.session, "leapp_repository"): - collector.session.leapp_repository = RepositoryManager() - collector.session.repo_base_dir = current_repo_basedir - _load_and_add_repo(collector.session.leapp_repository, current_repo_basedir) - else: -- if not collector.session.leapp_repository.repo_by_id( -- get_repository_id(current_repo_basedir) -- ): -+ repo_id = get_repository_id(current_repo_basedir) -+ repo = collector.session.leapp_repository.repo_by_id(repo_id) -+ if not repo: -+ # We are about to load a new repository, which will append module loaders -+ # to sys.meta_path. Cleaning actor context will restore the meta_path used -+ # when the context was entered, so if we did the cleanup later we would -+ # undo appends to the meta_path. -+ _cleanup_actor_context_from_session(collector) -+ - _load_and_add_repo(collector.session.leapp_repository, current_repo_basedir) - - # we're forcing the actor context switch only when traversing new --- -2.54.0 - diff --git a/0064-testutils-logger_mocked-accepts-kwargs.patch b/0064-testutils-logger_mocked-accepts-kwargs.patch deleted file mode 100644 index 15665de..0000000 --- a/0064-testutils-logger_mocked-accepts-kwargs.patch +++ /dev/null @@ -1,52 +0,0 @@ -From 8b5840dd94efca6a30ec1b1ba5ac75bb015e4dac Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Mon, 4 May 2026 14:41:41 +0200 -Subject: [PATCH 064/105] testutils: logger_mocked: accepts kwargs - -For logger calls specifying kw like: exc_info, stack_info, ... -methods in logger_mocked were broken. Allow to specify **kwargs so -the mocking is allowed. If any kw is specified, it's ignored in the -mocked call at this moment as we do not have use for it in tests. - -Assisted-by: Claude Sonnet 4.5 -Signed-off-by: Petr Stodulka ---- - repos/system_upgrade/common/libraries/testutils.py | 10 +++++----- - 1 file changed, 5 insertions(+), 5 deletions(-) - -diff --git a/repos/system_upgrade/common/libraries/testutils.py b/repos/system_upgrade/common/libraries/testutils.py -index 0a56d698..c9d6337a 100644 ---- a/repos/system_upgrade/common/libraries/testutils.py -+++ b/repos/system_upgrade/common/libraries/testutils.py -@@ -45,23 +45,23 @@ class logger_mocked: - self.warnmsg = [] - self.errmsg = [] - -- def debug(self, *args): -+ def debug(self, *args, **kwargs): - self.dbgmsg.extend(args) - -- def info(self, *args): -+ def info(self, *args, **kwargs): - self.infomsg.extend(args) - - @deprecated(since='2020-09-23', message=( - 'The logging.warn method has been deprecated since Python 3.3.' - 'Use the warning method instead.' - )) -- def warn(self, *args): -+ def warn(self, *args, **kwargs): - self.warnmsg.extend(args) - -- def warning(self, *args): -+ def warning(self, *args, **kwargs): - self.warnmsg.extend(args) - -- def error(self, *args): -+ def error(self, *args, **kwargs): - self.errmsg.extend(args) - - def __call__(self): --- -2.54.0 - diff --git a/0065-conftest-Add-leapp_tmpdir-fixture-for-standardized-t.patch b/0065-conftest-Add-leapp_tmpdir-fixture-for-standardized-t.patch deleted file mode 100644 index 6dac0a1..0000000 --- a/0065-conftest-Add-leapp_tmpdir-fixture-for-standardized-t.patch +++ /dev/null @@ -1,52 +0,0 @@ -From d3e20f73aca107a48ae7ee5734bd6a8542dfca96 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Mon, 4 May 2026 15:10:04 +0000 -Subject: [PATCH 065/105] conftest: Add leapp_tmpdir fixture for standardized - temp directories - -Introduces a project-wide pytest fixture that ensures all tests create -temporary directories with the 'leapptest-' prefix in /tmp, maintaining -consistency across the test suite and simplifying cleanup. - -Co-Authored-By: Claude Sonnet 4.5 -Signed-off-by: Petr Stodulka ---- - conftest.py | 19 +++++++++++++++++++ - 1 file changed, 19 insertions(+) - -diff --git a/conftest.py b/conftest.py -index 542e22cb..f3d02e74 100644 ---- a/conftest.py -+++ b/conftest.py -@@ -1,5 +1,9 @@ - import logging - import os -+import shutil -+import tempfile -+ -+import pytest - - from leapp.repository.manager import RepositoryManager - from leapp.repository.scan import find_and_scan_repositories -@@ -121,3 +125,18 @@ def pytest_runtestloop(session): - ) - except AttributeError: - pass -+ -+ -+@pytest.fixture -+def leapp_tmpdir(): -+ """ -+ Create a temporary directory with 'leapptest-' prefix in /tmp. -+ -+ This fixture automatically creates and cleans up a temporary directory -+ that follows the project's naming convention for test directories. -+ -+ :returns: Path to the temporary directory -+ :rtype: str -+ """ -+ with tempfile.TemporaryDirectory(prefix='leapptest-', dir='/tmp') as tmpdir: -+ yield tmpdir --- -2.54.0 - diff --git a/0066-Reorganize-DNF-libraries-into-dnflibs-package.patch b/0066-Reorganize-DNF-libraries-into-dnflibs-package.patch deleted file mode 100644 index 861ec2f..0000000 --- a/0066-Reorganize-DNF-libraries-into-dnflibs-package.patch +++ /dev/null @@ -1,1748 +0,0 @@ -From 9e165750360adf579d9b80d8cb1bf3518a74c445 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Tue, 10 Mar 2026 10:45:41 +0000 -Subject: [PATCH 066/105] Reorganize DNF libraries into dnflibs package - -Consolidate DNF-related libraries (dnfconfig, dnfplugin, module) into a -new dnflibs package for better organization. The old module locations -remain as deprecated shims using @deprecated decorator, ensuring full -backward compatibility while encouraging migration to the new structure. - -I plan additional changes in future and I consider the original -structure problematic, like: -* I want to expose some of private functions, but there is no good - place where to put them - and keeping them in original places is - not good. -* The module.py library has kind of ambiguous name and it was not - clear what to expect from it without reading the content. - -With this change, all actions related to DNF are nicely closured. - -Note that unit-tests are going to be added in a following commits. - -Changes: -- Create repos/system_upgrade/common/libraries/dnflibs/ package -- Move dnfconfig.py to dnflibs/dnfconfig.py -- Move dnfplugin.py to dnflibs/dnfplugin.py -- Move module.py to dnflibs/dnfmodule.py (renamed) -- Mark original libraries and their content as deprecated -- Update the documentation to reflect deprecated functionality - -Assisted-by: Claude Sonnet 4.5 -Signed-off-by: Petr Stodulka ---- - .../libraries-and-api/deprecations-list.md | 6 + - .../common/libraries/dnfconfig.py | 107 +--- - .../common/libraries/dnflibs/__init__.py | 8 + - .../common/libraries/dnflibs/dnfconfig.py | 108 ++++ - .../common/libraries/dnflibs/dnfmodule.py | 105 ++++ - .../common/libraries/dnflibs/dnfplugin.py | 551 ++++++++++++++++++ - .../common/libraries/dnfplugin.py | 550 +++-------------- - .../system_upgrade/common/libraries/module.py | 109 +--- - 8 files changed, 900 insertions(+), 644 deletions(-) - create mode 100644 repos/system_upgrade/common/libraries/dnflibs/__init__.py - create mode 100644 repos/system_upgrade/common/libraries/dnflibs/dnfconfig.py - create mode 100644 repos/system_upgrade/common/libraries/dnflibs/dnfmodule.py - create mode 100644 repos/system_upgrade/common/libraries/dnflibs/dnfplugin.py - -diff --git a/docs/source/libraries-and-api/deprecations-list.md b/docs/source/libraries-and-api/deprecations-list.md -index a074ebc1..aa20f874 100644 ---- a/docs/source/libraries-and-api/deprecations-list.md -+++ b/docs/source/libraries-and-api/deprecations-list.md -@@ -18,6 +18,12 @@ Only the versions in which a deprecation has been made are listed. - - **`LEAPP_NO_NETWORK_RENAMING`** - It becomes obsoleted by the solution based on `net.naming-scheme` which replaces the legacy solution based on created udev link files correcting NIC names during the upgrade. - - Models: - - **`RenamedInterfaces`** - Information provided in this message is not always complete and it's not used since the `net.naming-scheme` kernel command line argument is set during the upgrade. -+- Shared libraries -+ - DNF-related libraries have been reorganized into the `leapp.libraries.common.dnflibs` package. The following modules have been moved: -+ - **`leapp.libraries.common.dnfconfig`** - Moved to `leapp.libraries.common.dnflibs.dnfconfig`. All public functions (`exclude_leapp_rpms`) are deprecated in the old location. -+ - **`leapp.libraries.common.dnfplugin`** - Moved to `leapp.libraries.common.dnflibs.dnfplugin`. All public functions (`install`, `build_plugin_data`, `create_config`, `backup_config`, `backup_debug_data`, `apply_workarounds`, `install_initramdisk_requirements`, `perform_transaction_install`, `perform_transaction_check`, `perform_rpm_download`, `perform_dry_run`) and constants (`DNF_PLUGIN_NAME`, `DNF_PLUGIN_PATH`, `DNF_PLUGIN_DATA_NAME`, `DNF_PLUGIN_DATA_PATH`, `DNF_PLUGIN_DATA_LOG_PATH`, `DNF_DEBUG_DATA_PATH`) are available in the new location. -+ - **`leapp.libraries.common.module`** - Moved to `leapp.libraries.common.dnflibs.dnfmodule` (renamed for clarity). All public functions (`get_modules`, `get_enabled_modules`, `map_installed_rpms_to_modules`) are deprecated in the old location. -+ - - ## v0.24.0 (till September 2026) - - Shared libraries -diff --git a/repos/system_upgrade/common/libraries/dnfconfig.py b/repos/system_upgrade/common/libraries/dnfconfig.py -index 9f1902b6..b0997836 100644 ---- a/repos/system_upgrade/common/libraries/dnfconfig.py -+++ b/repos/system_upgrade/common/libraries/dnfconfig.py -@@ -1,98 +1,20 @@ --from leapp.exceptions import StopActorExecutionError --from leapp.libraries.common.rpms import get_leapp_packages --from leapp.libraries.stdlib import api, CalledProcessError -+""" -+DEPRECATED: This module has been moved to leapp.libraries.common.dnflibs.dnfconfig - -+This shim will be removed in a future version. Please update imports to: -+ from leapp.libraries.common.dnflibs import dnfconfig -+ # or -+ from leapp.libraries.common import dnflibs -+""" - --def _strip_split(data, sep, maxsplit=-1): -- """ -- Just like str.split(), but remove ambient whitespaces from all items -- """ -- return [item.strip() for item in data.split(sep, maxsplit)] -- -- --def _get_main_dump(context, disable_plugins): -- """ -- Return the dnf configuration dump of main options for the given context. -- -- Returns the list of lines after the line with "[main]" section -- """ -- -- cmd = ['dnf', 'config-manager', '--dump'] -- -- if disable_plugins: -- for plugin in disable_plugins: -- cmd += ['--disableplugin', plugin] -- -- try: -- data = context.call(cmd, split=True)['stdout'] -- except CalledProcessError as e: -- api.current_logger().error('Cannot obtain the dnf configuration') -- raise StopActorExecutionError( -- message='Cannot obtain data about the DNF configuration', -- details={'stdout': e.stdout, 'stderr': e.stderr} -- ) -- -- try: -- # return index of the first item in the main section -- main_start = data.index('[main]') + 1 -- except ValueError: -- raise StopActorExecutionError( -- message='Invalid DNF configuration data (missing [main])', -- details=data, -- ) -- -- output_data = {} -- for line in data[main_start:]: -- if not line.strip(): -- continue -- try: -- key, val = _strip_split(line, '=', 1) -- output_data[key] = val -- except ValueError: -- # This is not expected to happen, but call it a seatbelt in case -- # the dnf dump implementation will change and we will miss it -- # This is not such a hard error as the one above, as it means -- # some values could be incomplete, however we are still able -- # to continue. -- api.current_logger().warning( -- 'Cannot parse the dnf dump correctly, line: {}'.format(line)) -- pass -- -- return output_data -- -- --def _get_excluded_pkgs(context, disable_plugins): -- """ -- Return the list of excluded packages for DNF in the given context. -- -- It shouldn't be used on the source system. It is expected this functions -- is called only in the target userspace container or on the target system. -- """ -- pkgs = _strip_split(_get_main_dump(context, disable_plugins).get('exclude', ''), ',') -- return [i for i in pkgs if i] -- -- --def _set_excluded_pkgs(context, pkglist, disable_plugins): -- """ -- Configure DNF to exclude packages in the given list -- -- Raise the CalledProcessError on error. -- """ -- exclude = 'exclude={}'.format(','.join(pkglist)) -- cmd = ['dnf', 'config-manager', '--save', '--setopt', exclude] -- -- if disable_plugins: -- for plugin in disable_plugins: -- cmd += ['--disableplugin', plugin] -- -- try: -- context.call(cmd) -- except CalledProcessError: -- api.current_logger().error('Cannot set the dnf configuration') -- raise -- api.current_logger().debug('The DNF configuration has been updated to exclude leapp packages.') -+from leapp.libraries.common.dnflibs import dnfconfig as _dnfconfig -+from leapp.utils.deprecation import deprecated - - -+@deprecated(since='2026-03-10', message=( -+ 'This function has been moved to leapp.libraries.common.dnflibs.dnfconfig module. ' -+ 'Please update your imports to use the new location.' -+)) - def exclude_leapp_rpms(context, disable_plugins): - """ - Ensure the leapp RPMs are excluded from any DNF transaction. -@@ -104,5 +26,4 @@ def exclude_leapp_rpms(context, disable_plugins): - So user will have to drop these packages from the exclude after the - upgrade. - """ -- to_exclude = list(set(_get_excluded_pkgs(context, disable_plugins) + get_leapp_packages())) -- _set_excluded_pkgs(context, to_exclude, disable_plugins) -+ return _dnfconfig.exclude_leapp_rpms(context, disable_plugins) -diff --git a/repos/system_upgrade/common/libraries/dnflibs/__init__.py b/repos/system_upgrade/common/libraries/dnflibs/__init__.py -new file mode 100644 -index 00000000..d94e4b4c ---- /dev/null -+++ b/repos/system_upgrade/common/libraries/dnflibs/__init__.py -@@ -0,0 +1,8 @@ -+""" -+DNF-related libraries for the upgrade process. -+ -+This package consolidates DNF functionality previously scattered across: -+- leapp.libraries.common.dnfconfig -> dnflibs.dnfconfig -+- leapp.libraries.common.dnfplugin -> dnflibs.dnfplugin -+- leapp.libraries.common.module -> dnflibs.dnfmodule -+""" -diff --git a/repos/system_upgrade/common/libraries/dnflibs/dnfconfig.py b/repos/system_upgrade/common/libraries/dnflibs/dnfconfig.py -new file mode 100644 -index 00000000..9f1902b6 ---- /dev/null -+++ b/repos/system_upgrade/common/libraries/dnflibs/dnfconfig.py -@@ -0,0 +1,108 @@ -+from leapp.exceptions import StopActorExecutionError -+from leapp.libraries.common.rpms import get_leapp_packages -+from leapp.libraries.stdlib import api, CalledProcessError -+ -+ -+def _strip_split(data, sep, maxsplit=-1): -+ """ -+ Just like str.split(), but remove ambient whitespaces from all items -+ """ -+ return [item.strip() for item in data.split(sep, maxsplit)] -+ -+ -+def _get_main_dump(context, disable_plugins): -+ """ -+ Return the dnf configuration dump of main options for the given context. -+ -+ Returns the list of lines after the line with "[main]" section -+ """ -+ -+ cmd = ['dnf', 'config-manager', '--dump'] -+ -+ if disable_plugins: -+ for plugin in disable_plugins: -+ cmd += ['--disableplugin', plugin] -+ -+ try: -+ data = context.call(cmd, split=True)['stdout'] -+ except CalledProcessError as e: -+ api.current_logger().error('Cannot obtain the dnf configuration') -+ raise StopActorExecutionError( -+ message='Cannot obtain data about the DNF configuration', -+ details={'stdout': e.stdout, 'stderr': e.stderr} -+ ) -+ -+ try: -+ # return index of the first item in the main section -+ main_start = data.index('[main]') + 1 -+ except ValueError: -+ raise StopActorExecutionError( -+ message='Invalid DNF configuration data (missing [main])', -+ details=data, -+ ) -+ -+ output_data = {} -+ for line in data[main_start:]: -+ if not line.strip(): -+ continue -+ try: -+ key, val = _strip_split(line, '=', 1) -+ output_data[key] = val -+ except ValueError: -+ # This is not expected to happen, but call it a seatbelt in case -+ # the dnf dump implementation will change and we will miss it -+ # This is not such a hard error as the one above, as it means -+ # some values could be incomplete, however we are still able -+ # to continue. -+ api.current_logger().warning( -+ 'Cannot parse the dnf dump correctly, line: {}'.format(line)) -+ pass -+ -+ return output_data -+ -+ -+def _get_excluded_pkgs(context, disable_plugins): -+ """ -+ Return the list of excluded packages for DNF in the given context. -+ -+ It shouldn't be used on the source system. It is expected this functions -+ is called only in the target userspace container or on the target system. -+ """ -+ pkgs = _strip_split(_get_main_dump(context, disable_plugins).get('exclude', ''), ',') -+ return [i for i in pkgs if i] -+ -+ -+def _set_excluded_pkgs(context, pkglist, disable_plugins): -+ """ -+ Configure DNF to exclude packages in the given list -+ -+ Raise the CalledProcessError on error. -+ """ -+ exclude = 'exclude={}'.format(','.join(pkglist)) -+ cmd = ['dnf', 'config-manager', '--save', '--setopt', exclude] -+ -+ if disable_plugins: -+ for plugin in disable_plugins: -+ cmd += ['--disableplugin', plugin] -+ -+ try: -+ context.call(cmd) -+ except CalledProcessError: -+ api.current_logger().error('Cannot set the dnf configuration') -+ raise -+ api.current_logger().debug('The DNF configuration has been updated to exclude leapp packages.') -+ -+ -+def exclude_leapp_rpms(context, disable_plugins): -+ """ -+ Ensure the leapp RPMs are excluded from any DNF transaction. -+ -+ This has to be called several times to ensure that our RPMs are not removed -+ or updated (replaced) during the IPU. The action should happen inside -+ - the target userspace container -+ - on the host system -+ So user will have to drop these packages from the exclude after the -+ upgrade. -+ """ -+ to_exclude = list(set(_get_excluded_pkgs(context, disable_plugins) + get_leapp_packages())) -+ _set_excluded_pkgs(context, to_exclude, disable_plugins) -diff --git a/repos/system_upgrade/common/libraries/dnflibs/dnfmodule.py b/repos/system_upgrade/common/libraries/dnflibs/dnfmodule.py -new file mode 100644 -index 00000000..d85e4659 ---- /dev/null -+++ b/repos/system_upgrade/common/libraries/dnflibs/dnfmodule.py -@@ -0,0 +1,105 @@ -+import warnings -+ -+from leapp.exceptions import StopActorExecutionError -+from leapp.libraries.common.config.version import get_source_major_version -+ -+try: -+ import dnf -+except ImportError: -+ dnf = None -+ warnings.warn('Could not import the `dnf` python module.', ImportWarning) -+ -+try: -+ import hawkey -+except ImportError: -+ hawkey = None -+ warnings.warn('Could not import the `hawkey` python module.', ImportWarning) -+ -+ -+def _create_or_get_dnf_base(base=None): -+ if not base: -+ # The DNF command reads /etc/yum/vars/releasever, but the DNF library does not. It parses redhat-release -+ # package to retrieve system's major version which it then uses as $releasever. However, some systems might -+ # have repositories only for the exact system version (including the minor number). In a case when -+ # /etc/yum/vars/releasever is present, read its contents so that we can access repositores on such systems. -+ conf = dnf.conf.Conf() -+ -+ # preload releasever from what we know, this will be our fallback -+ conf.substitutions['releasever'] = get_source_major_version() -+ -+ # load all substitutions from etc -+ conf.substitutions.update_from_etc('/') -+ -+ base = dnf.Base(conf=conf) -+ base.conf.read() -+ base.init_plugins() -+ base.read_all_repos() -+ # configure plugins after the repositories are loaded -+ # e.g. the amazon-id plugin requires loaded repositories -+ # for the proper configuration. -+ base.configure_plugins() -+ -+ try: -+ base.fill_sack() -+ except dnf.exceptions.RepoError as e: -+ err_msg = str(e) -+ repoid = err_msg.split('repo:')[-1].strip() if 'repo:' in err_msg else 'unknown repo' -+ repoid = repoid.strip('"').strip("'").replace('\\"', '') -+ raise StopActorExecutionError( -+ message='DNF failed to load repositories: {}'.format(str(e)), -+ details={ -+ 'hint': 'Ensure the {} repository definition is correct or remove it ' -+ 'if the repository is not needed anymore.'.format(repoid) -+ } -+ ) -+ return base -+ -+ -+def get_modules(base=None): -+ """ -+ Return info about all module streams as a list of libdnf.module.ModulePackage objects. -+ """ -+ if not dnf: -+ return [] -+ base = _create_or_get_dnf_base(base) -+ -+ module_base = dnf.module.module_base.ModuleBase(base) -+ # this method is absent on RHEL 7, in which case there are no modules anyway -+ if not hasattr(module_base, 'get_modules'): -+ return [] -+ return module_base.get_modules('*')[0] -+ -+ -+def get_enabled_modules(): -+ """ -+ Return currently enabled module streams as a list of libdnf.module.ModulePackage objects. -+ """ -+ if not dnf: -+ return [] -+ -+ base = _create_or_get_dnf_base() -+ modules = get_modules(base) -+ -+ # if modules are not supported (RHEL 7), base.sack._moduleContainer won't exist -+ # luckily in that case modules are empty and the element won't even be accessed -+ return [m for m in modules if base.sack._moduleContainer.isEnabled(m)] -+ -+ -+def map_installed_rpms_to_modules(): -+ """ -+ Map installed modular packages to the module streams they come from. -+ """ -+ modules = get_modules() -+ # empty on RHEL 7 because of no modules -+ if not modules: -+ return {} -+ # create a reverse mapping from the RPMS to module streams -+ # key: tuple of 4 strings representing a NVRA (name, version, release, arch) of an RPM -+ # value: tuple of 2 strings representing a module and its stream -+ rpm_streams = {} -+ for module in modules: -+ for rpm in module.getArtifacts(): -+ nevra = hawkey.split_nevra(rpm) -+ rpm_key = (nevra.name, nevra.version, nevra.release, nevra.arch) -+ rpm_streams[rpm_key] = (module.getName(), module.getStream()) -+ return rpm_streams -diff --git a/repos/system_upgrade/common/libraries/dnflibs/dnfplugin.py b/repos/system_upgrade/common/libraries/dnflibs/dnfplugin.py -new file mode 100644 -index 00000000..32407283 ---- /dev/null -+++ b/repos/system_upgrade/common/libraries/dnflibs/dnfplugin.py -@@ -0,0 +1,551 @@ -+import contextlib -+import itertools -+import json -+import os -+import re -+import shutil -+ -+from leapp.exceptions import StopActorExecutionError -+from leapp.libraries.common import guards, mounting, overlaygen, rhsm, utils -+from leapp.libraries.common.config.version import get_target_major_version, get_target_version -+from leapp.libraries.common.dnflibs import dnfconfig -+from leapp.libraries.common.gpg import is_nogpgcheck_set -+from leapp.libraries.stdlib import api, CalledProcessError, config -+from leapp.models import DNFWorkaround -+ -+DNF_PLUGIN_NAME = 'rhel_upgrade.py' -+_DEDICATED_URL = 'https://access.redhat.com/solutions/7011704' -+ -+ -+class _DnfPluginPathStr(str): -+ _PATHS = { -+ "9": os.path.join('/lib/python3.9/site-packages/dnf-plugins', DNF_PLUGIN_NAME), -+ "10": os.path.join('/lib/python3.12/site-packages/dnf-plugins', DNF_PLUGIN_NAME), -+ } -+ -+ def __init__(self): # noqa: W0231; pylint: disable=super-init-not-called -+ self.data = "" -+ -+ def _feed(self): -+ major = get_target_major_version() -+ if major not in _DnfPluginPathStr._PATHS: -+ raise KeyError('{} is not a supported target version of RHEL'.format(major)) -+ self.data = _DnfPluginPathStr._PATHS[major] -+ -+ def __str__(self): -+ self._feed() -+ return str(self.data) -+ -+ def __repr__(self): -+ self._feed() -+ return repr(self.data) -+ -+ def lstrip(self, chars=None): -+ self._feed() -+ return self.data.lstrip(chars) -+ -+ -+# Deprecated -+DNF_PLUGIN_PATH = _DnfPluginPathStr() -+ -+DNF_PLUGIN_DATA_NAME = 'dnf-plugin-data.txt' -+DNF_PLUGIN_DATA_PATH = os.path.join('/var/lib/leapp', DNF_PLUGIN_DATA_NAME) -+DNF_PLUGIN_DATA_LOG_PATH = os.path.join('/var/log/leapp', DNF_PLUGIN_DATA_NAME) -+DNF_DEBUG_DATA_PATH = '/var/log/leapp/dnf-debugdata/' -+ -+ -+def install(target_basedir): -+ """ -+ Installs our plugin to the DNF plugins. -+ """ -+ try: -+ shutil.copy2( -+ api.get_file_path(DNF_PLUGIN_NAME), -+ os.path.join(target_basedir, DNF_PLUGIN_PATH.lstrip('/'))) -+ except EnvironmentError as e: -+ api.current_logger().debug('Failed to install DNF plugin', exc_info=True) -+ raise StopActorExecutionError( -+ message='Failed to install DNF plugin. Error: {}'.format(str(e)) -+ ) -+ -+ -+def _rebuild_rpm_db(context, root=None): -+ """ -+ Convert rpmdb from BerkeleyDB to Sqlite -+ """ -+ base_cmd = ['rpmdb', '--rebuilddb'] -+ cmd = base_cmd if not root else base_cmd + ['-r', root] -+ context.call(cmd) -+ -+ -+def build_plugin_data(target_repoids, debug, test, tasks, on_aws): -+ """ -+ Generates a dictionary with the DNF plugin data. -+ """ -+ # get list of repo IDs of target repositories that should be used for upgrade -+ data = { -+ 'pkgs_info': { -+ 'local_rpms': sorted(os.path.join('/installroot', pkg.lstrip('/')) for pkg in tasks.local_rpms), -+ 'to_install': sorted(tasks.to_install), -+ 'to_remove': sorted(tasks.to_remove), -+ 'to_upgrade': sorted(tasks.to_upgrade), -+ 'modules_to_enable': sorted(['{}:{}'.format(m.name, m.stream) for m in tasks.modules_to_enable]), -+ }, -+ 'dnf_conf': { -+ 'allow_erasing': True, -+ 'best': True, -+ 'debugsolver': debug, -+ 'disable_repos': True, -+ 'enable_repos': target_repoids, -+ 'gpgcheck': not is_nogpgcheck_set(), -+ 'platform_id': 'platform:el{}'.format(get_target_major_version()), -+ 'releasever': get_target_version(), -+ 'installroot': '/installroot', -+ 'test_flag': test -+ }, -+ 'rhui': { -+ 'aws': { -+ 'on_aws': on_aws, -+ 'region': None, -+ } -+ } -+ } -+ return data -+ -+ -+def create_config(context, target_repoids, debug, test, tasks, on_aws=False): -+ """ -+ Creates the configuration data file for our DNF plugin. -+ """ -+ context.makedirs(os.path.dirname(DNF_PLUGIN_DATA_PATH), exists_ok=True) -+ with context.open(DNF_PLUGIN_DATA_PATH, 'w+') as f: -+ config_data = build_plugin_data( -+ target_repoids=target_repoids, debug=debug, test=test, tasks=tasks, on_aws=on_aws -+ ) -+ json.dump(config_data, f, sort_keys=True, indent=2) -+ -+ -+def backup_config(context): -+ """ -+ Backs up the configuration data used for the plugin. -+ """ -+ context.copy_from(DNF_PLUGIN_DATA_PATH, DNF_PLUGIN_DATA_LOG_PATH) -+ -+ -+def backup_debug_data(context): -+ """ -+ Performs the backup of DNF debug data -+ """ -+ if config.is_debug(): -+ # The debugdata is a folder generated by dnf when using the --debugsolver dnf option. We switch on the -+ # debug_solver dnf config parameter in our rhel-upgrade dnf plugin when LEAPP_DEBUG env var set to 1. -+ try: -+ context.copytree_from('/debugdata', DNF_DEBUG_DATA_PATH) -+ except OSError as e: -+ api.current_logger().warning('Failed to copy debugdata. Message: {}'.format(str(e)), exc_info=True) -+ -+ -+def _handle_transaction_err_msg_old(stage, xfs_info, err): -+ # NOTE(pstodulk): This is going to be removed in future! -+ message = 'DNF execution failed with non zero exit code.' -+ details = {'STDOUT': err.stdout, 'STDERR': err.stderr} -+ -+ if 'more space needed on the' in err.stderr and stage != 'upgrade': -+ # Disk Requirements: -+ # At least more space needed on the filesystem. -+ # -+ article_section = 'Generic case' -+ if xfs_info.present and xfs_info.without_ftype: -+ article_section = 'XFS ftype=0 case' -+ -+ message = ('There is not enough space on the file system hosting /var/lib/leapp directory ' -+ 'to extract the packages.') -+ details = {'hint': "Please follow the instructions in the '{}' section of the article at: " -+ "link: https://access.redhat.com/solutions/5057391".format(article_section)} -+ -+ raise StopActorExecutionError(message=message, details=details) -+ -+ -+def _handle_transaction_err_msg(err, is_container=False): -+ NO_SPACE_STR = 'more space needed on the' -+ if NO_SPACE_STR not in err.stderr: -+ message = 'DNF execution failed with non zero exit code.' -+ # if there was a problem reaching repos and proxy is configured in DNF/YUM configs, the -+ # proxy is likely the problem. -+ # NOTE(mmatuska): We can't consistently detect there was a problem reaching some repos, -+ # because it isn't clear what are all the possible DNF error messages we can encounter, -+ # such as: "Failed to synchronize cache for repo ..." or "Errors during downloading -+ # metadata for # repository" or "No more mirrors to try - All mirrors were already tried -+ # without success" -+ # NOTE(mmatuska): We could check PkgManagerInfo to detect if proxy is indeed configured, -+ # however it would be pretty ugly to pass it all the way down here -+ proxy_hint = ( -+ "If there was a problem reaching remote content (see stderr output) and proxy is " -+ "configured in the YUM/DNF configuration file, the proxy configuration is likely " -+ "causing this error. " -+ "Make sure the proxy is properly configured in /etc/dnf/dnf.conf. " -+ "It's also possible the proxy settings in the DNF configuration file are " -+ "incompatible with the target system. A compatible configuration can be " -+ "placed in /etc/leapp/files/dnf.conf which, if present, it will be used during " -+ "some parts of the upgrade instead of original /etc/dnf/dnf.conf. " -+ "In such case the configuration will also be applied to the target system. " -+ "Note that /etc/dnf/dnf.conf needs to be still configured correctly " -+ "for your current system to pass the early phases of the upgrade process." -+ ) -+ details = {'STDOUT': err.stdout, 'STDERR': err.stderr, 'hint': proxy_hint} -+ raise StopActorExecutionError(message=message, details=details) -+ -+ # Disk Requirements: -+ # At least more space needed on the filesystem. -+ # -+ missing_space = [line.strip() for line in err.stderr.split('\n') if NO_SPACE_STR in line] -+ if is_container: -+ size_str = re.match(r'At least (.*) more space needed', missing_space[0]).group(1) -+ message = 'There is not enough space on the file system hosting /var/lib/leapp.' -+ hint = ( -+ 'Increase the free space on the filesystem hosting' -+ ' /var/lib/leapp by {} at minimum. It is suggested to provide' -+ ' reasonably more space to be able to perform all planned actions' -+ ' (e.g. when 200MB is missing, add 1700MB or more).\n\n' -+ 'It is also a good practice to create dedicated partition' -+ ' for /var/lib/leapp when more space is needed, which can be' -+ ' dropped after the system upgrade is fully completed' -+ ' For more info, see: {}' -+ .format(size_str, _DEDICATED_URL) -+ ) -+ # we do not want to confuse customers by the orig msg speaking about -+ # missing space on '/'. Skip the Disk Requirements section. -+ # The information is part of the hint. -+ details = {'hint': hint} -+ else: -+ message = 'There is not enough space on some file systems to perform the upgrade transaction.' -+ hint = ( -+ 'Increase the free space on listed filesystems. Presented values' -+ ' are required minimum calculated by RPM and it is suggested to' -+ ' provide reasonably more free space (e.g. when 200 MB is missing' -+ ' on /usr, add 1200MB or more).' -+ ) -+ details = {'hint': hint, 'Disk Requirements': '\n'.join(missing_space)} -+ -+ raise StopActorExecutionError(message=message, details=details) -+ -+ -+def _transaction(context, stage, target_repoids, tasks, plugin_info, xfs_info, -+ test=False, cmd_prefix=None, on_aws=False): -+ """ -+ Perform the actual DNF rpm download via our DNF plugin -+ """ -+ -+ # we do not want -+ if stage not in ['dry-run', 'upgrade']: -+ create_config( -+ context=context, -+ target_repoids=target_repoids, -+ debug=config.is_debug(), -+ test=test, tasks=tasks, -+ on_aws=on_aws -+ ) -+ backup_config(context=context) -+ -+ # FIXME: rhsm -+ with guards.guarded_execution(guards.connection_guard(), guards.space_guard()): -+ cmd_prefix = cmd_prefix or [] -+ common_params = [] -+ if config.is_verbose(): -+ common_params.append('-v') -+ if rhsm.skip_rhsm(): -+ common_params += ['--disableplugin', 'subscription-manager'] -+ if plugin_info: -+ for info in plugin_info: -+ if stage in info.disable_in: -+ common_params += ['--disableplugin', info.name] -+ env = {} -+ if get_target_major_version() == '9': -+ # allow handling new RHEL 9 syscalls by systemd-nspawn -+ env = {'SYSTEMD_SECCOMP': '0'} -+ -+ if tasks.modules_to_reset: -+ # We shall only reset modules that are not going to be enabled -+ # This will make sure it is so -+ modules_to_reset = {(module.name, module.stream) for module in tasks.modules_to_reset} -+ modules_to_enable = {(module.name, module.stream) for module in tasks.modules_to_enable} -+ module_reset_list = [module[0] for module in modules_to_reset - modules_to_enable] -+ # Perform module reset -+ cmd = ['/usr/bin/dnf', 'module', 'reset', '--enabled', ] + module_reset_list -+ cmd += ['--disablerepo', '*', '-y', '--installroot', '/installroot'] -+ try: -+ context.call( -+ cmd=cmd_prefix + cmd + common_params, -+ callback_raw=utils.logging_handler, -+ env=env -+ ) -+ except (CalledProcessError, OSError): -+ api.current_logger().debug('Failed to reset modules via dnf with an error. Ignoring.', -+ exc_info=True) -+ -+ cmd = [ -+ '/usr/bin/dnf', -+ 'rhel-upgrade', -+ stage, -+ DNF_PLUGIN_DATA_PATH -+ ] -+ try: -+ context.call( -+ cmd=cmd_prefix + cmd + common_params, -+ callback_raw=utils.logging_handler, -+ env=env -+ ) -+ except OSError as e: -+ api.current_logger().error('Could not call dnf command: Message: %s', str(e), exc_info=True) -+ raise StopActorExecutionError( -+ message='Failed to execute dnf. Reason: {}'.format(str(e)) -+ ) -+ except CalledProcessError as e: -+ api.current_logger().error('Cannot calculate, check, test, or perform the upgrade transaction.') -+ _handle_transaction_err_msg(e, is_container=False) -+ finally: -+ if stage == 'check': -+ backup_debug_data(context=context) -+ -+ -+@contextlib.contextmanager -+def _prepare_transaction(used_repos, target_userspace_info, binds=()): -+ """ Creates the transaction environment needed for the target userspace DNF execution """ -+ target_repoids = set() -+ for message in used_repos: -+ target_repoids.update([repo.repoid for repo in message.repos]) -+ with mounting.NspawnActions(base_dir=target_userspace_info.path, binds=binds) as context: -+ yield context, list(target_repoids), target_userspace_info -+ -+ -+def apply_workarounds(context=None): -+ """ -+ Apply registered workarounds in the given context environment -+ """ -+ context = context or mounting.NotIsolatedActions(base_dir='/') -+ for workaround in api.consume(DNFWorkaround): -+ try: -+ api.show_message('Applying transaction workaround - {}'.format(workaround.display_name)) -+ if workaround.script_args: -+ cmd_str = '{script} {args}'.format( -+ script=workaround.script_path, -+ args=' '.join(workaround.script_args) -+ ) -+ else: -+ cmd_str = workaround.script_path -+ context.call(['/bin/bash', '-c', cmd_str]) -+ except (OSError, CalledProcessError) as e: -+ raise StopActorExecutionError( -+ message=('Failed to execute script to apply transaction workaround {display_name}.' -+ ' Message: {error}'.format(error=str(e), display_name=workaround.display_name)) -+ ) -+ -+ -+def install_initramdisk_requirements(packages, target_userspace_info, used_repos): -+ """ -+ Performs the installation of packages into the initram disk -+ """ -+ mount_binds = ['/:/installroot'] -+ with _prepare_transaction(used_repos=used_repos, target_userspace_info=target_userspace_info, -+ binds=mount_binds) as (context, target_repoids, _unused): -+ if int(get_target_major_version()) >= 9: -+ _rebuild_rpm_db(context) -+ repos_opt = [['--enablerepo', repo] for repo in target_repoids] -+ repos_opt = list(itertools.chain(*repos_opt)) -+ cmd = [ -+ 'dnf', -+ 'install', -+ '-y'] -+ if is_nogpgcheck_set(): -+ cmd.append('--nogpgcheck') -+ cmd += [ -+ '--setopt=module_platform_id=platform:el{}'.format(get_target_major_version()), -+ '--setopt=keepcache=1', -+ '--releasever', api.current_actor().configuration.version.target, -+ '--disablerepo', '*' -+ ] + repos_opt + list(packages) -+ if config.is_verbose(): -+ cmd.append('-v') -+ if rhsm.skip_rhsm(): -+ cmd += ['--disableplugin', 'subscription-manager'] -+ env = {} -+ if get_target_major_version() == '9': -+ # allow handling new RHEL 9 syscalls by systemd-nspawn -+ env = {'SYSTEMD_SECCOMP': '0'} -+ try: -+ context.call(cmd, env=env) -+ except CalledProcessError as e: -+ api.current_logger().error( -+ 'Cannot install packages in the target container required to build the upgrade initramfs.' -+ ) -+ _handle_transaction_err_msg(e, is_container=True) -+ -+ -+def perform_transaction_install(target_userspace_info, storage_info, used_repos, tasks, plugin_info, xfs_info): -+ """ -+ Performs the actual installation with the DNF rhel-upgrade plugin using the target userspace -+ """ -+ -+ stage = 'upgrade' -+ -+ # These bind mounts are performed by systemd-nspawn --bind parameters -+ bind_mounts = [ -+ '/:/installroot', -+ '/dev:/installroot/dev', -+ '/proc:/installroot/proc', -+ '/run/udev:/installroot/run/udev', -+ ] -+ -+ # we are bindmounting host's "/sys" to the intermediate "/hostsys" -+ # in the upgrade initramdisk to avoid cgroups tree layout clash -+ bind_mounts.append('/hostsys:/installroot/sys') -+ -+ already_mounted = {entry.split(':')[0] for entry in bind_mounts} -+ for entry in storage_info.fstab: -+ mp = entry.fs_file -+ if not os.path.isdir(mp): -+ continue -+ if mp not in already_mounted: -+ bind_mounts.append('{}:{}'.format(mp, os.path.join('/installroot', mp.lstrip('/')))) -+ -+ if os.path.ismount('/boot'): -+ bind_mounts.append('/boot:/installroot/boot') -+ -+ if os.path.ismount('/boot/efi'): -+ bind_mounts.append('/boot/efi:/installroot/boot/efi') -+ -+ with _prepare_transaction(used_repos=used_repos, -+ target_userspace_info=target_userspace_info, -+ binds=bind_mounts -+ ) as (context, target_repoids, _unused): -+ # the below nsenter command is important as we need to enter sysvipc namespace on the host so we can -+ # communicate with udev -+ cmd_prefix = ['nsenter', '--ipc=/installroot/proc/1/ns/ipc'] -+ -+ disable_plugins = [] -+ if plugin_info: -+ for info in plugin_info: -+ if stage in info.disable_in: -+ disable_plugins += [info.name] -+ -+ # we have to ensure the leapp packages will stay untouched -+ # Note: this is the most probably duplicate action - it should be already -+ # set like that, however seatbelt is a good thing. -+ dnfconfig.exclude_leapp_rpms(context, disable_plugins) -+ -+ if int(get_target_major_version()) >= 9: -+ _rebuild_rpm_db(context, root='/installroot') -+ _transaction( -+ context=context, stage='upgrade', target_repoids=target_repoids, plugin_info=plugin_info, -+ xfs_info=xfs_info, tasks=tasks, cmd_prefix=cmd_prefix -+ ) -+ -+ # we have to ensure the leapp packages will stay untouched even after the -+ # upgrade is fully finished (it cannot be done before the upgrade -+ # on the host as the config-manager plugin is available since rhel-8) -+ dnfconfig.exclude_leapp_rpms(mounting.NotIsolatedActions(base_dir='/'), disable_plugins=disable_plugins) -+ -+ -+@contextlib.contextmanager -+def _prepare_perform(used_repos, target_userspace_info, xfs_info, storage_info, target_iso=None): -+ # noqa: W0135; pylint: disable=bad-option-value,contextmanager-generator-missing-cleanup -+ # NOTE(pstodulk): the pylint check is not valid in this case - finally is covered -+ # implicitly -+ # noqa: W0135 -+ reserve_space = overlaygen.get_recommended_leapp_free_space(target_userspace_info.path) -+ with _prepare_transaction(used_repos=used_repos, -+ target_userspace_info=target_userspace_info -+ ) as (context, target_repoids, userspace_info): -+ with overlaygen.create_source_overlay(mounts_dir=userspace_info.mounts, scratch_dir=userspace_info.scratch, -+ xfs_info=xfs_info, storage_info=storage_info, -+ mount_target=os.path.join(context.base_dir, 'installroot'), -+ scratch_reserve=reserve_space) as overlay: -+ with mounting.mount_upgrade_iso_to_root_dir(target_userspace_info.path, target_iso): -+ yield context, overlay, target_repoids -+ -+ -+def perform_transaction_check(target_userspace_info, -+ used_repos, -+ tasks, -+ xfs_info, -+ storage_info, -+ plugin_info, -+ target_iso=None): -+ """ -+ Perform DNF transaction check using our plugin -+ """ -+ -+ stage = 'check' -+ -+ with _prepare_perform(used_repos=used_repos, target_userspace_info=target_userspace_info, xfs_info=xfs_info, -+ storage_info=storage_info, target_iso=target_iso) as (context, overlay, target_repoids): -+ apply_workarounds(overlay.nspawn()) -+ -+ disable_plugins = [] -+ if plugin_info: -+ for info in plugin_info: -+ if stage in info.disable_in: -+ disable_plugins += [info.name] -+ -+ dnfconfig.exclude_leapp_rpms(context, disable_plugins) -+ _transaction( -+ context=context, stage='check', target_repoids=target_repoids, plugin_info=plugin_info, xfs_info=xfs_info, -+ tasks=tasks -+ ) -+ -+ -+def perform_rpm_download(target_userspace_info, -+ used_repos, -+ tasks, -+ xfs_info, -+ storage_info, -+ plugin_info, -+ target_iso=None, -+ on_aws=False): -+ """ -+ Perform RPM download including the transaction test using dnf with our plugin -+ """ -+ -+ stage = 'download' -+ -+ with _prepare_perform(used_repos=used_repos, -+ target_userspace_info=target_userspace_info, -+ xfs_info=xfs_info, -+ storage_info=storage_info, -+ target_iso=target_iso) as (context, overlay, target_repoids): -+ -+ disable_plugins = [] -+ if plugin_info: -+ for info in plugin_info: -+ if stage in info.disable_in: -+ disable_plugins += [info.name] -+ -+ apply_workarounds(overlay.nspawn()) -+ dnfconfig.exclude_leapp_rpms(context, disable_plugins) -+ _transaction( -+ context=context, stage='download', target_repoids=target_repoids, plugin_info=plugin_info, tasks=tasks, -+ test=True, on_aws=on_aws, xfs_info=xfs_info -+ ) -+ -+ -+def perform_dry_run(target_userspace_info, -+ used_repos, -+ tasks, -+ xfs_info, -+ storage_info, -+ plugin_info, -+ target_iso=None, -+ on_aws=False): -+ """ -+ Perform the dnf transaction test / dry-run using only cached data. -+ """ -+ with _prepare_perform(used_repos=used_repos, -+ target_userspace_info=target_userspace_info, -+ xfs_info=xfs_info, -+ storage_info=storage_info, -+ target_iso=target_iso) as (context, overlay, target_repoids): -+ apply_workarounds(overlay.nspawn()) -+ _transaction( -+ context=context, stage='dry-run', target_repoids=target_repoids, plugin_info=plugin_info, tasks=tasks, -+ test=True, on_aws=on_aws, xfs_info=xfs_info -+ ) -diff --git a/repos/system_upgrade/common/libraries/dnfplugin.py b/repos/system_upgrade/common/libraries/dnfplugin.py -index b91bcbe7..713cc34b 100644 ---- a/repos/system_upgrade/common/libraries/dnfplugin.py -+++ b/repos/system_upgrade/common/libraries/dnfplugin.py -@@ -1,468 +1,118 @@ --import contextlib --import itertools --import json --import os --import re --import shutil -- --from leapp.exceptions import StopActorExecutionError --from leapp.libraries.common import dnfconfig, guards, mounting, overlaygen, rhsm, utils --from leapp.libraries.common.config.version import get_target_major_version, get_target_version --from leapp.libraries.common.gpg import is_nogpgcheck_set --from leapp.libraries.stdlib import api, CalledProcessError, config --from leapp.models import DNFWorkaround -- --DNF_PLUGIN_NAME = 'rhel_upgrade.py' --_DEDICATED_URL = 'https://access.redhat.com/solutions/7011704' -- -- --class _DnfPluginPathStr(str): -- _PATHS = { -- "9": os.path.join('/lib/python3.9/site-packages/dnf-plugins', DNF_PLUGIN_NAME), -- "10": os.path.join('/lib/python3.12/site-packages/dnf-plugins', DNF_PLUGIN_NAME), -- } -- -- def __init__(self): # noqa: W0231; pylint: disable=super-init-not-called -- self.data = "" -- -- def _feed(self): -- major = get_target_major_version() -- if major not in _DnfPluginPathStr._PATHS: -- raise KeyError('{} is not a supported target version of RHEL'.format(major)) -- self.data = _DnfPluginPathStr._PATHS[major] -- -- def __str__(self): -- self._feed() -- return str(self.data) -- -- def __repr__(self): -- self._feed() -- return repr(self.data) -- -- def lstrip(self, chars=None): -- self._feed() -- return self.data.lstrip(chars) -- -- --# Deprecated --DNF_PLUGIN_PATH = _DnfPluginPathStr() -- --DNF_PLUGIN_DATA_NAME = 'dnf-plugin-data.txt' --DNF_PLUGIN_DATA_PATH = os.path.join('/var/lib/leapp', DNF_PLUGIN_DATA_NAME) --DNF_PLUGIN_DATA_LOG_PATH = os.path.join('/var/log/leapp', DNF_PLUGIN_DATA_NAME) --DNF_DEBUG_DATA_PATH = '/var/log/leapp/dnf-debugdata/' -- -- -+""" -+DEPRECATED: This module has been moved to leapp.libraries.common.dnflibs.dnfplugin -+ -+This shim will be removed in a future version. Please update imports to: -+ from leapp.libraries.common.dnflibs import dnfplugin -+ # or -+ from leapp.libraries.common import dnflibs -+""" -+ -+from leapp.libraries.common.dnflibs import dnfplugin as _dnfplugin -+from leapp.utils.deprecation import deprecated -+ -+# Re-export constants (not deprecated) -+DNF_PLUGIN_NAME = _dnfplugin.DNF_PLUGIN_NAME -+DNF_PLUGIN_PATH = _dnfplugin.DNF_PLUGIN_PATH -+DNF_PLUGIN_DATA_NAME = _dnfplugin.DNF_PLUGIN_DATA_NAME -+DNF_PLUGIN_DATA_PATH = _dnfplugin.DNF_PLUGIN_DATA_PATH -+DNF_PLUGIN_DATA_LOG_PATH = _dnfplugin.DNF_PLUGIN_DATA_LOG_PATH -+DNF_DEBUG_DATA_PATH = _dnfplugin.DNF_DEBUG_DATA_PATH -+ -+ -+@deprecated(since='2026-03-10', message=( -+ 'This function has been moved to leapp.libraries.common.dnflibs.dnfplugin module. ' -+ 'Please update your imports to use the new location.' -+)) - def install(target_basedir): - """ - Installs our plugin to the DNF plugins. - """ -- try: -- shutil.copy2( -- api.get_file_path(DNF_PLUGIN_NAME), -- os.path.join(target_basedir, DNF_PLUGIN_PATH.lstrip('/'))) -- except EnvironmentError as e: -- api.current_logger().debug('Failed to install DNF plugin', exc_info=True) -- raise StopActorExecutionError( -- message='Failed to install DNF plugin. Error: {}'.format(str(e)) -- ) -- -- --def _rebuild_rpm_db(context, root=None): -- """ -- Convert rpmdb from BerkeleyDB to Sqlite -- """ -- base_cmd = ['rpmdb', '--rebuilddb'] -- cmd = base_cmd if not root else base_cmd + ['-r', root] -- context.call(cmd) -+ return _dnfplugin.install(target_basedir) - - -+@deprecated(since='2026-03-10', message=( -+ 'This function has been moved to leapp.libraries.common.dnflibs.dnfplugin module. ' -+ 'Please update your imports to use the new location.' -+)) - def build_plugin_data(target_repoids, debug, test, tasks, on_aws): - """ - Generates a dictionary with the DNF plugin data. - """ -- # get list of repo IDs of target repositories that should be used for upgrade -- data = { -- 'pkgs_info': { -- 'local_rpms': sorted(os.path.join('/installroot', pkg.lstrip('/')) for pkg in tasks.local_rpms), -- 'to_install': sorted(tasks.to_install), -- 'to_remove': sorted(tasks.to_remove), -- 'to_upgrade': sorted(tasks.to_upgrade), -- 'modules_to_enable': sorted(['{}:{}'.format(m.name, m.stream) for m in tasks.modules_to_enable]), -- }, -- 'dnf_conf': { -- 'allow_erasing': True, -- 'best': True, -- 'debugsolver': debug, -- 'disable_repos': True, -- 'enable_repos': target_repoids, -- 'gpgcheck': not is_nogpgcheck_set(), -- 'platform_id': 'platform:el{}'.format(get_target_major_version()), -- 'releasever': get_target_version(), -- 'installroot': '/installroot', -- 'test_flag': test -- }, -- 'rhui': { -- 'aws': { -- 'on_aws': on_aws, -- 'region': None, -- } -- } -- } -- return data -+ return _dnfplugin.build_plugin_data(target_repoids, debug, test, tasks, on_aws) - - -+@deprecated(since='2026-03-10', message=( -+ 'This function has been moved to leapp.libraries.common.dnflibs.dnfplugin module. ' -+ 'Please update your imports to use the new location.' -+)) - def create_config(context, target_repoids, debug, test, tasks, on_aws=False): - """ - Creates the configuration data file for our DNF plugin. - """ -- context.makedirs(os.path.dirname(DNF_PLUGIN_DATA_PATH), exists_ok=True) -- with context.open(DNF_PLUGIN_DATA_PATH, 'w+') as f: -- config_data = build_plugin_data( -- target_repoids=target_repoids, debug=debug, test=test, tasks=tasks, on_aws=on_aws -- ) -- json.dump(config_data, f, sort_keys=True, indent=2) -+ return _dnfplugin.create_config(context, target_repoids, debug, test, tasks, on_aws) - - -+@deprecated(since='2026-03-10', message=( -+ 'This function has been moved to leapp.libraries.common.dnflibs.dnfplugin module. ' -+ 'Please update your imports to use the new location.' -+)) - def backup_config(context): - """ - Backs up the configuration data used for the plugin. - """ -- context.copy_from(DNF_PLUGIN_DATA_PATH, DNF_PLUGIN_DATA_LOG_PATH) -+ return _dnfplugin.backup_config(context) - - -+@deprecated(since='2026-03-10', message=( -+ 'This function has been moved to leapp.libraries.common.dnflibs.dnfplugin module. ' -+ 'Please update your imports to use the new location.' -+)) - def backup_debug_data(context): - """ - Performs the backup of DNF debug data - """ -- if config.is_debug(): -- # The debugdata is a folder generated by dnf when using the --debugsolver dnf option. We switch on the -- # debug_solver dnf config parameter in our rhel-upgrade dnf plugin when LEAPP_DEBUG env var set to 1. -- try: -- context.copytree_from('/debugdata', DNF_DEBUG_DATA_PATH) -- except OSError as e: -- api.current_logger().warning('Failed to copy debugdata. Message: {}'.format(str(e)), exc_info=True) -- -- --def _handle_transaction_err_msg_old(stage, xfs_info, err): -- # NOTE(pstodulk): This is going to be removed in future! -- message = 'DNF execution failed with non zero exit code.' -- details = {'STDOUT': err.stdout, 'STDERR': err.stderr} -- -- if 'more space needed on the' in err.stderr and stage != 'upgrade': -- # Disk Requirements: -- # At least more space needed on the filesystem. -- # -- article_section = 'Generic case' -- if xfs_info.present and xfs_info.without_ftype: -- article_section = 'XFS ftype=0 case' -- -- message = ('There is not enough space on the file system hosting /var/lib/leapp directory ' -- 'to extract the packages.') -- details = {'hint': "Please follow the instructions in the '{}' section of the article at: " -- "link: https://access.redhat.com/solutions/5057391".format(article_section)} -- -- raise StopActorExecutionError(message=message, details=details) -- -- --def _handle_transaction_err_msg(err, is_container=False): -- NO_SPACE_STR = 'more space needed on the' -- if NO_SPACE_STR not in err.stderr: -- message = 'DNF execution failed with non zero exit code.' -- # if there was a problem reaching repos and proxy is configured in DNF/YUM configs, the -- # proxy is likely the problem. -- # NOTE(mmatuska): We can't consistently detect there was a problem reaching some repos, -- # because it isn't clear what are all the possible DNF error messages we can encounter, -- # such as: "Failed to synchronize cache for repo ..." or "Errors during downloading -- # metadata for # repository" or "No more mirrors to try - All mirrors were already tried -- # without success" -- # NOTE(mmatuska): We could check PkgManagerInfo to detect if proxy is indeed configured, -- # however it would be pretty ugly to pass it all the way down here -- proxy_hint = ( -- "If there was a problem reaching remote content (see stderr output) and proxy is " -- "configured in the YUM/DNF configuration file, the proxy configuration is likely " -- "causing this error. " -- "Make sure the proxy is properly configured in /etc/dnf/dnf.conf. " -- "It's also possible the proxy settings in the DNF configuration file are " -- "incompatible with the target system. A compatible configuration can be " -- "placed in /etc/leapp/files/dnf.conf which, if present, it will be used during " -- "some parts of the upgrade instead of original /etc/dnf/dnf.conf. " -- "In such case the configuration will also be applied to the target system. " -- "Note that /etc/dnf/dnf.conf needs to be still configured correctly " -- "for your current system to pass the early phases of the upgrade process." -- ) -- details = {'STDOUT': err.stdout, 'STDERR': err.stderr, 'hint': proxy_hint} -- raise StopActorExecutionError(message=message, details=details) -- -- # Disk Requirements: -- # At least more space needed on the filesystem. -- # -- missing_space = [line.strip() for line in err.stderr.split('\n') if NO_SPACE_STR in line] -- if is_container: -- size_str = re.match(r'At least (.*) more space needed', missing_space[0]).group(1) -- message = 'There is not enough space on the file system hosting /var/lib/leapp.' -- hint = ( -- 'Increase the free space on the filesystem hosting' -- ' /var/lib/leapp by {} at minimum. It is suggested to provide' -- ' reasonably more space to be able to perform all planned actions' -- ' (e.g. when 200MB is missing, add 1700MB or more).\n\n' -- 'It is also a good practice to create dedicated partition' -- ' for /var/lib/leapp when more space is needed, which can be' -- ' dropped after the system upgrade is fully completed' -- ' For more info, see: {}' -- .format(size_str, _DEDICATED_URL) -- ) -- # we do not want to confuse customers by the orig msg speaking about -- # missing space on '/'. Skip the Disk Requirements section. -- # The information is part of the hint. -- details = {'hint': hint} -- else: -- message = 'There is not enough space on some file systems to perform the upgrade transaction.' -- hint = ( -- 'Increase the free space on listed filesystems. Presented values' -- ' are required minimum calculated by RPM and it is suggested to' -- ' provide reasonably more free space (e.g. when 200 MB is missing' -- ' on /usr, add 1200MB or more).' -- ) -- details = {'hint': hint, 'Disk Requirements': '\n'.join(missing_space)} -- -- raise StopActorExecutionError(message=message, details=details) -- -- --def _transaction(context, stage, target_repoids, tasks, plugin_info, xfs_info, -- test=False, cmd_prefix=None, on_aws=False): -- """ -- Perform the actual DNF rpm download via our DNF plugin -- """ -- -- # we do not want -- if stage not in ['dry-run', 'upgrade']: -- create_config( -- context=context, -- target_repoids=target_repoids, -- debug=config.is_debug(), -- test=test, tasks=tasks, -- on_aws=on_aws -- ) -- backup_config(context=context) -- -- # FIXME: rhsm -- with guards.guarded_execution(guards.connection_guard(), guards.space_guard()): -- cmd_prefix = cmd_prefix or [] -- common_params = [] -- if config.is_verbose(): -- common_params.append('-v') -- if rhsm.skip_rhsm(): -- common_params += ['--disableplugin', 'subscription-manager'] -- if plugin_info: -- for info in plugin_info: -- if stage in info.disable_in: -- common_params += ['--disableplugin', info.name] -- env = {} -- if get_target_major_version() == '9': -- # allow handling new RHEL 9 syscalls by systemd-nspawn -- env = {'SYSTEMD_SECCOMP': '0'} -- -- if tasks.modules_to_reset: -- # We shall only reset modules that are not going to be enabled -- # This will make sure it is so -- modules_to_reset = {(module.name, module.stream) for module in tasks.modules_to_reset} -- modules_to_enable = {(module.name, module.stream) for module in tasks.modules_to_enable} -- module_reset_list = [module[0] for module in modules_to_reset - modules_to_enable] -- # Perform module reset -- cmd = ['/usr/bin/dnf', 'module', 'reset', '--enabled', ] + module_reset_list -- cmd += ['--disablerepo', '*', '-y', '--installroot', '/installroot'] -- try: -- context.call( -- cmd=cmd_prefix + cmd + common_params, -- callback_raw=utils.logging_handler, -- env=env -- ) -- except (CalledProcessError, OSError): -- api.current_logger().debug('Failed to reset modules via dnf with an error. Ignoring.', -- exc_info=True) -- -- cmd = [ -- '/usr/bin/dnf', -- 'rhel-upgrade', -- stage, -- DNF_PLUGIN_DATA_PATH -- ] -- try: -- context.call( -- cmd=cmd_prefix + cmd + common_params, -- callback_raw=utils.logging_handler, -- env=env -- ) -- except OSError as e: -- api.current_logger().error('Could not call dnf command: Message: %s', str(e), exc_info=True) -- raise StopActorExecutionError( -- message='Failed to execute dnf. Reason: {}'.format(str(e)) -- ) -- except CalledProcessError as e: -- api.current_logger().error('Cannot calculate, check, test, or perform the upgrade transaction.') -- _handle_transaction_err_msg(e, is_container=False) -- finally: -- if stage == 'check': -- backup_debug_data(context=context) -- -- --@contextlib.contextmanager --def _prepare_transaction(used_repos, target_userspace_info, binds=()): -- """ Creates the transaction environment needed for the target userspace DNF execution """ -- target_repoids = set() -- for message in used_repos: -- target_repoids.update([repo.repoid for repo in message.repos]) -- with mounting.NspawnActions(base_dir=target_userspace_info.path, binds=binds) as context: -- yield context, list(target_repoids), target_userspace_info -+ return _dnfplugin.backup_debug_data(context) - - -+@deprecated(since='2026-03-10', message=( -+ 'This function has been moved to leapp.libraries.common.dnflibs.dnfplugin module. ' -+ 'Please update your imports to use the new location.' -+)) - def apply_workarounds(context=None): - """ - Apply registered workarounds in the given context environment - """ -- context = context or mounting.NotIsolatedActions(base_dir='/') -- for workaround in api.consume(DNFWorkaround): -- try: -- api.show_message('Applying transaction workaround - {}'.format(workaround.display_name)) -- if workaround.script_args: -- cmd_str = '{script} {args}'.format( -- script=workaround.script_path, -- args=' '.join(workaround.script_args) -- ) -- else: -- cmd_str = workaround.script_path -- context.call(['/bin/bash', '-c', cmd_str]) -- except (OSError, CalledProcessError) as e: -- raise StopActorExecutionError( -- message=('Failed to execute script to apply transaction workaround {display_name}.' -- ' Message: {error}'.format(error=str(e), display_name=workaround.display_name)) -- ) -+ return _dnfplugin.apply_workarounds(context) - - -+@deprecated(since='2026-03-10', message=( -+ 'This function has been moved to leapp.libraries.common.dnflibs.dnfplugin module. ' -+ 'Please update your imports to use the new location.' -+)) - def install_initramdisk_requirements(packages, target_userspace_info, used_repos): - """ - Performs the installation of packages into the initram disk - """ -- mount_binds = ['/:/installroot'] -- with _prepare_transaction(used_repos=used_repos, target_userspace_info=target_userspace_info, -- binds=mount_binds) as (context, target_repoids, _unused): -- if int(get_target_major_version()) >= 9: -- _rebuild_rpm_db(context) -- repos_opt = [['--enablerepo', repo] for repo in target_repoids] -- repos_opt = list(itertools.chain(*repos_opt)) -- cmd = [ -- 'dnf', -- 'install', -- '-y'] -- if is_nogpgcheck_set(): -- cmd.append('--nogpgcheck') -- cmd += [ -- '--setopt=module_platform_id=platform:el{}'.format(get_target_major_version()), -- '--setopt=keepcache=1', -- '--releasever', api.current_actor().configuration.version.target, -- '--disablerepo', '*' -- ] + repos_opt + list(packages) -- if config.is_verbose(): -- cmd.append('-v') -- if rhsm.skip_rhsm(): -- cmd += ['--disableplugin', 'subscription-manager'] -- env = {} -- if get_target_major_version() == '9': -- # allow handling new RHEL 9 syscalls by systemd-nspawn -- env = {'SYSTEMD_SECCOMP': '0'} -- try: -- context.call(cmd, env=env) -- except CalledProcessError as e: -- api.current_logger().error( -- 'Cannot install packages in the target container required to build the upgrade initramfs.' -- ) -- _handle_transaction_err_msg(e, is_container=True) -+ return _dnfplugin.install_initramdisk_requirements(packages, target_userspace_info, used_repos) - - -+@deprecated(since='2026-03-10', message=( -+ 'This function has been moved to leapp.libraries.common.dnflibs.dnfplugin module. ' -+ 'Please update your imports to use the new location.' -+)) - def perform_transaction_install(target_userspace_info, storage_info, used_repos, tasks, plugin_info, xfs_info): - """ - Performs the actual installation with the DNF rhel-upgrade plugin using the target userspace - """ -- -- stage = 'upgrade' -- -- # These bind mounts are performed by systemd-nspawn --bind parameters -- bind_mounts = [ -- '/:/installroot', -- '/dev:/installroot/dev', -- '/proc:/installroot/proc', -- '/run/udev:/installroot/run/udev', -- ] -- -- # we are bindmounting host's "/sys" to the intermediate "/hostsys" -- # in the upgrade initramdisk to avoid cgroups tree layout clash -- bind_mounts.append('/hostsys:/installroot/sys') -- -- already_mounted = {entry.split(':')[0] for entry in bind_mounts} -- for entry in storage_info.fstab: -- mp = entry.fs_file -- if not os.path.isdir(mp): -- continue -- if mp not in already_mounted: -- bind_mounts.append('{}:{}'.format(mp, os.path.join('/installroot', mp.lstrip('/')))) -- -- if os.path.ismount('/boot'): -- bind_mounts.append('/boot:/installroot/boot') -- -- if os.path.ismount('/boot/efi'): -- bind_mounts.append('/boot/efi:/installroot/boot/efi') -- -- with _prepare_transaction(used_repos=used_repos, -- target_userspace_info=target_userspace_info, -- binds=bind_mounts -- ) as (context, target_repoids, _unused): -- # the below nsenter command is important as we need to enter sysvipc namespace on the host so we can -- # communicate with udev -- cmd_prefix = ['nsenter', '--ipc=/installroot/proc/1/ns/ipc'] -- -- disable_plugins = [] -- if plugin_info: -- for info in plugin_info: -- if stage in info.disable_in: -- disable_plugins += [info.name] -- -- # we have to ensure the leapp packages will stay untouched -- # Note: this is the most probably duplicate action - it should be already -- # set like that, however seatbelt is a good thing. -- dnfconfig.exclude_leapp_rpms(context, disable_plugins) -- -- if int(get_target_major_version()) >= 9: -- _rebuild_rpm_db(context, root='/installroot') -- _transaction( -- context=context, stage='upgrade', target_repoids=target_repoids, plugin_info=plugin_info, -- xfs_info=xfs_info, tasks=tasks, cmd_prefix=cmd_prefix -- ) -- -- # we have to ensure the leapp packages will stay untouched even after the -- # upgrade is fully finished (it cannot be done before the upgrade -- # on the host as the config-manager plugin is available since rhel-8) -- dnfconfig.exclude_leapp_rpms(mounting.NotIsolatedActions(base_dir='/'), disable_plugins=disable_plugins) -- -- --@contextlib.contextmanager --def _prepare_perform(used_repos, target_userspace_info, xfs_info, storage_info, target_iso=None): -- # noqa: W0135; pylint: disable=bad-option-value,contextmanager-generator-missing-cleanup -- # NOTE(pstodulk): the pylint check is not valid in this case - finally is covered -- # implicitly -- # noqa: W0135 -- reserve_space = overlaygen.get_recommended_leapp_free_space(target_userspace_info.path) -- with _prepare_transaction(used_repos=used_repos, -- target_userspace_info=target_userspace_info -- ) as (context, target_repoids, userspace_info): -- with overlaygen.create_source_overlay(mounts_dir=userspace_info.mounts, scratch_dir=userspace_info.scratch, -- xfs_info=xfs_info, storage_info=storage_info, -- mount_target=os.path.join(context.base_dir, 'installroot'), -- scratch_reserve=reserve_space) as overlay: -- with mounting.mount_upgrade_iso_to_root_dir(target_userspace_info.path, target_iso): -- yield context, overlay, target_repoids -+ return _dnfplugin.perform_transaction_install( -+ target_userspace_info, storage_info, used_repos, tasks, plugin_info, xfs_info -+ ) - - -+@deprecated(since='2026-03-10', message=( -+ 'This function has been moved to leapp.libraries.common.dnflibs.dnfplugin module. ' -+ 'Please update your imports to use the new location.' -+)) - def perform_transaction_check(target_userspace_info, - used_repos, - tasks, -@@ -473,26 +123,15 @@ def perform_transaction_check(target_userspace_info, - """ - Perform DNF transaction check using our plugin - """ -- -- stage = 'check' -- -- with _prepare_perform(used_repos=used_repos, target_userspace_info=target_userspace_info, xfs_info=xfs_info, -- storage_info=storage_info, target_iso=target_iso) as (context, overlay, target_repoids): -- apply_workarounds(overlay.nspawn()) -- -- disable_plugins = [] -- if plugin_info: -- for info in plugin_info: -- if stage in info.disable_in: -- disable_plugins += [info.name] -- -- dnfconfig.exclude_leapp_rpms(context, disable_plugins) -- _transaction( -- context=context, stage='check', target_repoids=target_repoids, plugin_info=plugin_info, xfs_info=xfs_info, -- tasks=tasks -- ) -+ return _dnfplugin.perform_transaction_check( -+ target_userspace_info, used_repos, tasks, xfs_info, storage_info, plugin_info, target_iso -+ ) - - -+@deprecated(since='2026-03-10', message=( -+ 'This function has been moved to leapp.libraries.common.dnflibs.dnfplugin module. ' -+ 'Please update your imports to use the new location.' -+)) - def perform_rpm_download(target_userspace_info, - used_repos, - tasks, -@@ -504,29 +143,15 @@ def perform_rpm_download(target_userspace_info, - """ - Perform RPM download including the transaction test using dnf with our plugin - """ -- -- stage = 'download' -- -- with _prepare_perform(used_repos=used_repos, -- target_userspace_info=target_userspace_info, -- xfs_info=xfs_info, -- storage_info=storage_info, -- target_iso=target_iso) as (context, overlay, target_repoids): -- -- disable_plugins = [] -- if plugin_info: -- for info in plugin_info: -- if stage in info.disable_in: -- disable_plugins += [info.name] -- -- apply_workarounds(overlay.nspawn()) -- dnfconfig.exclude_leapp_rpms(context, disable_plugins) -- _transaction( -- context=context, stage='download', target_repoids=target_repoids, plugin_info=plugin_info, tasks=tasks, -- test=True, on_aws=on_aws, xfs_info=xfs_info -- ) -+ return _dnfplugin.perform_rpm_download( -+ target_userspace_info, used_repos, tasks, xfs_info, storage_info, plugin_info, target_iso, on_aws -+ ) - - -+@deprecated(since='2026-03-10', message=( -+ 'This function has been moved to leapp.libraries.common.dnflibs.dnfplugin module. ' -+ 'Please update your imports to use the new location.' -+)) - def perform_dry_run(target_userspace_info, - used_repos, - tasks, -@@ -538,13 +163,6 @@ def perform_dry_run(target_userspace_info, - """ - Perform the dnf transaction test / dry-run using only cached data. - """ -- with _prepare_perform(used_repos=used_repos, -- target_userspace_info=target_userspace_info, -- xfs_info=xfs_info, -- storage_info=storage_info, -- target_iso=target_iso) as (context, overlay, target_repoids): -- apply_workarounds(overlay.nspawn()) -- _transaction( -- context=context, stage='dry-run', target_repoids=target_repoids, plugin_info=plugin_info, tasks=tasks, -- test=True, on_aws=on_aws, xfs_info=xfs_info -- ) -+ return _dnfplugin.perform_dry_run( -+ target_userspace_info, used_repos, tasks, xfs_info, storage_info, plugin_info, target_iso, on_aws -+ ) -diff --git a/repos/system_upgrade/common/libraries/module.py b/repos/system_upgrade/common/libraries/module.py -index d85e4659..46b87a75 100644 ---- a/repos/system_upgrade/common/libraries/module.py -+++ b/repos/system_upgrade/common/libraries/module.py -@@ -1,105 +1,44 @@ --import warnings -+""" -+DEPRECATED: This module has been moved to leapp.libraries.common.dnflibs.dnfmodule - --from leapp.exceptions import StopActorExecutionError --from leapp.libraries.common.config.version import get_source_major_version -+This shim will be removed in a future version. Please update imports to: -+ from leapp.libraries.common.dnflibs import dnfmodule -+ # or -+ from leapp.libraries.common import dnflibs -+""" - --try: -- import dnf --except ImportError: -- dnf = None -- warnings.warn('Could not import the `dnf` python module.', ImportWarning) -- --try: -- import hawkey --except ImportError: -- hawkey = None -- warnings.warn('Could not import the `hawkey` python module.', ImportWarning) -- -- --def _create_or_get_dnf_base(base=None): -- if not base: -- # The DNF command reads /etc/yum/vars/releasever, but the DNF library does not. It parses redhat-release -- # package to retrieve system's major version which it then uses as $releasever. However, some systems might -- # have repositories only for the exact system version (including the minor number). In a case when -- # /etc/yum/vars/releasever is present, read its contents so that we can access repositores on such systems. -- conf = dnf.conf.Conf() -- -- # preload releasever from what we know, this will be our fallback -- conf.substitutions['releasever'] = get_source_major_version() -- -- # load all substitutions from etc -- conf.substitutions.update_from_etc('/') -- -- base = dnf.Base(conf=conf) -- base.conf.read() -- base.init_plugins() -- base.read_all_repos() -- # configure plugins after the repositories are loaded -- # e.g. the amazon-id plugin requires loaded repositories -- # for the proper configuration. -- base.configure_plugins() -- -- try: -- base.fill_sack() -- except dnf.exceptions.RepoError as e: -- err_msg = str(e) -- repoid = err_msg.split('repo:')[-1].strip() if 'repo:' in err_msg else 'unknown repo' -- repoid = repoid.strip('"').strip("'").replace('\\"', '') -- raise StopActorExecutionError( -- message='DNF failed to load repositories: {}'.format(str(e)), -- details={ -- 'hint': 'Ensure the {} repository definition is correct or remove it ' -- 'if the repository is not needed anymore.'.format(repoid) -- } -- ) -- return base -+from leapp.libraries.common.dnflibs import dnfmodule as _dnfmodule -+from leapp.utils.deprecation import deprecated - - -+@deprecated(since='2026-03-10', message=( -+ 'This function has been moved to leapp.libraries.common.dnflibs.dnfmodule module. ' -+ 'Please update your imports to use the new location.' -+)) - def get_modules(base=None): - """ - Return info about all module streams as a list of libdnf.module.ModulePackage objects. - """ -- if not dnf: -- return [] -- base = _create_or_get_dnf_base(base) -- -- module_base = dnf.module.module_base.ModuleBase(base) -- # this method is absent on RHEL 7, in which case there are no modules anyway -- if not hasattr(module_base, 'get_modules'): -- return [] -- return module_base.get_modules('*')[0] -+ return _dnfmodule.get_modules(base) - - -+@deprecated(since='2026-03-10', message=( -+ 'This function has been moved to leapp.libraries.common.dnflibs.dnfmodule module. ' -+ 'Please update your imports to use the new location.' -+)) - def get_enabled_modules(): - """ - Return currently enabled module streams as a list of libdnf.module.ModulePackage objects. - """ -- if not dnf: -- return [] -- -- base = _create_or_get_dnf_base() -- modules = get_modules(base) -- -- # if modules are not supported (RHEL 7), base.sack._moduleContainer won't exist -- # luckily in that case modules are empty and the element won't even be accessed -- return [m for m in modules if base.sack._moduleContainer.isEnabled(m)] -+ return _dnfmodule.get_enabled_modules() - - -+@deprecated(since='2026-03-10', message=( -+ 'This function has been moved to leapp.libraries.common.dnflibs.dnfmodule module. ' -+ 'Please update your imports to use the new location.' -+)) - def map_installed_rpms_to_modules(): - """ - Map installed modular packages to the module streams they come from. - """ -- modules = get_modules() -- # empty on RHEL 7 because of no modules -- if not modules: -- return {} -- # create a reverse mapping from the RPMS to module streams -- # key: tuple of 4 strings representing a NVRA (name, version, release, arch) of an RPM -- # value: tuple of 2 strings representing a module and its stream -- rpm_streams = {} -- for module in modules: -- for rpm in module.getArtifacts(): -- nevra = hawkey.split_nevra(rpm) -- rpm_key = (nevra.name, nevra.version, nevra.release, nevra.arch) -- rpm_streams[rpm_key] = (module.getName(), module.getStream()) -- return rpm_streams -+ return _dnfmodule.map_installed_rpms_to_modules() --- -2.54.0 - diff --git a/0067-Update-imports-to-use-new-dnflibs-modules.patch b/0067-Update-imports-to-use-new-dnflibs-modules.patch deleted file mode 100644 index 1cda50a..0000000 --- a/0067-Update-imports-to-use-new-dnflibs-modules.patch +++ /dev/null @@ -1,256 +0,0 @@ -From f60f6af718e6d8e1459022374be836b2850add03 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Tue, 10 Mar 2026 10:49:38 +0000 -Subject: [PATCH 067/105] Update imports to use new dnflibs modules - -Update all actors and tests to import DNF libraries from the new -dnflibs package location instead of the deprecated root paths. - -Changes: -- Update dnfplugin imports to use dnflibs.dnfplugin -- Update module imports to use dnflibs.dnfmodule -- Affects 9 actors and 3 test files - -This completes the migration to the new library structure, ensuring -all internal code uses the new import paths while maintaining backward -compatibility through deprecation shims. - -Assisted-by: Claude Sonnet 4.5 -Signed-off-by: Petr Stodulka ---- - docs/source/libraries-and-api/deprecations-list.md | 7 +++---- - .../common/actors/applytransactionworkarounds/actor.py | 2 +- - .../tests/unit_test_applytransactionworkarounds.py | 2 +- - repos/system_upgrade/common/actors/dnfdryrun/actor.py | 2 +- - .../common/actors/dnfpackagedownload/actor.py | 2 +- - .../common/actors/dnftransactioncheck/actor.py | 2 +- - .../common/actors/dnfupgradetransaction/actor.py | 2 +- - .../common/actors/getenabledmodules/actor.py | 2 +- - .../libraries/upgradeinitramfsgenerator.py | 3 ++- - .../common/actors/rpmscanner/libraries/rpmscanner.py | 4 ++-- - .../common/actors/rpmscanner/tests/test_rpmscanner.py | 10 +++++----- - .../targetuserspacecreator/libraries/userspacegen.py | 3 ++- - .../common/libraries/tests/test_dnfplugin.py | 2 +- - 13 files changed, 22 insertions(+), 21 deletions(-) - -diff --git a/docs/source/libraries-and-api/deprecations-list.md b/docs/source/libraries-and-api/deprecations-list.md -index aa20f874..301de558 100644 ---- a/docs/source/libraries-and-api/deprecations-list.md -+++ b/docs/source/libraries-and-api/deprecations-list.md -@@ -19,10 +19,9 @@ Only the versions in which a deprecation has been made are listed. - - Models: - - **`RenamedInterfaces`** - Information provided in this message is not always complete and it's not used since the `net.naming-scheme` kernel command line argument is set during the upgrade. - - Shared libraries -- - DNF-related libraries have been reorganized into the `leapp.libraries.common.dnflibs` package. The following modules have been moved: -- - **`leapp.libraries.common.dnfconfig`** - Moved to `leapp.libraries.common.dnflibs.dnfconfig`. All public functions (`exclude_leapp_rpms`) are deprecated in the old location. -- - **`leapp.libraries.common.dnfplugin`** - Moved to `leapp.libraries.common.dnflibs.dnfplugin`. All public functions (`install`, `build_plugin_data`, `create_config`, `backup_config`, `backup_debug_data`, `apply_workarounds`, `install_initramdisk_requirements`, `perform_transaction_install`, `perform_transaction_check`, `perform_rpm_download`, `perform_dry_run`) and constants (`DNF_PLUGIN_NAME`, `DNF_PLUGIN_PATH`, `DNF_PLUGIN_DATA_NAME`, `DNF_PLUGIN_DATA_PATH`, `DNF_PLUGIN_DATA_LOG_PATH`, `DNF_DEBUG_DATA_PATH`) are available in the new location. -- - **`leapp.libraries.common.module`** - Moved to `leapp.libraries.common.dnflibs.dnfmodule` (renamed for clarity). All public functions (`get_modules`, `get_enabled_modules`, `map_installed_rpms_to_modules`) are deprecated in the old location. -+ - **`leapp.libraries.common.dnfconfig`** - Moved to `leapp.libraries.common.dnflibs.dnfconfig`. Original library is deprecated. -+ - **`leapp.libraries.common.dnfplugin`** - Moved to `leapp.libraries.common.dnflibs.dnfplugin`. Original library is deprecated. -+ - **`leapp.libraries.common.module`** - Replaced by `leapp.libraries.common.dnflibs.dnfmodule` (renamed for clarity). Original library is deprecated. - - - ## v0.24.0 (till September 2026) -diff --git a/repos/system_upgrade/common/actors/applytransactionworkarounds/actor.py b/repos/system_upgrade/common/actors/applytransactionworkarounds/actor.py -index f1514dd1..00fb4c4c 100644 ---- a/repos/system_upgrade/common/actors/applytransactionworkarounds/actor.py -+++ b/repos/system_upgrade/common/actors/applytransactionworkarounds/actor.py -@@ -1,5 +1,5 @@ - from leapp.actors import Actor --from leapp.libraries.common import dnfplugin -+from leapp.libraries.common.dnflibs import dnfplugin - from leapp.models import DNFWorkaround - from leapp.tags import IPUWorkflowTag, PreparationPhaseTag - -diff --git a/repos/system_upgrade/common/actors/applytransactionworkarounds/tests/unit_test_applytransactionworkarounds.py b/repos/system_upgrade/common/actors/applytransactionworkarounds/tests/unit_test_applytransactionworkarounds.py -index 96b8094f..92d316b8 100644 ---- a/repos/system_upgrade/common/actors/applytransactionworkarounds/tests/unit_test_applytransactionworkarounds.py -+++ b/repos/system_upgrade/common/actors/applytransactionworkarounds/tests/unit_test_applytransactionworkarounds.py -@@ -1,6 +1,6 @@ - import os - --from leapp.libraries.common.dnfplugin import api, apply_workarounds, mounting -+from leapp.libraries.common.dnflibs.dnfplugin import api, apply_workarounds, mounting - from leapp.libraries.common.testutils import CurrentActorMocked - from leapp.models import DNFWorkaround - -diff --git a/repos/system_upgrade/common/actors/dnfdryrun/actor.py b/repos/system_upgrade/common/actors/dnfdryrun/actor.py -index bc3267b4..1f1d6bd1 100644 ---- a/repos/system_upgrade/common/actors/dnfdryrun/actor.py -+++ b/repos/system_upgrade/common/actors/dnfdryrun/actor.py -@@ -1,5 +1,5 @@ - from leapp.actors import Actor --from leapp.libraries.common import dnfplugin -+from leapp.libraries.common.dnflibs import dnfplugin - from leapp.models import ( - BootContent, - DNFPluginTask, -diff --git a/repos/system_upgrade/common/actors/dnfpackagedownload/actor.py b/repos/system_upgrade/common/actors/dnfpackagedownload/actor.py -index b54f5627..796b21d4 100644 ---- a/repos/system_upgrade/common/actors/dnfpackagedownload/actor.py -+++ b/repos/system_upgrade/common/actors/dnfpackagedownload/actor.py -@@ -1,5 +1,5 @@ - from leapp.actors import Actor --from leapp.libraries.common import dnfplugin -+from leapp.libraries.common.dnflibs import dnfplugin - from leapp.models import ( - DNFPluginTask, - DNFWorkaround, -diff --git a/repos/system_upgrade/common/actors/dnftransactioncheck/actor.py b/repos/system_upgrade/common/actors/dnftransactioncheck/actor.py -index b545d1ce..49f9bfe7 100644 ---- a/repos/system_upgrade/common/actors/dnftransactioncheck/actor.py -+++ b/repos/system_upgrade/common/actors/dnftransactioncheck/actor.py -@@ -1,5 +1,5 @@ - from leapp.actors import Actor --from leapp.libraries.common import dnfplugin -+from leapp.libraries.common.dnflibs import dnfplugin - from leapp.models import ( - DNFPluginTask, - DNFWorkaround, -diff --git a/repos/system_upgrade/common/actors/dnfupgradetransaction/actor.py b/repos/system_upgrade/common/actors/dnfupgradetransaction/actor.py -index 2e069296..7bf8ab23 100644 ---- a/repos/system_upgrade/common/actors/dnfupgradetransaction/actor.py -+++ b/repos/system_upgrade/common/actors/dnfupgradetransaction/actor.py -@@ -1,7 +1,7 @@ - import shutil - - from leapp.actors import Actor --from leapp.libraries.common import dnfplugin -+from leapp.libraries.common.dnflibs import dnfplugin - from leapp.libraries.stdlib import run - from leapp.models import ( - DNFPluginTask, -diff --git a/repos/system_upgrade/common/actors/getenabledmodules/actor.py b/repos/system_upgrade/common/actors/getenabledmodules/actor.py -index 71cefef6..38f458ef 100644 ---- a/repos/system_upgrade/common/actors/getenabledmodules/actor.py -+++ b/repos/system_upgrade/common/actors/getenabledmodules/actor.py -@@ -1,5 +1,5 @@ - from leapp.actors import Actor --from leapp.libraries.common.module import get_enabled_modules -+from leapp.libraries.common.dnflibs.dnfmodule import get_enabled_modules - from leapp.models import EnabledModules, Module - from leapp.tags import FactsPhaseTag, IPUWorkflowTag - -diff --git a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py -index 1a0dccd8..aec1debf 100644 ---- a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py -+++ b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py -@@ -4,8 +4,9 @@ import shutil - from collections import namedtuple - - from leapp.exceptions import StopActorExecutionError --from leapp.libraries.common import dnfplugin, mounting -+from leapp.libraries.common import mounting - from leapp.libraries.common.config.version import get_target_major_version -+from leapp.libraries.common.dnflibs import dnfplugin - from leapp.libraries.stdlib import api, CalledProcessError - from leapp.models import RequiredUpgradeInitramPackages # deprecated - from leapp.models import UpgradeDracutModule # deprecated -diff --git a/repos/system_upgrade/common/actors/rpmscanner/libraries/rpmscanner.py b/repos/system_upgrade/common/actors/rpmscanner/libraries/rpmscanner.py -index 2a2ee03e..ebf17e19 100644 ---- a/repos/system_upgrade/common/actors/rpmscanner/libraries/rpmscanner.py -+++ b/repos/system_upgrade/common/actors/rpmscanner/libraries/rpmscanner.py -@@ -1,8 +1,8 @@ - import warnings - - from leapp.exceptions import StopActorExecutionError --from leapp.libraries.common import module as module_lib - from leapp.libraries.common import rpms -+from leapp.libraries.common.dnflibs import dnfmodule - from leapp.libraries.stdlib import api - from leapp.models import InstalledRPM, RPM - -@@ -100,7 +100,7 @@ def map_modular_rpms_to_modules(): - """ - Map modular packages to the module streams they come from. - """ -- modules = module_lib.get_modules() -+ modules = dnfmodule.get_modules() - # empty on RHEL 7 because of no modules - if not modules: - return {} -diff --git a/repos/system_upgrade/common/actors/rpmscanner/tests/test_rpmscanner.py b/repos/system_upgrade/common/actors/rpmscanner/tests/test_rpmscanner.py -index 151a1b2b..9955aff7 100644 ---- a/repos/system_upgrade/common/actors/rpmscanner/tests/test_rpmscanner.py -+++ b/repos/system_upgrade/common/actors/rpmscanner/tests/test_rpmscanner.py -@@ -4,8 +4,8 @@ import pytest - - from leapp.exceptions import StopActorExecutionError - from leapp.libraries.actor import rpmscanner --from leapp.libraries.common import module as module_lib - from leapp.libraries.common import rpms, testutils -+from leapp.libraries.common.dnflibs import dnfmodule - from leapp.libraries.stdlib import api - from leapp.models import InstalledRPM, RPM - from leapp.snactor.fixture import current_actor_context -@@ -103,20 +103,20 @@ MODULES = [ - - @pytest.mark.skipif(no_yum and no_dnf, reason='yum/dnf is unavailable') - def test_actor_execution(monkeypatch, current_actor_context): -- monkeypatch.setattr(rpmscanner.module_lib, 'get_modules', lambda: []) -+ monkeypatch.setattr(rpmscanner.dnfmodule, 'get_modules', lambda: []) - current_actor_context.run() - assert current_actor_context.consume(InstalledRPM) - assert current_actor_context.consume(InstalledRPM)[0].items - - - def test_map_modular_rpms_to_modules_empty(monkeypatch): -- monkeypatch.setattr(module_lib, 'get_modules', lambda: []) -+ monkeypatch.setattr(dnfmodule, 'get_modules', lambda: []) - mapping = rpmscanner.map_modular_rpms_to_modules() - assert not mapping - - - def test_map_modular_rpms_to_modules(monkeypatch): -- monkeypatch.setattr(module_lib, 'get_modules', lambda: MODULES) -+ monkeypatch.setattr(dnfmodule, 'get_modules', lambda: MODULES) - mapping = rpmscanner.map_modular_rpms_to_modules() - assert mapping[ - ('afterburn', '0', '4.2.0', '1.module_f31+6825+8330d585', 'x86_64') -@@ -154,7 +154,7 @@ PACKAGE_REPOS = { - - - def test_process(monkeypatch): -- monkeypatch.setattr(module_lib, 'get_modules', lambda: MODULES) -+ monkeypatch.setattr(dnfmodule, 'get_modules', lambda: MODULES) - monkeypatch.setattr(rpmscanner, 'get_package_repository_data', lambda: PACKAGE_REPOS) - monkeypatch.setattr(rpms, 'get_installed_rpms', lambda: INSTALLED_RPMS) - monkeypatch.setattr(api, 'produce', testutils.produce_mocked()) -diff --git a/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py b/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py -index 2475aad4..a776deac 100644 ---- a/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py -+++ b/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py -@@ -6,7 +6,7 @@ import shutil - from leapp import reporting - from leapp.exceptions import StopActorExecution, StopActorExecutionError - from leapp.libraries.actor import constants --from leapp.libraries.common import distro, dnfplugin, mounting, overlaygen, repofileutils, rhsm, utils -+from leapp.libraries.common import distro, mounting, overlaygen, repofileutils, rhsm, utils - from leapp.libraries.common.config import ( - get_env, - get_product_type, -@@ -19,6 +19,7 @@ from leapp.libraries.common.config.version import ( - get_target_major_version, - get_target_version - ) -+from leapp.libraries.common.dnflibs import dnfplugin - from leapp.libraries.common.gpg import get_path_to_gpg_certs, is_nogpgcheck_set - from leapp.libraries.stdlib import api, CalledProcessError, config, run - from leapp.models import RequiredTargetUserspacePackages # deprecated -diff --git a/repos/system_upgrade/common/libraries/tests/test_dnfplugin.py b/repos/system_upgrade/common/libraries/tests/test_dnfplugin.py -index 1ca95945..2f220785 100644 ---- a/repos/system_upgrade/common/libraries/tests/test_dnfplugin.py -+++ b/repos/system_upgrade/common/libraries/tests/test_dnfplugin.py -@@ -3,8 +3,8 @@ from collections import namedtuple - import pytest - - import leapp.models --from leapp.libraries.common import dnfplugin - from leapp.libraries.common.config.version import get_major_version -+from leapp.libraries.common.dnflibs import dnfplugin - from leapp.libraries.common.testutils import CurrentActorMocked - from leapp.libraries.stdlib import api - from leapp.models.fields import Boolean --- -2.54.0 - diff --git a/0068-dnflibs-introduce-DNFError-exception.patch b/0068-dnflibs-introduce-DNFError-exception.patch deleted file mode 100644 index cd5967e..0000000 --- a/0068-dnflibs-introduce-DNFError-exception.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 1b6beff15bf6e5c23feb74ab5bc16ea4c526597b Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Mon, 4 May 2026 14:46:05 +0200 -Subject: [PATCH 068/105] dnflibs: introduce DNFError exception - -This is generic exception used for all DNF related errors raised -in dnflibs libraries. IOW, all exceptions introduced in these libraries -are based on DNFError, so if wanted, it's possible to catch a DNFError -exception in calling functions instead of listing each possible DNF -exception separately. - -Signed-off-by: Petr Stodulka ---- - repos/system_upgrade/common/libraries/dnflibs/__init__.py | 8 ++++++++ - 1 file changed, 8 insertions(+) - -diff --git a/repos/system_upgrade/common/libraries/dnflibs/__init__.py b/repos/system_upgrade/common/libraries/dnflibs/__init__.py -index d94e4b4c..1aef3295 100644 ---- a/repos/system_upgrade/common/libraries/dnflibs/__init__.py -+++ b/repos/system_upgrade/common/libraries/dnflibs/__init__.py -@@ -6,3 +6,11 @@ This package consolidates DNF functionality previously scattered across: - - leapp.libraries.common.dnfplugin -> dnflibs.dnfplugin - - leapp.libraries.common.module -> dnflibs.dnfmodule - """ -+ -+from leapp.exceptions import StopActorExecutionError -+ -+ -+class DNFError(StopActorExecutionError): -+ """ -+ Generic exception inherited by all DNF errors raised in dnflibs libraries. -+ """ --- -2.54.0 - diff --git a/0069-dnflibs.dnfplugin-Drop-dead-code-_handle_transaction.patch b/0069-dnflibs.dnfplugin-Drop-dead-code-_handle_transaction.patch deleted file mode 100644 index 31342b6..0000000 --- a/0069-dnflibs.dnfplugin-Drop-dead-code-_handle_transaction.patch +++ /dev/null @@ -1,48 +0,0 @@ -From 7d576c6d427d748e43c71e9ee031f556810fbb36 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Tue, 10 Mar 2026 16:11:47 +0100 -Subject: [PATCH 069/105] dnflibs.dnfplugin: Drop dead code: - _handle_transaction_err_msg_old - -This function is not used anywhere anymore. Let's just remove it. - -Signed-off-by: Petr Stodulka ---- - .../common/libraries/dnflibs/dnfplugin.py | 21 ------------------- - 1 file changed, 21 deletions(-) - -diff --git a/repos/system_upgrade/common/libraries/dnflibs/dnfplugin.py b/repos/system_upgrade/common/libraries/dnflibs/dnfplugin.py -index 32407283..b0e10054 100644 ---- a/repos/system_upgrade/common/libraries/dnflibs/dnfplugin.py -+++ b/repos/system_upgrade/common/libraries/dnflibs/dnfplugin.py -@@ -145,27 +145,6 @@ def backup_debug_data(context): - api.current_logger().warning('Failed to copy debugdata. Message: {}'.format(str(e)), exc_info=True) - - --def _handle_transaction_err_msg_old(stage, xfs_info, err): -- # NOTE(pstodulk): This is going to be removed in future! -- message = 'DNF execution failed with non zero exit code.' -- details = {'STDOUT': err.stdout, 'STDERR': err.stderr} -- -- if 'more space needed on the' in err.stderr and stage != 'upgrade': -- # Disk Requirements: -- # At least more space needed on the filesystem. -- # -- article_section = 'Generic case' -- if xfs_info.present and xfs_info.without_ftype: -- article_section = 'XFS ftype=0 case' -- -- message = ('There is not enough space on the file system hosting /var/lib/leapp directory ' -- 'to extract the packages.') -- details = {'hint': "Please follow the instructions in the '{}' section of the article at: " -- "link: https://access.redhat.com/solutions/5057391".format(article_section)} -- -- raise StopActorExecutionError(message=message, details=details) -- -- - def _handle_transaction_err_msg(err, is_container=False): - NO_SPACE_STR = 'more space needed on the' - if NO_SPACE_STR not in err.stderr: --- -2.54.0 - diff --git a/0070-dnflibs.dnfplugin-Drop-deprecated-DNF_PLUGIN_PATH.patch b/0070-dnflibs.dnfplugin-Drop-deprecated-DNF_PLUGIN_PATH.patch deleted file mode 100644 index af09491..0000000 --- a/0070-dnflibs.dnfplugin-Drop-deprecated-DNF_PLUGIN_PATH.patch +++ /dev/null @@ -1,107 +0,0 @@ -From e84fe5ebffc05e5f44d431ff770c0910131a6343 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Tue, 10 Mar 2026 18:24:46 +0100 -Subject: [PATCH 070/105] dnflibs.dnfplugin: Drop deprecated DNF_PLUGIN_PATH - -This has been deprecated for a long time - dropping it finally to -clean the code a little bit after the moving to a new path. - -Signed-off-by: Petr Stodulka ---- - .../common/libraries/dnflibs/dnfplugin.py | 49 ++++++------------- - .../common/libraries/dnfplugin.py | 3 +- - 2 files changed, 17 insertions(+), 35 deletions(-) - -diff --git a/repos/system_upgrade/common/libraries/dnflibs/dnfplugin.py b/repos/system_upgrade/common/libraries/dnflibs/dnfplugin.py -index b0e10054..43179138 100644 ---- a/repos/system_upgrade/common/libraries/dnflibs/dnfplugin.py -+++ b/repos/system_upgrade/common/libraries/dnflibs/dnfplugin.py -@@ -14,54 +14,37 @@ from leapp.libraries.stdlib import api, CalledProcessError, config - from leapp.models import DNFWorkaround - - DNF_PLUGIN_NAME = 'rhel_upgrade.py' --_DEDICATED_URL = 'https://access.redhat.com/solutions/7011704' -- -- --class _DnfPluginPathStr(str): -- _PATHS = { -- "9": os.path.join('/lib/python3.9/site-packages/dnf-plugins', DNF_PLUGIN_NAME), -- "10": os.path.join('/lib/python3.12/site-packages/dnf-plugins', DNF_PLUGIN_NAME), -- } -+DNF_PLUGIN_DATA_NAME = 'dnf-plugin-data.txt' -+DNF_PLUGIN_DATA_PATH = os.path.join('/var/lib/leapp', DNF_PLUGIN_DATA_NAME) -+DNF_PLUGIN_DATA_LOG_PATH = os.path.join('/var/log/leapp', DNF_PLUGIN_DATA_NAME) -+DNF_DEBUG_DATA_PATH = '/var/log/leapp/dnf-debugdata/' - -- def __init__(self): # noqa: W0231; pylint: disable=super-init-not-called -- self.data = "" -+_DEDICATED_URL = 'https://access.redhat.com/solutions/7011704' -+_DNF_PLUGIN_PATHS = { -+ '9': os.path.join('/lib/python3.9/site-packages/dnf-plugins', DNF_PLUGIN_NAME), -+ '10': os.path.join('/lib/python3.12/site-packages/dnf-plugins', DNF_PLUGIN_NAME), -+} - -- def _feed(self): -- major = get_target_major_version() -- if major not in _DnfPluginPathStr._PATHS: -- raise KeyError('{} is not a supported target version of RHEL'.format(major)) -- self.data = _DnfPluginPathStr._PATHS[major] - -- def __str__(self): -- self._feed() -- return str(self.data) - -- def __repr__(self): -- self._feed() -- return repr(self.data) - -- def lstrip(self, chars=None): -- self._feed() -- return self.data.lstrip(chars) - - --# Deprecated --DNF_PLUGIN_PATH = _DnfPluginPathStr() - --DNF_PLUGIN_DATA_NAME = 'dnf-plugin-data.txt' --DNF_PLUGIN_DATA_PATH = os.path.join('/var/lib/leapp', DNF_PLUGIN_DATA_NAME) --DNF_PLUGIN_DATA_LOG_PATH = os.path.join('/var/log/leapp', DNF_PLUGIN_DATA_NAME) --DNF_DEBUG_DATA_PATH = '/var/log/leapp/dnf-debugdata/' - - - def install(target_basedir): - """ - Installs our plugin to the DNF plugins. - """ -+ # NOTE(pstodulk): Keeping KeyError unhandled as this can happen only -+ # for the new major upgrade path. -+ tgt_plugin_path = os.path.join( -+ target_basedir, -+ _DNF_PLUGIN_PATHS[get_target_major_version()].lstrip('/') -+ ) - try: -- shutil.copy2( -- api.get_file_path(DNF_PLUGIN_NAME), -- os.path.join(target_basedir, DNF_PLUGIN_PATH.lstrip('/'))) -+ shutil.copy2(api.get_file_path(DNF_PLUGIN_NAME), tgt_plugin_path) - except EnvironmentError as e: - api.current_logger().debug('Failed to install DNF plugin', exc_info=True) - raise StopActorExecutionError( -diff --git a/repos/system_upgrade/common/libraries/dnfplugin.py b/repos/system_upgrade/common/libraries/dnfplugin.py -index 713cc34b..19e4d4e2 100644 ---- a/repos/system_upgrade/common/libraries/dnfplugin.py -+++ b/repos/system_upgrade/common/libraries/dnfplugin.py -@@ -10,9 +10,8 @@ This shim will be removed in a future version. Please update imports to: - from leapp.libraries.common.dnflibs import dnfplugin as _dnfplugin - from leapp.utils.deprecation import deprecated - --# Re-export constants (not deprecated) -+# Re-export constants - DNF_PLUGIN_NAME = _dnfplugin.DNF_PLUGIN_NAME --DNF_PLUGIN_PATH = _dnfplugin.DNF_PLUGIN_PATH - DNF_PLUGIN_DATA_NAME = _dnfplugin.DNF_PLUGIN_DATA_NAME - DNF_PLUGIN_DATA_PATH = _dnfplugin.DNF_PLUGIN_DATA_PATH - DNF_PLUGIN_DATA_LOG_PATH = _dnfplugin.DNF_PLUGIN_DATA_LOG_PATH --- -2.54.0 - diff --git a/0071-dnflibs.dnfplugin-Introduce-new-DNF-exceptions-and-t.patch b/0071-dnflibs.dnfplugin-Introduce-new-DNF-exceptions-and-t.patch deleted file mode 100644 index 5fd5883..0000000 --- a/0071-dnflibs.dnfplugin-Introduce-new-DNF-exceptions-and-t.patch +++ /dev/null @@ -1,903 +0,0 @@ -From e5f867a809db5ae9e671cb4a5c02eb2b88f3bf39 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Tue, 10 Mar 2026 18:27:18 +0100 -Subject: [PATCH 071/105] dnflibs.dnfplugin: Introduce new DNF exceptions and - tests - -Originally the library raised usually StopActorExecutionError exception -in case of issues. But as we agreed recently, we want to start to -raise rather proper exceptions in libraries and handle this better -in callers (actors). - -For the comfort (and sake of safety) the newly created exception -classes are based on the DNFError class, which is based on StopActorExecutionError, -so these exceptions will be properly handled also if they are not caught -correctly in actors. Also, in case of this library we kind of know -that we are the only callers of these functions as they are fundamental -for the upgrade process and majority of them can be executed just once -per the upgrade process. - -Docstrings in public functions have been properly updated to reflect -the change. This includes also docstrings of other public functions -in the library. - -List of new exceptions: - * DNFPluginInstallError - * DNFExecutionError - * DNFUpgradeTransactionError - * RegisteredWorkaroundApplicationError - -Assisted-by: Claude Sonnet 4.5 -Signed-off-by: Petr Stodulka ---- - .../common/libraries/dnflibs/dnfplugin.py | 194 +++++++++++- - .../libraries/dnflibs/tests/test_dnfplugin.py | 294 ++++++++++++++++++ - .../common/libraries/tests/test_dnfplugin.py | 193 ------------ - 3 files changed, 475 insertions(+), 206 deletions(-) - create mode 100644 repos/system_upgrade/common/libraries/dnflibs/tests/test_dnfplugin.py - delete mode 100644 repos/system_upgrade/common/libraries/tests/test_dnfplugin.py - -diff --git a/repos/system_upgrade/common/libraries/dnflibs/dnfplugin.py b/repos/system_upgrade/common/libraries/dnflibs/dnfplugin.py -index 43179138..cb810a2a 100644 ---- a/repos/system_upgrade/common/libraries/dnflibs/dnfplugin.py -+++ b/repos/system_upgrade/common/libraries/dnflibs/dnfplugin.py -@@ -8,7 +8,7 @@ import shutil - from leapp.exceptions import StopActorExecutionError - from leapp.libraries.common import guards, mounting, overlaygen, rhsm, utils - from leapp.libraries.common.config.version import get_target_major_version, get_target_version --from leapp.libraries.common.dnflibs import dnfconfig -+from leapp.libraries.common.dnflibs import dnfconfig, DNFError - from leapp.libraries.common.gpg import is_nogpgcheck_set - from leapp.libraries.stdlib import api, CalledProcessError, config - from leapp.models import DNFWorkaround -@@ -26,16 +26,43 @@ _DNF_PLUGIN_PATHS = { - } - - -+class DNFPluginInstallError(DNFError): -+ """ -+ Raised when cannot install the rhel_upgrade DNF plugin. -+ """ -+ -+ -+class DNFExecutionError(DNFError): -+ """ -+ Raised when cannot execute DNF. -+ """ - - -+class DNFUpgradeTransactionError(DNFError): -+ """ -+ Raised when an error with the DNF upgrade transaction occurs. - -+ This covers all stages of the upgrade, from the initial calculation in -+ preupgrade phases to the final execution of the transaction in the -+ upgrade environment. -+ """ - - -+class RegisteredWorkaroundApplicationError(StopActorExecutionError): -+ """ -+ Raised when a registered workaround cannot be applied or the application failed. -+ """ - - - def install(target_basedir): - """ -- Installs our plugin to the DNF plugins. -+ Installs the rhel_upgrade plugin to the DNF plugin under specified container path. -+ -+ :param target_basedir: The target userspace container base directory -+ :type target_basedir: str -+ :raises DNFPluginInstallError: When cannot install the DNF plugin. -+ :raises KeyError: When the installation DNF plugin path is not defined for -+ the target system (yet). - """ - # NOTE(pstodulk): Keeping KeyError unhandled as this can happen only - # for the new major upgrade path. -@@ -47,7 +74,7 @@ def install(target_basedir): - shutil.copy2(api.get_file_path(DNF_PLUGIN_NAME), tgt_plugin_path) - except EnvironmentError as e: - api.current_logger().debug('Failed to install DNF plugin', exc_info=True) -- raise StopActorExecutionError( -+ raise DNFPluginInstallError( - message='Failed to install DNF plugin. Error: {}'.format(str(e)) - ) - -@@ -55,6 +82,11 @@ def install(target_basedir): - def _rebuild_rpm_db(context, root=None): - """ - Convert rpmdb from BerkeleyDB to Sqlite -+ -+ :param context: Execution context -+ :type context: mounting.IsolatedActions -+ :param root: The system root dir for which the rpmdb should be rebuilt -+ :type root: str - """ - base_cmd = ['rpmdb', '--rebuilddb'] - cmd = base_cmd if not root else base_cmd + ['-r', root] -@@ -64,6 +96,19 @@ def _rebuild_rpm_db(context, root=None): - def build_plugin_data(target_repoids, debug, test, tasks, on_aws): - """ - Generates a dictionary with the DNF plugin data. -+ -+ :param target_repoids: Target repositories to use for the upgrade -+ :type target_repoids: list -+ :param debug: Whether debug data should be produced -+ :type debug: bool -+ :param test: Flag whether the test of the DNF transaction should be performed -+ :type test: bool -+ :param tasks: Explicit RPM transaction tasks for the DNF plugin -+ :type tasks: FilteredRpmTransactionTasks -+ :param on_aws: Whether running on AWS infrastructure -+ :type on_aws: bool -+ :returns: Dictionary with data for the DNF plugin -+ :rtype: dict - """ - # get list of repo IDs of target repositories that should be used for upgrade - data = { -@@ -99,6 +144,19 @@ def build_plugin_data(target_repoids, debug, test, tasks, on_aws): - def create_config(context, target_repoids, debug, test, tasks, on_aws=False): - """ - Creates the configuration data file for our DNF plugin. -+ -+ :param context: Execution context -+ :type context: mounting.IsolatedActions -+ :param target_repoids: Target repositories to use for the upgrade -+ :type target_repoids: list -+ :param debug: Whether debug data should be produced -+ :type debug: bool -+ :param test: Flag whether the test of the DNF transaction should be performed -+ :type test: bool -+ :param tasks: Explicit RPM transaction tasks for the DNF plugin -+ :type tasks: FilteredRpmTransactionTasks -+ :param on_aws: Whether running on AWS infrastructure -+ :type on_aws: bool - """ - context.makedirs(os.path.dirname(DNF_PLUGIN_DATA_PATH), exists_ok=True) - with context.open(DNF_PLUGIN_DATA_PATH, 'w+') as f: -@@ -111,6 +169,9 @@ def create_config(context, target_repoids, debug, test, tasks, on_aws=False): - def backup_config(context): - """ - Backs up the configuration data used for the plugin. -+ -+ :param context: Execution context -+ :type context: mounting.IsolatedActions - """ - context.copy_from(DNF_PLUGIN_DATA_PATH, DNF_PLUGIN_DATA_LOG_PATH) - -@@ -118,6 +179,9 @@ def backup_config(context): - def backup_debug_data(context): - """ - Performs the backup of DNF debug data -+ -+ :param context: Execution context -+ :type context: mounting.IsolatedActions - """ - if config.is_debug(): - # The debugdata is a folder generated by dnf when using the --debugsolver dnf option. We switch on the -@@ -155,7 +219,7 @@ def _handle_transaction_err_msg(err, is_container=False): - "for your current system to pass the early phases of the upgrade process." - ) - details = {'STDOUT': err.stdout, 'STDERR': err.stderr, 'hint': proxy_hint} -- raise StopActorExecutionError(message=message, details=details) -+ raise DNFUpgradeTransactionError(message=message, details=details) - - # Disk Requirements: - # At least more space needed on the filesystem. -@@ -189,7 +253,7 @@ def _handle_transaction_err_msg(err, is_container=False): - ) - details = {'hint': hint, 'Disk Requirements': '\n'.join(missing_space)} - -- raise StopActorExecutionError(message=message, details=details) -+ raise DNFUpgradeTransactionError(message=message, details=details) - - - def _transaction(context, stage, target_repoids, tasks, plugin_info, xfs_info, -@@ -198,7 +262,6 @@ def _transaction(context, stage, target_repoids, tasks, plugin_info, xfs_info, - Perform the actual DNF rpm download via our DNF plugin - """ - -- # we do not want - if stage not in ['dry-run', 'upgrade']: - create_config( - context=context, -@@ -259,7 +322,7 @@ def _transaction(context, stage, target_repoids, tasks, plugin_info, xfs_info, - ) - except OSError as e: - api.current_logger().error('Could not call dnf command: Message: %s', str(e), exc_info=True) -- raise StopActorExecutionError( -+ raise DNFExecutionError( - message='Failed to execute dnf. Reason: {}'.format(str(e)) - ) - except CalledProcessError as e: -@@ -283,8 +346,17 @@ def _prepare_transaction(used_repos, target_userspace_info, binds=()): - def apply_workarounds(context=None): - """ - Apply registered workarounds in the given context environment -+ -+ Note this function consumes DNFWorkaround messages! An actor calling this -+ function must list the DNFWorkaround in the list of consumable messages. -+ -+ :param context: Execution context (defaults to NotIsolatedActions) -+ :type context: mounting.IsolatedActions | None -+ :raises RegisteredWorkaroundApplicationError: When a workaround script fails to execute. - """ - context = context or mounting.NotIsolatedActions(base_dir='/') -+ # FIXME(pstodulk): add check that actor is consuming DNFWorkaround, and raise -+ # new error if it is not. - for workaround in api.consume(DNFWorkaround): - try: - api.show_message('Applying transaction workaround - {}'.format(workaround.display_name)) -@@ -297,15 +369,28 @@ def apply_workarounds(context=None): - cmd_str = workaround.script_path - context.call(['/bin/bash', '-c', cmd_str]) - except (OSError, CalledProcessError) as e: -- raise StopActorExecutionError( -- message=('Failed to execute script to apply transaction workaround {display_name}.' -- ' Message: {error}'.format(error=str(e), display_name=workaround.display_name)) -+ raise RegisteredWorkaroundApplicationError( -+ message=f'Failed to execute script to apply transaction workaround {workaround.display_name}.', -+ details={ -+ 'error message': str(e), -+ 'workaround name': workaround.display_name -+ } - ) - - - def install_initramdisk_requirements(packages, target_userspace_info, used_repos): - """ - Performs the installation of packages into the initram disk -+ -+ :param packages: List of package names to install -+ :type packages: list[str] -+ :param target_userspace_info: Information about the target userspace -+ :type target_userspace_info: TargetUserSpaceInfo -+ :param used_repos: Target repositories to use for the installation -+ :type used_repos: Iterable[UsedTargetRepositories] -+ :raises DNFUpgradeTransactionError: When the DNF transaction fails (insufficient disk space, -+ package conflicts, or repository access issues). -+ - """ - mount_binds = ['/:/installroot'] - with _prepare_transaction(used_repos=used_repos, target_userspace_info=target_userspace_info, -@@ -346,6 +431,25 @@ def install_initramdisk_requirements(packages, target_userspace_info, used_repos - def perform_transaction_install(target_userspace_info, storage_info, used_repos, tasks, plugin_info, xfs_info): - """ - Performs the actual installation with the DNF rhel-upgrade plugin using the target userspace -+ -+ :param target_userspace_info: Information about the target userspace -+ :type target_userspace_info: TargetUserSpaceInfo -+ :param storage_info: Storage and filesystem information -+ :type storage_info: StorageInfo -+ :param used_repos: Target repositories to use for the upgrade -+ :type used_repos: UsedTargetRepositories -+ :param tasks: Explicit RPM transaction tasks for the DNF plugin -+ :type tasks: FilteredRpmTransactionTasks -+ :param plugin_info: DNF plugin configuration -+ :type plugin_info: list[DNFPluginTask] -+ :param xfs_info: XFS filesystem information -+ :type xfs_info: XFSPresence -+ :raises DNFExecutionError: When the DNF command fails to execute. -+ :raises DNFUpgradeTransactionError: When the upgrade transaction encounters an error -+ (insufficient disk space, package conflicts, etc.). -+ -+ .. seealso:: -+ :func:`dnfconfig.exclude_leapp_rpms` - For DNF configuration exceptions - """ - - stage = 'upgrade' -@@ -398,7 +502,7 @@ def perform_transaction_install(target_userspace_info, storage_info, used_repos, - if int(get_target_major_version()) >= 9: - _rebuild_rpm_db(context, root='/installroot') - _transaction( -- context=context, stage='upgrade', target_repoids=target_repoids, plugin_info=plugin_info, -+ context=context, stage=stage, target_repoids=target_repoids, plugin_info=plugin_info, - xfs_info=xfs_info, tasks=tasks, cmd_prefix=cmd_prefix - ) - -@@ -435,6 +539,27 @@ def perform_transaction_check(target_userspace_info, - target_iso=None): - """ - Perform DNF transaction check using our plugin -+ -+ :param target_userspace_info: Information about the target userspace -+ :type target_userspace_info: TargetUserSpaceInfo -+ :param storage_info: Storage and filesystem information -+ :type storage_info: StorageInfo -+ :param used_repos: Target repositories to use for the upgrade -+ :type used_repos: UsedTargetRepositories -+ :param tasks: Explicit RPM transaction tasks for the DNF plugin -+ :type tasks: FilteredRpmTransactionTasks -+ :param plugin_info: DNF plugin configuration -+ :type plugin_info: list[DNFPluginTask] -+ :param xfs_info: XFS filesystem information -+ :type xfs_info: XFSPresence -+ :param target_iso: Optional path to target ISO image -+ :type target_iso: TargetOSInstallationImage | None -+ :raises DNFExecutionError: When the DNF command fails to execute. -+ :raises DNFUpgradeTransactionError: When the transaction check encounters an error -+ (package dependency conflicts, repository issues, etc.). -+ -+ .. seealso:: -+ :func:`dnfconfig.exclude_leapp_rpms` - For DNF configuration exceptions - """ - - stage = 'check' -@@ -451,7 +576,7 @@ def perform_transaction_check(target_userspace_info, - - dnfconfig.exclude_leapp_rpms(context, disable_plugins) - _transaction( -- context=context, stage='check', target_repoids=target_repoids, plugin_info=plugin_info, xfs_info=xfs_info, -+ context=context, stage=stage, target_repoids=target_repoids, plugin_info=plugin_info, xfs_info=xfs_info, - tasks=tasks - ) - -@@ -466,6 +591,29 @@ def perform_rpm_download(target_userspace_info, - on_aws=False): - """ - Perform RPM download including the transaction test using dnf with our plugin -+ -+ :param target_userspace_info: Information about the target userspace -+ :type target_userspace_info: TargetUserSpaceInfo -+ :param storage_info: Storage and filesystem information -+ :type storage_info: StorageInfo -+ :param used_repos: Target repositories to use for the upgrade -+ :type used_repos: UsedTargetRepositories -+ :param tasks: Explicit RPM transaction tasks for the DNF plugin -+ :type tasks: FilteredRpmTransactionTasks -+ :param plugin_info: DNF plugin configuration -+ :type plugin_info: list[DNFPluginTask] -+ :param xfs_info: XFS filesystem information -+ :type xfs_info: XFSPresence -+ :param target_iso: Optional path to target ISO image -+ :type target_iso: TargetOSInstallationImage | None -+ :param on_aws: Whether running on AWS infrastructure -+ :type on_aws: bool -+ :raises DNFExecutionError: When the DNF command fails to execute. -+ :raises DNFUpgradeTransactionError: When the RPM download or transaction test fails -+ (insufficient disk space, network issues, repository problems). -+ -+ .. seealso:: -+ :func:`dnfconfig.exclude_leapp_rpms` - For DNF configuration exceptions - """ - - stage = 'download' -@@ -485,7 +633,7 @@ def perform_rpm_download(target_userspace_info, - apply_workarounds(overlay.nspawn()) - dnfconfig.exclude_leapp_rpms(context, disable_plugins) - _transaction( -- context=context, stage='download', target_repoids=target_repoids, plugin_info=plugin_info, tasks=tasks, -+ context=context, stage=stage, target_repoids=target_repoids, plugin_info=plugin_info, tasks=tasks, - test=True, on_aws=on_aws, xfs_info=xfs_info - ) - -@@ -500,6 +648,26 @@ def perform_dry_run(target_userspace_info, - on_aws=False): - """ - Perform the dnf transaction test / dry-run using only cached data. -+ -+ :param target_userspace_info: Information about the target userspace -+ :type target_userspace_info: TargetUserSpaceInfo -+ :param storage_info: Storage and filesystem information -+ :type storage_info: StorageInfo -+ :param used_repos: Target repositories to use for the upgrade -+ :type used_repos: UsedTargetRepositories -+ :param tasks: Explicit RPM transaction tasks for the DNF plugin -+ :type tasks: FilteredRpmTransactionTasks -+ :param plugin_info: DNF plugin configuration -+ :type plugin_info: list[DNFPluginTask] -+ :param xfs_info: XFS filesystem information -+ :type xfs_info: XFSPresence -+ :param target_iso: Optional path to target ISO image -+ :type target_iso: TargetOSInstallationImage | None -+ :param on_aws: Whether running on AWS infrastructure -+ :type on_aws: bool -+ :raises DNFExecutionError: When the DNF command fails to execute. -+ :raises DNFUpgradeTransactionError: When the dry-run transaction test encounters an error -+ (package dependency conflicts, insufficient disk space). - """ - with _prepare_perform(used_repos=used_repos, - target_userspace_info=target_userspace_info, -diff --git a/repos/system_upgrade/common/libraries/dnflibs/tests/test_dnfplugin.py b/repos/system_upgrade/common/libraries/dnflibs/tests/test_dnfplugin.py -new file mode 100644 -index 00000000..18908934 ---- /dev/null -+++ b/repos/system_upgrade/common/libraries/dnflibs/tests/test_dnfplugin.py -@@ -0,0 +1,294 @@ -+import os -+from unittest.mock import MagicMock -+ -+import pytest -+ -+from leapp.libraries.common.dnflibs import dnfplugin -+from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked -+from leapp.libraries.stdlib import api, CalledProcessError -+from leapp.models import DNFWorkaround, Module -+ -+ -+class MockContext: -+ def __init__(self, should_raise=None): -+ self.should_raise = should_raise -+ self.makedirs_calls = [] -+ self.open_calls = [] -+ self.copy_from_calls = [] -+ self.copytree_from_calls = [] -+ self.call_calls = [] -+ self._files = {} -+ -+ def makedirs(self, path, exists_ok=False): -+ self.makedirs_calls.append((path, exists_ok)) -+ -+ def open(self, path, mode): -+ self.open_calls.append((path, mode)) -+ mock_file = MagicMock() -+ self._files[path] = mock_file -+ return mock_file -+ -+ def copy_from(self, src, dst): -+ self.copy_from_calls.append((src, dst)) -+ -+ def copytree_from(self, src, dst): -+ self.copytree_from_calls.append((src, dst)) -+ if self.should_raise: -+ raise self.should_raise -+ -+ def call(self, cmd, **kwargs): -+ self.call_calls.append(cmd) -+ if self.should_raise: -+ raise self.should_raise -+ -+ -+class MockTasks: -+ def __init__(self): -+ self.local_rpms = ['/path/to/rpm1.rpm', '/path/to/rpm2.rpm'] -+ self.to_install = ['pkg1', 'pkg2'] -+ self.to_remove = ['old-pkg1', 'old-pkg2'] -+ self.to_upgrade = ['upgrade-pkg1', 'upgrade-pkg2'] -+ self.modules_to_enable = [] -+ self.modules_to_reset = [] -+ -+ -+@pytest.mark.parametrize('target_version', ['9', '10']) -+def test_install_success(monkeypatch, leapp_tmpdir, target_version): -+ target_basedir = leapp_tmpdir -+ -+ plugin_source = os.path.join(leapp_tmpdir, dnfplugin.DNF_PLUGIN_NAME) -+ target_plugin_dir = os.path.join( -+ target_basedir, -+ dnfplugin._DNF_PLUGIN_PATHS[target_version].lstrip('/') -+ ) -+ -+ def mocked_copy2(src, dst): -+ assert plugin_source == src -+ assert target_plugin_dir == dst -+ -+ def mocked_get_file_path(fpath): -+ assert fpath == dnfplugin.DNF_PLUGIN_NAME -+ return plugin_source -+ -+ monkeypatch.setattr(dnfplugin, 'get_target_major_version', lambda: target_version) -+ monkeypatch.setattr(api, 'get_file_path', mocked_get_file_path) -+ monkeypatch.setattr(dnfplugin.shutil, 'copy2', mocked_copy2) -+ -+ dnfplugin.install(target_basedir) -+ -+ -+def test_install_failure(monkeypatch, leapp_tmpdir): -+ target_basedir = leapp_tmpdir -+ -+ monkeypatch.setattr(dnfplugin, 'get_target_major_version', lambda: '9') -+ monkeypatch.setattr(api, 'get_file_path', lambda name: '/nonexistent/file') -+ monkeypatch.setattr(api, 'current_logger', logger_mocked()) -+ -+ with pytest.raises(dnfplugin.DNFPluginInstallError) as exc_info: -+ dnfplugin.install(target_basedir) -+ -+ assert 'Failed to install DNF plugin' in str(exc_info.value) -+ -+ -+def test_install_unsupported_version(monkeypatch, leapp_tmpdir): -+ target_basedir = leapp_tmpdir -+ -+ def mocked_copy2(dummy_src, dummy_dst): -+ assert False -+ -+ monkeypatch.setattr(dnfplugin, 'get_target_major_version', lambda: '99') -+ monkeypatch.setattr(dnfplugin.shutil, 'copy2', mocked_copy2) -+ -+ with pytest.raises(KeyError): -+ dnfplugin.install(target_basedir) -+ -+ -+@pytest.mark.parametrize('debug,test,on_aws', [ -+ (True, False, False), -+ (False, True, True), -+ (True, True, False), -+]) -+def test_build_plugin_data(monkeypatch, debug, test, on_aws): -+ tasks = MockTasks() -+ module1 = Module(name='nodejs', stream='18') -+ module2 = Module(name='postgresql', stream='15') -+ tasks.modules_to_enable = [module1, module2] -+ -+ target_repoids = ['rhel-9-baseos', 'rhel-9-appstream'] -+ -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(dst_ver='9.3')) -+ monkeypatch.setattr(dnfplugin, 'is_nogpgcheck_set', lambda: False) -+ -+ data = dnfplugin.build_plugin_data(target_repoids, debug, test, tasks, on_aws) -+ -+ assert data['pkgs_info']['local_rpms'] == ['/installroot/path/to/rpm1.rpm', '/installroot/path/to/rpm2.rpm'] -+ assert data['pkgs_info']['to_install'] == ['pkg1', 'pkg2'] -+ assert data['pkgs_info']['to_remove'] == ['old-pkg1', 'old-pkg2'] -+ assert data['pkgs_info']['to_upgrade'] == ['upgrade-pkg1', 'upgrade-pkg2'] -+ assert 'nodejs:18' in data['pkgs_info']['modules_to_enable'] -+ assert 'postgresql:15' in data['pkgs_info']['modules_to_enable'] -+ -+ assert data['dnf_conf']['debugsolver'] is debug -+ assert data['dnf_conf']['enable_repos'] == target_repoids -+ assert data['dnf_conf']['platform_id'] == 'platform:el9' -+ assert data['dnf_conf']['releasever'] == '9.3' -+ assert data['dnf_conf']['test_flag'] is test -+ assert data['dnf_conf']['gpgcheck'] is True -+ -+ assert data['rhui']['aws']['on_aws'] is on_aws -+ -+ -+def test_build_plugin_data_with_nogpgcheck(monkeypatch): -+ tasks = MockTasks() -+ -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(dst_ver='9.3')) -+ monkeypatch.setattr(dnfplugin, 'is_nogpgcheck_set', lambda: True) -+ -+ data = dnfplugin.build_plugin_data([], False, False, tasks, False) -+ -+ assert data['dnf_conf']['gpgcheck'] is False -+ -+ -+def test_create_config(monkeypatch): -+ context = MockContext() -+ tasks = MockTasks() -+ target_repoids = ['repo1', 'repo2'] -+ -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(dst_ver='9.3')) -+ monkeypatch.setattr(dnfplugin, 'is_nogpgcheck_set', lambda: False) -+ -+ dnfplugin.create_config(context, target_repoids, debug=True, test=False, tasks=tasks, on_aws=False) -+ -+ assert len(context.makedirs_calls) == 1 -+ assert context.makedirs_calls[0][0] == os.path.dirname(dnfplugin.DNF_PLUGIN_DATA_PATH) -+ -+ assert len(context.open_calls) == 1 -+ assert context.open_calls[0][0] == dnfplugin.DNF_PLUGIN_DATA_PATH -+ assert context.open_calls[0][1] == 'w+' -+ -+ -+def test_backup_config(): -+ context = MockContext() -+ -+ dnfplugin.backup_config(context) -+ -+ assert len(context.copy_from_calls) == 1 -+ assert context.copy_from_calls[0] == (dnfplugin.DNF_PLUGIN_DATA_PATH, dnfplugin.DNF_PLUGIN_DATA_LOG_PATH) -+ -+ -+def test_backup_debug_data_with_debug(monkeypatch): -+ context = MockContext() -+ -+ monkeypatch.setattr(dnfplugin.config, 'is_debug', lambda: True) -+ -+ dnfplugin.backup_debug_data(context) -+ -+ assert len(context.copytree_from_calls) == 1 -+ assert context.copytree_from_calls[0][0] == '/debugdata' -+ -+ -+def test_backup_debug_data_without_debug(monkeypatch): -+ context = MockContext() -+ -+ monkeypatch.setattr(dnfplugin.config, 'is_debug', lambda: False) -+ -+ dnfplugin.backup_debug_data(context) -+ -+ assert len(context.copytree_from_calls) == 0 -+ -+ -+def test_backup_debug_data_oserror(monkeypatch): -+ context = MockContext(should_raise=OSError('Permission denied')) -+ -+ monkeypatch.setattr(dnfplugin.config, 'is_debug', lambda: True) -+ monkeypatch.setattr(api, 'current_logger', logger_mocked()) -+ -+ dnfplugin.backup_debug_data(context) -+ -+ -+@pytest.mark.parametrize('stderr,is_container,expected_in_message', [ -+ ('At least 500MB more space needed on the / filesystem.', True, '500MB'), -+ ('At least 500MB more space needed on the /usr filesystem.', False, '/usr'), -+ ('At least 200MB more space needed on the /var filesystem.', False, '/var'), -+]) -+def test_handle_transaction_err_msg_no_space(stderr, is_container, expected_in_message): -+ err = CalledProcessError( -+ 'dnf failed', -+ ['dnf', 'upgrade'], -+ { -+ 'stdout': 'stdout', -+ 'stderr': 'Error: Transaction test error:\n Disk Requirements:\n {}'.format(stderr), -+ 'exit_code': 1 -+ } -+ ) -+ -+ with pytest.raises(dnfplugin.DNFUpgradeTransactionError) as exc_info: -+ dnfplugin._handle_transaction_err_msg(err, is_container=is_container) -+ -+ assert 'not enough space' in str(exc_info.value).lower() -+ hint = str(exc_info.value.details.get('hint', '')) -+ disk_reqs = str(exc_info.value.details.get('Disk Requirements', '')) -+ assert expected_in_message in (hint + disk_reqs) -+ -+ -+def test_handle_transaction_err_msg_generic_error(): -+ err = CalledProcessError( -+ 'dnf failed', -+ ['dnf', 'upgrade'], -+ {'stdout': 'stdout output', 'stderr': 'Some other DNF error', 'exit_code': 1} -+ ) -+ -+ with pytest.raises(dnfplugin.DNFUpgradeTransactionError) as exc_info: -+ dnfplugin._handle_transaction_err_msg(err, is_container=False) -+ -+ assert 'DNF execution failed' in str(exc_info.value) -+ assert exc_info.value.details['STDOUT'] == 'stdout output' -+ assert exc_info.value.details['STDERR'] == 'Some other DNF error' -+ assert 'proxy' in exc_info.value.details['hint'].lower() -+ -+ -+def test_apply_workarounds_success(monkeypatch): -+ workaround1 = DNFWorkaround( -+ display_name='Workaround 1', -+ script_path='/path/to/script1.sh', -+ script_args=[] -+ ) -+ workaround2 = DNFWorkaround( -+ display_name='Workaround 2', -+ script_path='/path/to/script2.sh', -+ script_args=['arg1', 'arg2'] -+ ) -+ -+ context = MockContext() -+ -+ monkeypatch.setattr(api, 'consume', lambda model: [workaround1, workaround2]) -+ monkeypatch.setattr(api, 'show_message', lambda msg: None) -+ -+ dnfplugin.apply_workarounds(context) -+ -+ assert len(context.call_calls) == 2 -+ assert context.call_calls[0] == ['/bin/bash', '-c', '/path/to/script1.sh'] -+ assert context.call_calls[1] == ['/bin/bash', '-c', '/path/to/script2.sh arg1 arg2'] -+ -+ -+def test_apply_workarounds_script_fails(monkeypatch): -+ workaround = DNFWorkaround( -+ display_name='Failing Workaround', -+ script_path='/path/to/failing.sh', -+ script_args=[] -+ ) -+ -+ context = MockContext(should_raise=CalledProcessError( -+ 'Script failed', -+ ['/bin/bash', '-c', '/path/to/failing.sh'], -+ {'exit_code': 1, 'stdout': '', 'stderr': 'Script error'} -+ )) -+ -+ monkeypatch.setattr(api, 'consume', lambda model: [workaround]) -+ monkeypatch.setattr(api, 'show_message', lambda msg: None) -+ -+ with pytest.raises(dnfplugin.RegisteredWorkaroundApplicationError) as exc_info: -+ dnfplugin.apply_workarounds(context) -+ -+ assert 'Failed to execute script' in str(exc_info.value) -+ assert 'Failing Workaround' in exc_info.value.details['workaround name'] -diff --git a/repos/system_upgrade/common/libraries/tests/test_dnfplugin.py b/repos/system_upgrade/common/libraries/tests/test_dnfplugin.py -deleted file mode 100644 -index 2f220785..00000000 ---- a/repos/system_upgrade/common/libraries/tests/test_dnfplugin.py -+++ /dev/null -@@ -1,193 +0,0 @@ --from collections import namedtuple -- --import pytest -- --import leapp.models --from leapp.libraries.common.config.version import get_major_version --from leapp.libraries.common.dnflibs import dnfplugin --from leapp.libraries.common.testutils import CurrentActorMocked --from leapp.libraries.stdlib import api --from leapp.models.fields import Boolean --from leapp.topics import Topic -- -- --class DATADnfPluginDataTopic(Topic): -- name = 'data_dnf_plugin_data' -- -- --fields = leapp.models.fields -- --TaskData = namedtuple('TaskData', 'expected initdata') -- --TEST_INSTALL_PACKAGES = TaskData( -- expected=('install1', 'install2'), -- initdata=('install1', 'install2') --) --TEST_REMOVE_PACKAGES = TaskData( -- expected=('remove1', 'remove2'), -- initdata=('remove1', 'remove2'), --) --TEST_UPGRADE_PACKAGES = TaskData( -- expected=('upgrade1', 'upgrade2'), -- initdata=('upgrade1', 'upgrade2'), --) --TEST_ENABLE_MODULES = TaskData( -- expected=('enable1:stream1', 'enable2:stream2'), -- initdata=( -- leapp.models.Module(name='enable1', stream='stream1'), -- leapp.models.Module(name='enable2', stream='stream2'), -- ) --) -- -- --class DATADnfPluginDataPkgsInfo(leapp.models.Model): -- topic = DATADnfPluginDataTopic -- local_rpms = fields.List(fields.String()) -- to_install = fields.List(fields.StringEnum(choices=TEST_INSTALL_PACKAGES.expected)) -- to_remove = fields.List(fields.StringEnum(choices=TEST_REMOVE_PACKAGES.expected)) -- to_upgrade = fields.List(fields.StringEnum(choices=TEST_UPGRADE_PACKAGES.expected)) -- modules_to_enable = fields.List(fields.StringEnum(choices=TEST_ENABLE_MODULES.expected)) -- -- --TEST_ENABLE_REPOS_CHOICES = ('enabled_repo', 'BASEOS', 'APPSTREAM') -- -- --class BooleanEnum(fields.EnumMixin, Boolean): -- pass -- -- --class DATADnfPluginDataDnfConf(leapp.models.Model): -- topic = DATADnfPluginDataTopic -- allow_erasing = BooleanEnum(choices=[True]) -- best = BooleanEnum(choices=[True]) -- debugsolver = fields.Boolean() -- disable_repos = BooleanEnum(choices=[True]) -- enable_repos = fields.List(fields.StringEnum(choices=TEST_ENABLE_REPOS_CHOICES)) -- gpgcheck = fields.Boolean() -- platform_id = fields.StringEnum(choices=['platform:el8', 'platform:el9']) -- releasever = fields.String() -- installroot = fields.StringEnum(choices=['/installroot']) -- test_flag = fields.Boolean() -- -- --class DATADnfPluginDataRHUIAWS(leapp.models.Model): -- topic = DATADnfPluginDataTopic -- on_aws = fields.Boolean() -- region = fields.Nullable(fields.String()) -- -- --class DATADnfPluginDataRHUI(leapp.models.Model): -- topic = DATADnfPluginDataTopic -- aws = fields.Model(DATADnfPluginDataRHUIAWS) -- -- --class DATADnfPluginData(leapp.models.Model): -- topic = DATADnfPluginDataTopic -- pkgs_info = fields.Model(DATADnfPluginDataPkgsInfo) -- dnf_conf = fields.Model(DATADnfPluginDataDnfConf) -- rhui = fields.Model(DATADnfPluginDataRHUI) -- -- --# Delete those models from leapp.models to 'unpolute' the module --del leapp.models.DATADnfPluginDataPkgsInfo --del leapp.models.DATADnfPluginDataDnfConf --del leapp.models.DATADnfPluginDataRHUI --del leapp.models.DATADnfPluginDataRHUIAWS --del leapp.models.DATADnfPluginData -- -- --_CONFIG_BUILD_TEST_DEFINITION = ( -- # Parameter, Input Data, Expected Fields with data -- ('debug', False, ('dnf_conf', 'debugsolver'), False), -- ('debug', True, ('dnf_conf', 'debugsolver'), True), -- ('target_repoids', TEST_ENABLE_REPOS_CHOICES, ('dnf_conf', 'enable_repos'), list(TEST_ENABLE_REPOS_CHOICES)), -- ('target_repoids', TEST_ENABLE_REPOS_CHOICES[0:1], -- ('dnf_conf', 'enable_repos'), list(TEST_ENABLE_REPOS_CHOICES[0:1])), -- ('target_repoids', TEST_ENABLE_REPOS_CHOICES[1:], -- ('dnf_conf', 'enable_repos'), list(TEST_ENABLE_REPOS_CHOICES[1:])), -- ('target_repoids', TEST_ENABLE_REPOS_CHOICES[2:], -- ('dnf_conf', 'enable_repos'), list(TEST_ENABLE_REPOS_CHOICES[2:])), -- ('test', False, ('dnf_conf', 'test_flag'), False), -- ('test', True, ('dnf_conf', 'test_flag'), True), --) -- -- --@pytest.mark.parametrize('used_target_version', ['8.4', '8.5', '9.0', '9.1']) --@pytest.mark.parametrize('parameter,input_value,test_path,expected_value', _CONFIG_BUILD_TEST_DEFINITION) --def test_build_plugin_data_variations( -- monkeypatch, -- used_target_version, -- parameter, -- input_value, -- test_path, -- expected_value, --): -- used_target_major_version = get_major_version(used_target_version) -- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(dst_ver=used_target_version)) -- inputs = { -- 'target_repoids': ['BASEOS', 'APPSTREAM'], -- 'debug': True, -- 'test': True, -- 'on_aws': False, -- 'tasks': leapp.models.FilteredRpmTransactionTasks( -- to_install=TEST_INSTALL_PACKAGES.initdata, -- to_remove=TEST_REMOVE_PACKAGES.initdata, -- to_upgrade=TEST_UPGRADE_PACKAGES.initdata, -- modules_to_enable=TEST_ENABLE_MODULES.initdata -- ) -- } -- inputs[parameter] = input_value -- created = DATADnfPluginData.create( -- dnfplugin.build_plugin_data( -- **inputs -- ) -- ) -- assert created.dnf_conf.platform_id == 'platform:el{}'.format(used_target_major_version) -- assert created.dnf_conf.releasever == used_target_version -- value = created -- for path in test_path: -- value = getattr(value, path) -- assert value == expected_value -- -- --def test_build_plugin_data(monkeypatch): -- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(dst_ver='8.4')) -- # Use leapp to validate format and data -- created = DATADnfPluginData.create( -- dnfplugin.build_plugin_data( -- target_repoids=['BASEOS', 'APPSTREAM'], -- debug=True, -- test=True, -- on_aws=False, -- tasks=leapp.models.FilteredRpmTransactionTasks( -- to_install=TEST_INSTALL_PACKAGES.initdata, -- to_remove=TEST_REMOVE_PACKAGES.initdata, -- to_upgrade=TEST_UPGRADE_PACKAGES.initdata, -- modules_to_enable=TEST_ENABLE_MODULES.initdata -- ) -- ) -- ) -- assert created.dnf_conf.debugsolver is True -- assert created.dnf_conf.test_flag is True -- assert created.rhui.aws.on_aws is False -- -- with pytest.raises(fields.ModelViolationError): -- DATADnfPluginData.create( -- dnfplugin.build_plugin_data( -- target_repoids=['BASEOS', 'APPSTREAM'], -- debug=True, -- test=True, -- on_aws=False, -- tasks=leapp.models.FilteredRpmTransactionTasks( -- to_install=TEST_INSTALL_PACKAGES.initdata, -- to_remove=TEST_REMOVE_PACKAGES.initdata, -- to_upgrade=TEST_UPGRADE_PACKAGES.initdata, -- # Enforcing the failure -- modules_to_enable=( -- leapp.models.Module( -- name='broken', stream=None -- ), -- ), -- ) -- ) -- ) --- -2.54.0 - diff --git a/0072-dnflibs-dnfmodule-Move-init-of-dnf.Base-to-dnflibs-a.patch b/0072-dnflibs-dnfmodule-Move-init-of-dnf.Base-to-dnflibs-a.patch deleted file mode 100644 index 58b976a..0000000 --- a/0072-dnflibs-dnfmodule-Move-init-of-dnf.Base-to-dnflibs-a.patch +++ /dev/null @@ -1,637 +0,0 @@ -From a48283cb02eeca31847b5501698d259979efc78b Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Wed, 11 Mar 2026 18:42:48 +0100 -Subject: [PATCH 072/105] dnflibs/dnfmodule: Move init of dnf.Base to dnflibs - and add tests - -Introduce `create__dnf_base()` function in the `dnflibs` package. -The function replaces original private function -`dnflibs.dnfmodule._create_or_get_dnf_base`. - -The correct creation and initialisation of the dnf.Base object is -non-trivial. To cover the initialisation correctly for various setups -(including also systems using RHUI which requires additional transparent -DNF plugin) we make this function public so it can be used in actors. - -The sack in dnf.Base object is filled and prepared for use. -The function can raise `DNFRepoError` exception, based on -`DNFError`, if a repository cannot be loaded properly, -providing additional details about the problem. - -NOTE that included tests provides just trivial coverate as there -is not a good way to cover this library by unit-tests without full -mock of DNF and hawkey libraries - if we want to keep test independent -on systems where they are executed. Keeping the valid testing on CI -pipelines with integration tests. - -Assisted-by: Claude Sonnet 4.5 -Signed-off-by: Petr Stodulka ---- - .../common/libraries/dnflibs/__init__.py | 67 +++++ - .../common/libraries/dnflibs/dnfmodule.py | 71 ++--- - .../libraries/dnflibs/tests/test_dnflibs.py | 118 ++++++++ - .../libraries/dnflibs/tests/test_dnfmodule.py | 270 ++++++++++++++++++ - 4 files changed, 483 insertions(+), 43 deletions(-) - create mode 100644 repos/system_upgrade/common/libraries/dnflibs/tests/test_dnflibs.py - create mode 100644 repos/system_upgrade/common/libraries/dnflibs/tests/test_dnfmodule.py - -diff --git a/repos/system_upgrade/common/libraries/dnflibs/__init__.py b/repos/system_upgrade/common/libraries/dnflibs/__init__.py -index 1aef3295..787119a7 100644 ---- a/repos/system_upgrade/common/libraries/dnflibs/__init__.py -+++ b/repos/system_upgrade/common/libraries/dnflibs/__init__.py -@@ -7,10 +7,77 @@ This package consolidates DNF functionality previously scattered across: - - leapp.libraries.common.module -> dnflibs.dnfmodule - """ - -+import warnings -+ - from leapp.exceptions import StopActorExecutionError -+from leapp.libraries.common.config.version import get_source_major_version -+ -+try: -+ import dnf -+except ImportError: -+ dnf = None -+ warnings.warn('Could not import the `dnf` python module.', ImportWarning) - - - class DNFError(StopActorExecutionError): - """ - Generic exception inherited by all DNF errors raised in dnflibs libraries. - """ -+ -+ -+class DNFRepoError(DNFError): -+ """ -+ Used when DNF fails to load repositories. -+ """ -+ -+ -+def create_dnf_base(): -+ """ -+ Create properly initialized dnf.Base with filled sack. -+ -+ The proper initialisation of dnf.Base object is non-trivial and order of -+ operations matters - we made plenty of mistakes already before the function -+ got to this state, covering various setups and systems. So use it instead -+ of trying to do so manually. -+ -+ :returns: Initialized dnf.Base object with filled sack -+ :rtype: dnf.Base -+ :raises DNFRepoError: When a repository cannot be loaded -+ """ -+ # The DNF command reads /etc/dnf/vars/releasever, but the DNF library does not. It parses redhat-release -+ # package to retrieve system's major version which it then uses as $releasever. However, some systems might -+ # have repositories only for the exact system version (including the minor number). In a case when -+ # /etc/dnf/vars/releasever is present, read its contents so that we can access repositores on such systems. -+ conf = dnf.conf.Conf() -+ -+ # preload releasever from what we know, this will be our fallback -+ conf.substitutions['releasever'] = get_source_major_version() -+ -+ # load all substitutions from etc -+ conf.substitutions.update_from_etc('/') -+ -+ base = dnf.Base(conf=conf) -+ base.conf.read() -+ base.init_plugins() -+ base.read_all_repos() -+ -+ # configure plugins after the repositories are loaded -+ # e.g. the amazon-id plugin requires loaded repositories -+ # for the proper configuration. -+ base.configure_plugins() -+ -+ try: -+ base.fill_sack() -+ except dnf.exceptions.RepoError as e: -+ err_msg = str(e) -+ repoid = err_msg.split('repo:')[-1].strip() if 'repo:' in err_msg else 'unknown repo' -+ repoid = repoid.strip('"').strip("'").replace('\\"', '') -+ raise DNFRepoError( -+ message='DNF failed to load repositories: {}'.format(str(e)), -+ details={ -+ 'hint': 'Ensure the {} repository definition is correct or remove it ' -+ 'if the repository is not needed anymore.'.format(repoid) -+ } -+ ) -+ -+ return base -diff --git a/repos/system_upgrade/common/libraries/dnflibs/dnfmodule.py b/repos/system_upgrade/common/libraries/dnflibs/dnfmodule.py -index d85e4659..b274d917 100644 ---- a/repos/system_upgrade/common/libraries/dnflibs/dnfmodule.py -+++ b/repos/system_upgrade/common/libraries/dnflibs/dnfmodule.py -@@ -1,7 +1,6 @@ - import warnings - --from leapp.exceptions import StopActorExecutionError --from leapp.libraries.common.config.version import get_source_major_version -+from leapp.libraries.common.dnflibs import create_dnf_base - - try: - import dnf -@@ -16,55 +15,26 @@ except ImportError: - warnings.warn('Could not import the `hawkey` python module.', ImportWarning) - - --def _create_or_get_dnf_base(base=None): -- if not base: -- # The DNF command reads /etc/yum/vars/releasever, but the DNF library does not. It parses redhat-release -- # package to retrieve system's major version which it then uses as $releasever. However, some systems might -- # have repositories only for the exact system version (including the minor number). In a case when -- # /etc/yum/vars/releasever is present, read its contents so that we can access repositores on such systems. -- conf = dnf.conf.Conf() -- -- # preload releasever from what we know, this will be our fallback -- conf.substitutions['releasever'] = get_source_major_version() -- -- # load all substitutions from etc -- conf.substitutions.update_from_etc('/') -- -- base = dnf.Base(conf=conf) -- base.conf.read() -- base.init_plugins() -- base.read_all_repos() -- # configure plugins after the repositories are loaded -- # e.g. the amazon-id plugin requires loaded repositories -- # for the proper configuration. -- base.configure_plugins() -- -- try: -- base.fill_sack() -- except dnf.exceptions.RepoError as e: -- err_msg = str(e) -- repoid = err_msg.split('repo:')[-1].strip() if 'repo:' in err_msg else 'unknown repo' -- repoid = repoid.strip('"').strip("'").replace('\\"', '') -- raise StopActorExecutionError( -- message='DNF failed to load repositories: {}'.format(str(e)), -- details={ -- 'hint': 'Ensure the {} repository definition is correct or remove it ' -- 'if the repository is not needed anymore.'.format(repoid) -- } -- ) -- return base -- -- - def get_modules(base=None): - """ - Return info about all module streams as a list of libdnf.module.ModulePackage objects. -+ -+ The function return an empty list if the DNF python module is not present. -+ -+ :param base: If it is set, use it instead of creating a new one. -+ :type base: dnf.Base | None -+ -+ .. seealso:: -+ :func:`create_dnf_base` for exceptions raised when creating dnf.Base - """ - if not dnf: - return [] -- base = _create_or_get_dnf_base(base) -+ if not base: -+ base = create_dnf_base() - - module_base = dnf.module.module_base.ModuleBase(base) - # this method is absent on RHEL 7, in which case there are no modules anyway -+ # note the method could be removed in future as well - so keep the check - if not hasattr(module_base, 'get_modules'): - return [] - return module_base.get_modules('*')[0] -@@ -73,11 +43,20 @@ def get_modules(base=None): - def get_enabled_modules(): - """ - Return currently enabled module streams as a list of libdnf.module.ModulePackage objects. -+ -+ The function return an empty list if the DNF python module is not present. -+ -+ :returns: Currently enabled module streams -+ :rtype: List[libdnf.module.ModulePackage] -+ -+ .. seealso:: -+ :func:`get_modules` for exceptions raised during module discovery. -+ :func:`create_dnf_base` for exceptions raised when creating dnf.Base - """ - if not dnf: - return [] - -- base = _create_or_get_dnf_base() -+ base = create_dnf_base() - modules = get_modules(base) - - # if modules are not supported (RHEL 7), base.sack._moduleContainer won't exist -@@ -88,6 +67,12 @@ def get_enabled_modules(): - def map_installed_rpms_to_modules(): - """ - Map installed modular packages to the module streams they come from. -+ -+ :returns: Mapping of RPM NVRA tuples to (module_name, stream) tuples -+ :rtype: dict -+ -+ .. seealso:: -+ :func:`get_modules` for exceptions raised during module discovery. - """ - modules = get_modules() - # empty on RHEL 7 because of no modules -diff --git a/repos/system_upgrade/common/libraries/dnflibs/tests/test_dnflibs.py b/repos/system_upgrade/common/libraries/dnflibs/tests/test_dnflibs.py -new file mode 100644 -index 00000000..daf9c125 ---- /dev/null -+++ b/repos/system_upgrade/common/libraries/dnflibs/tests/test_dnflibs.py -@@ -0,0 +1,118 @@ -+import pytest -+ -+from leapp.libraries.common import dnflibs -+from leapp.libraries.common.testutils import CurrentActorMocked -+from leapp.libraries.stdlib import api -+ -+ -+class MockSubstitutions(dict): -+ def update_from_etc(self, path): -+ pass -+ -+ -+class MockDNFConf: -+ def __init__(self): -+ self.substitutions = MockSubstitutions({'releasever': '8'}) -+ -+ def read(self): -+ pass -+ -+ -+class MockDNFBase: -+ def __init__(self, conf=None): -+ self.conf = conf or MockDNFConf() -+ self._fill_sack_called = False -+ -+ def read(self): -+ pass -+ -+ def init_plugins(self): -+ pass -+ -+ def read_all_repos(self): -+ pass -+ -+ def configure_plugins(self): -+ pass -+ -+ def fill_sack(self): -+ self._fill_sack_called = True -+ -+ -+class MockDNF: -+ """Mock dnf module""" -+ class conf: -+ Conf = MockDNFConf -+ -+ Base = MockDNFBase -+ -+ class exceptions: -+ RepoError = Exception -+ -+ -+def test_create_dnf_base_success(monkeypatch): -+ """ -+ Test successful creation and initialization of dnf.Base -+ """ -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(src_ver='8.10')) -+ monkeypatch.setattr(dnflibs, 'dnf', MockDNF) -+ -+ base = dnflibs.create_dnf_base() -+ -+ assert base is not None -+ assert isinstance(base, MockDNFBase) -+ assert base._fill_sack_called -+ -+ -+@pytest.mark.parametrize('error_message,expected_repoid', [ -+ ('Failed to download metadata for repo: test-repo', 'test-repo'), -+ ('Error with repo: "my-repo"', 'my-repo'), -+ ("Failed for repo: 'quoted-repo'", 'quoted-repo'), -+ ('Generic error without repo information', 'unknown repo'), -+]) -+def test_create_dnf_base_repo_error(monkeypatch, error_message, expected_repoid): -+ class RepoError(Exception): -+ pass -+ -+ class MockExceptions: -+ pass -+ -+ MockExceptions.RepoError = RepoError -+ -+ class FailingMockDNFBase: -+ _repo_error = RepoError -+ _error_message = error_message -+ -+ def __init__(self, conf=None): -+ self.conf = conf or MockDNFConf() -+ -+ def read(self): -+ pass -+ -+ def init_plugins(self): -+ pass -+ -+ def read_all_repos(self): -+ pass -+ -+ def configure_plugins(self): -+ pass -+ -+ def fill_sack(self): -+ raise self._repo_error(self._error_message) -+ -+ class FailingMockDNF: -+ class conf: -+ Conf = MockDNFConf -+ -+ Base = FailingMockDNFBase -+ exceptions = MockExceptions -+ -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(src_ver='8.10')) -+ monkeypatch.setattr(dnflibs, 'dnf', FailingMockDNF) -+ -+ with pytest.raises(dnflibs.DNFRepoError) as exc_info: -+ dnflibs.create_dnf_base() -+ -+ assert 'DNF failed to load repositories' in str(exc_info.value) -+ assert expected_repoid in str(exc_info.value.details['hint']) -diff --git a/repos/system_upgrade/common/libraries/dnflibs/tests/test_dnfmodule.py b/repos/system_upgrade/common/libraries/dnflibs/tests/test_dnfmodule.py -new file mode 100644 -index 00000000..82ce62c7 ---- /dev/null -+++ b/repos/system_upgrade/common/libraries/dnflibs/tests/test_dnfmodule.py -@@ -0,0 +1,270 @@ -+""" -+Trivial unit testing of dnfmodule library => rely on integration tests. -+ -+There is actually not a good coverage we could do for this library via -+unit-tests. These tests seem to have a low value as basically DNF & hawkey -+libraries are completely mocked - and there is not a good way to write unit-tests -+without this full mocking. The library is covered by CI tests. -+""" -+ -+from leapp.libraries.common.dnflibs import dnfmodule -+ -+ -+class MockModulePackage: -+ def __init__(self, name, stream, artifacts=None): -+ self._name = name -+ self._stream = stream -+ self._artifacts = artifacts or [] -+ -+ def getName(self): -+ return self._name -+ -+ def getStream(self): -+ return self._stream -+ -+ def getArtifacts(self): -+ return self._artifacts -+ -+ -+class MockNEVRA: -+ def __init__(self, name, version, release, arch): -+ self.name = name -+ self.version = version -+ self.release = release -+ self.arch = arch -+ -+ -+class MockModuleBase: -+ def __init__(self, base): -+ self.base = base -+ self._modules = [] -+ -+ def get_modules(self, pattern): -+ return [self._modules, None] -+ -+ -+class MockModuleContainer: -+ def __init__(self): -+ self._enabled = set() -+ -+ def isEnabled(self, module): -+ return module.getName() in self._enabled -+ -+ def enable(self, module_name): -+ self._enabled.add(module_name) -+ -+ -+class MockSack: -+ def __init__(self): -+ self._moduleContainer = MockModuleContainer() -+ -+ -+class MockDNFBase: -+ def __init__(self): -+ self.sack = MockSack() -+ -+ -+def _split_nevra(rpm_str): -+ """Simple NEVRA parser for testing""" -+ parts = rpm_str.rsplit('.', 1) -+ arch = parts[-1] if len(parts) > 1 else 'noarch' -+ rest = parts[0] if len(parts) > 1 else rpm_str -+ -+ parts = rest.rsplit('-', 2) -+ if len(parts) >= 3: -+ name, version, release = parts[0], parts[1], parts[2] -+ elif len(parts) == 2: -+ name, version, release = parts[0], parts[1], '0' -+ else: -+ name, version, release = parts[0], '0', '0' -+ -+ return MockNEVRA(name, version, release, arch) -+ -+ -+def test_get_modules_with_base(monkeypatch): -+ module1 = MockModulePackage('nodejs', '18') -+ module2 = MockModulePackage('postgresql', '15') -+ -+ base = MockDNFBase() -+ -+ class ModuleBaseWithModules(MockModuleBase): -+ def __init__(self, base): -+ super().__init__(base) -+ self._modules = [module1, module2] -+ -+ class MockDNF: -+ class module: -+ class module_base: -+ ModuleBase = ModuleBaseWithModules -+ -+ monkeypatch.setattr(dnfmodule, 'dnf', MockDNF) -+ -+ modules = dnfmodule.get_modules(base) -+ -+ assert len(modules) == 2 -+ assert modules[0].getName() == 'nodejs' -+ assert modules[1].getName() == 'postgresql' -+ -+ -+def test_get_modules_without_base(monkeypatch): -+ module1 = MockModulePackage('nodejs', '18') -+ -+ def mock_create_dnf_base(): -+ return MockDNFBase() -+ -+ class ModuleBaseWithModules(MockModuleBase): -+ def __init__(self, base): -+ super().__init__(base) -+ self._modules = [module1] -+ -+ class MockDNF: -+ class module: -+ class module_base: -+ ModuleBase = ModuleBaseWithModules -+ -+ monkeypatch.setattr(dnfmodule, 'create_dnf_base', mock_create_dnf_base) -+ monkeypatch.setattr(dnfmodule, 'dnf', MockDNF) -+ -+ modules = dnfmodule.get_modules() -+ -+ assert len(modules) == 1 -+ assert modules[0].getName() == 'nodejs' -+ -+ -+def test_get_modules_no_get_modules_method(monkeypatch): -+ """ -+ Test the compatibility for DNF versions without module stream functionality. -+ -+ This is a known case for RHEL 7, which we do not support in upstream anymore. -+ However, it's possible that future versions of DNF will drop module stream -+ functionality as well. So keeping this test for now, but the functionality -+ can be "broken" in different ways from the one we test at this moment. -+ """ -+ base = MockDNFBase() -+ -+ class LimitedModuleBase: -+ def __init__(self, base): -+ self.base = base -+ -+ class MockDNF: -+ class module: -+ class module_base: -+ ModuleBase = LimitedModuleBase -+ -+ monkeypatch.setattr(dnfmodule, 'dnf', MockDNF) -+ -+ modules = dnfmodule.get_modules(base) -+ -+ assert modules == [] -+ -+ -+def test_get_enabled_modules(monkeypatch): -+ module1 = MockModulePackage('nodejs', '18') -+ module2 = MockModulePackage('postgresql', '15') -+ module3 = MockModulePackage('ruby', '3.0') -+ -+ def mock_create_dnf_base(): -+ base = MockDNFBase() -+ base.sack._moduleContainer.enable('nodejs') -+ base.sack._moduleContainer.enable('ruby') -+ return base -+ -+ class ModuleBaseWithModules(MockModuleBase): -+ def __init__(self, base): -+ super().__init__(base) -+ self._modules = [module1, module2, module3] -+ -+ class MockDNF: -+ class module: -+ class module_base: -+ ModuleBase = ModuleBaseWithModules -+ -+ monkeypatch.setattr(dnfmodule, 'create_dnf_base', mock_create_dnf_base) -+ monkeypatch.setattr(dnfmodule, 'dnf', MockDNF) -+ -+ enabled = dnfmodule.get_enabled_modules() -+ -+ assert len(enabled) == 2 -+ enabled_names = [m.getName() for m in enabled] -+ assert 'nodejs' in enabled_names -+ assert 'ruby' in enabled_names -+ assert 'postgresql' not in enabled_names -+ -+ -+def test_get_enabled_modules_no_dnf(monkeypatch): -+ monkeypatch.setattr(dnfmodule, 'dnf', None) -+ -+ enabled = dnfmodule.get_enabled_modules() -+ -+ assert enabled == [] -+ -+ -+def test_map_installed_rpms_to_modules(monkeypatch): -+ module1 = MockModulePackage('nodejs', '18', artifacts=[ -+ 'nodejs-18.0.0-1.x86_64', -+ 'npm-8.0.0-1.x86_64' -+ ]) -+ module2 = MockModulePackage('postgresql', '15', artifacts=[ -+ 'postgresql-15.0-1.x86_64', -+ 'postgresql-server-15.0-1.x86_64' -+ ]) -+ -+ class ModuleBaseWithModules(MockModuleBase): -+ def __init__(self, base): -+ super().__init__(base) -+ self._modules = [module1, module2] -+ -+ class MockDNF: -+ class module: -+ class module_base: -+ ModuleBase = ModuleBaseWithModules -+ -+ class MockHawkey: -+ split_nevra = staticmethod(_split_nevra) -+ -+ def mock_create_dnf_base(): -+ return MockDNFBase() -+ -+ monkeypatch.setattr(dnfmodule, 'create_dnf_base', mock_create_dnf_base) -+ monkeypatch.setattr(dnfmodule, 'dnf', MockDNF) -+ monkeypatch.setattr(dnfmodule, 'hawkey', MockHawkey) -+ -+ rpm_map = dnfmodule.map_installed_rpms_to_modules() -+ -+ assert ('nodejs', '18.0.0', '1', 'x86_64') in rpm_map -+ assert rpm_map[('nodejs', '18.0.0', '1', 'x86_64')] == ('nodejs', '18') -+ -+ assert ('npm', '8.0.0', '1', 'x86_64') in rpm_map -+ assert rpm_map[('npm', '8.0.0', '1', 'x86_64')] == ('nodejs', '18') -+ -+ assert ('postgresql', '15.0', '1', 'x86_64') in rpm_map -+ assert rpm_map[('postgresql', '15.0', '1', 'x86_64')] == ('postgresql', '15') -+ -+ assert ('postgresql-server', '15.0', '1', 'x86_64') in rpm_map -+ assert rpm_map[('postgresql-server', '15.0', '1', 'x86_64')] == ('postgresql', '15') -+ -+ -+def test_map_installed_rpms_to_modules_empty(monkeypatch): -+ class EmptyModuleBase(MockModuleBase): -+ def __init__(self, base): -+ super().__init__(base) -+ self._modules = [] -+ -+ class MockDNF: -+ class module: -+ class module_base: -+ ModuleBase = EmptyModuleBase -+ -+ class MockHawkey: -+ split_nevra = staticmethod(_split_nevra) -+ -+ def mock_create_dnf_base(): -+ return MockDNFBase() -+ -+ monkeypatch.setattr(dnfmodule, 'create_dnf_base', mock_create_dnf_base) -+ monkeypatch.setattr(dnfmodule, 'dnf', MockDNF) -+ monkeypatch.setattr(dnfmodule, 'hawkey', MockHawkey) -+ -+ rpm_map = dnfmodule.map_installed_rpms_to_modules() -+ -+ assert rpm_map == {} --- -2.54.0 - diff --git a/0073-dnflibs.dnfconfig-Raise-proper-exceptions-and-add-te.patch b/0073-dnflibs.dnfconfig-Raise-proper-exceptions-and-add-te.patch deleted file mode 100644 index 626f08d..0000000 --- a/0073-dnflibs.dnfconfig-Raise-proper-exceptions-and-add-te.patch +++ /dev/null @@ -1,363 +0,0 @@ -From bcc445b165cfd63ae659f699209fff10e6aa8c42 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Fri, 24 Apr 2026 15:52:07 +0200 -Subject: [PATCH 073/105] dnflibs.dnfconfig: Raise proper exceptions and add - tests - -Originally the library raised StopActorExecutionError or -CalledProcessError exceptions in case of problems. This change -introduces new exceptions to make clear what problem actually happens. -New exceptions are based on DNFError (which is based on -StopActorExecutionError, to ensure that errors are caught and handled -properly). - -List of new exceptions: - * InvalidDNFConfig - * CannotObtainDNFConfig - * CannotUpdateDNFConfig (replacing CalledProcessError) - -Assisted-by: Claude Sonnet 4.5 -Signed-off-by: Petr Stodulka ---- - .../common/libraries/dnflibs/dnfconfig.py | 46 +++- - .../libraries/dnflibs/tests/test_dnfconfig.py | 228 ++++++++++++++++++ - 2 files changed, 266 insertions(+), 8 deletions(-) - create mode 100644 repos/system_upgrade/common/libraries/dnflibs/tests/test_dnfconfig.py - -diff --git a/repos/system_upgrade/common/libraries/dnflibs/dnfconfig.py b/repos/system_upgrade/common/libraries/dnflibs/dnfconfig.py -index 9f1902b6..8872e592 100644 ---- a/repos/system_upgrade/common/libraries/dnflibs/dnfconfig.py -+++ b/repos/system_upgrade/common/libraries/dnflibs/dnfconfig.py -@@ -1,8 +1,26 @@ --from leapp.exceptions import StopActorExecutionError -+from leapp.libraries.common.dnflibs import DNFError - from leapp.libraries.common.rpms import get_leapp_packages - from leapp.libraries.stdlib import api, CalledProcessError - - -+class InvalidDNFConfig(DNFError): -+ """ -+ Raised when DNF configuration is invalid. -+ """ -+ -+ -+class CannotObtainDNFConfig(DNFError): -+ """ -+ Raised when cannot obtain DNF configuration. -+ """ -+ -+ -+class CannotUpdateDNFConfig(DNFError): -+ """ -+ Raised when the DNF configuration could not be updated. -+ """ -+ -+ - def _strip_split(data, sep, maxsplit=-1): - """ - Just like str.split(), but remove ambient whitespaces from all items -@@ -14,7 +32,7 @@ def _get_main_dump(context, disable_plugins): - """ - Return the dnf configuration dump of main options for the given context. - -- Returns the list of lines after the line with "[main]" section -+ :rtype: dict - """ - - cmd = ['dnf', 'config-manager', '--dump'] -@@ -27,7 +45,7 @@ def _get_main_dump(context, disable_plugins): - data = context.call(cmd, split=True)['stdout'] - except CalledProcessError as e: - api.current_logger().error('Cannot obtain the dnf configuration') -- raise StopActorExecutionError( -+ raise CannotObtainDNFConfig( - message='Cannot obtain data about the DNF configuration', - details={'stdout': e.stdout, 'stderr': e.stderr} - ) -@@ -36,7 +54,7 @@ def _get_main_dump(context, disable_plugins): - # return index of the first item in the main section - main_start = data.index('[main]') + 1 - except ValueError: -- raise StopActorExecutionError( -+ raise InvalidDNFConfig( - message='Invalid DNF configuration data (missing [main])', - details=data, - ) -@@ -76,7 +94,7 @@ def _set_excluded_pkgs(context, pkglist, disable_plugins): - """ - Configure DNF to exclude packages in the given list - -- Raise the CalledProcessError on error. -+ :raises CannotUpdateDNFConfig: When an attempt to update DNF configuration fails - """ - exclude = 'exclude={}'.format(','.join(pkglist)) - cmd = ['dnf', 'config-manager', '--save', '--setopt', exclude] -@@ -87,9 +105,12 @@ def _set_excluded_pkgs(context, pkglist, disable_plugins): - - try: - context.call(cmd) -- except CalledProcessError: -- api.current_logger().error('Cannot set the dnf configuration') -- raise -+ except CalledProcessError as e: -+ api.current_logger().error('Cannot update the dnf configuration') -+ raise CannotUpdateDNFConfig( -+ message='Cannot update the DNF configuration to exclude RPMs of the upgrade tooling.', -+ details={'stdout': e.stdout, 'stderr': e.stderr} -+ ) - api.current_logger().debug('The DNF configuration has been updated to exclude leapp packages.') - - -@@ -103,6 +124,15 @@ def exclude_leapp_rpms(context, disable_plugins): - - on the host system - So user will have to drop these packages from the exclude after the - upgrade. -+ -+ :param context: The execution context -+ :type context: mounting.IsolatedActions -+ :param disable_plugins: List of plugins supposed to be disabled during DNF execution -+ :type disable_plugins: list -+ :raises InvalidDNFConfig: When the discovered DNF configuratino is invalid. -+ E.g. the [main] section is missing. -+ :raises CannotObtainDNFConfig: The DNF call `dnf config-manager --dump` failed. -+ :raises CannotUpdateDNFConfig: The DNF call to update its configuration failed. - """ - to_exclude = list(set(_get_excluded_pkgs(context, disable_plugins) + get_leapp_packages())) - _set_excluded_pkgs(context, to_exclude, disable_plugins) -diff --git a/repos/system_upgrade/common/libraries/dnflibs/tests/test_dnfconfig.py b/repos/system_upgrade/common/libraries/dnflibs/tests/test_dnfconfig.py -new file mode 100644 -index 00000000..8cfa75ba ---- /dev/null -+++ b/repos/system_upgrade/common/libraries/dnflibs/tests/test_dnfconfig.py -@@ -0,0 +1,228 @@ -+import pytest -+ -+from leapp.libraries.common.dnflibs import dnfconfig -+from leapp.libraries.common.testutils import logger_mocked -+from leapp.libraries.stdlib import api, CalledProcessError -+ -+ -+class MockContext: -+ def __init__(self, stdout=None, should_raise=None): -+ self.stdout = stdout or [] -+ self.should_raise = should_raise -+ self.commands = [] -+ -+ @property -+ def last_cmd(self): -+ return self.commands[-1] if self.commands else None -+ -+ def call(self, cmd, split=False): -+ self.commands.append(cmd) -+ if self.should_raise: -+ raise self.should_raise -+ if split: -+ return {'stdout': self.stdout} -+ # TODO(pstodulk): this part is not used now, but if we want to make -+ # this in future more generic, we should update it properly -+ return None -+ -+ -+_SAMPLE_DNF_DUMP = [ -+ '[main]', -+ 'gpgcheck = 1', -+ 'installonly_limit = 3', -+ 'clean_requirements_on_remove = True', -+ 'exclude = pkg1,pkg2', -+] -+ -+ -+@pytest.mark.parametrize('data,sep,maxsplit,expected', [ -+ ('key = value', '=', -1, ['key', 'value']), -+ (' key = value ', '=', -1, ['key', 'value']), -+ ('a,b,c,d', ',', 2, ['a', 'b', 'c,d']), -+ ('pkg1, pkg2, pkg3', ',', -1, ['pkg1', 'pkg2', 'pkg3']), -+]) -+def test_strip_split(data, sep, maxsplit, expected): -+ result = dnfconfig._strip_split(data, sep, maxsplit) -+ assert result == expected -+ -+ -+def test_get_main_dump_success(monkeypatch): -+ context = MockContext(stdout=_SAMPLE_DNF_DUMP) -+ monkeypatch.setattr(api, 'current_logger', logger_mocked()) -+ -+ result = dnfconfig._get_main_dump(context, disable_plugins=None) -+ -+ assert 'gpgcheck' in result -+ assert result['gpgcheck'] == '1' -+ assert result['installonly_limit'] == '3' -+ assert result['exclude'] == 'pkg1,pkg2' -+ -+ -+def test_get_main_dump_with_disabled_plugins(): -+ context = MockContext(stdout=_SAMPLE_DNF_DUMP) -+ -+ dnfconfig._get_main_dump(context, disable_plugins=['plugin1', 'plugin2']) -+ -+ expected_cmd = [ -+ 'dnf', 'config-manager', '--dump', -+ '--disableplugin', 'plugin1', -+ '--disableplugin', 'plugin2' -+ ] -+ assert context.last_cmd == expected_cmd -+ -+ -+def test_get_main_dump_command_fails(monkeypatch): -+ err = CalledProcessError( -+ 'Command failed', -+ ['dnf', 'config-manager', '--dump'], -+ {'stdout': 'stdout output', 'stderr': 'stderr output', 'exit_code': 1} -+ ) -+ context = MockContext(should_raise=err) -+ -+ monkeypatch.setattr(api, 'current_logger', logger_mocked()) -+ -+ with pytest.raises(dnfconfig.CannotObtainDNFConfig) as exc_info: -+ dnfconfig._get_main_dump(context, disable_plugins=None) -+ -+ assert 'Cannot obtain data about the DNF configuration' in str(exc_info.value) -+ assert exc_info.value.details['stdout'] == 'stdout output' -+ assert exc_info.value.details['stderr'] == 'stderr output' -+ -+ -+def test_get_main_dump_missing_main_section(): -+ stdout = [ -+ '[repo1]', -+ 'name = Repository 1', -+ 'enabled = 1' -+ ] -+ context = MockContext(stdout=stdout) -+ -+ with pytest.raises(dnfconfig.InvalidDNFConfig) as exc_info: -+ dnfconfig._get_main_dump(context, disable_plugins=None) -+ -+ assert 'Invalid DNF configuration data (missing [main])' in str(exc_info.value) -+ -+ -+def test_get_main_dump_malformed_line(monkeypatch): -+ stdout = [ -+ '[main]', -+ 'gpgcheck = 1', -+ 'malformed line without equals', -+ 'exclude = pkg1' -+ ] -+ context = MockContext(stdout=stdout) -+ -+ mocked_logger = logger_mocked() -+ monkeypatch.setattr(api, 'current_logger', mocked_logger) -+ -+ result = dnfconfig._get_main_dump(context, disable_plugins=None) -+ -+ assert result['gpgcheck'] == '1' -+ assert result['exclude'] == 'pkg1' -+ assert len(mocked_logger.warnmsg) > 0 -+ assert 'malformed line without equals' in mocked_logger.warnmsg[0] -+ -+ -+@pytest.mark.parametrize('exclude_value,expected', [ -+ ('pkg1, pkg2, pkg3', ['pkg1', 'pkg2', 'pkg3']), -+ ('pkg1,pkg2, pkg3', ['pkg1', 'pkg2', 'pkg3']), -+ ('', []), -+ ('pkg1, , pkg2, ', ['pkg1', 'pkg2']), -+]) -+def test_get_excluded_pkgs(exclude_value, expected): -+ stdout = ['[main]', 'exclude = {}'.format(exclude_value)] if exclude_value else ['[main]', 'gpgcheck = 1'] -+ context = MockContext(stdout=stdout) -+ -+ result = dnfconfig._get_excluded_pkgs(context, disable_plugins=None) -+ -+ assert result == expected -+ -+ -+def test_set_excluded_pkgs_success(): -+ context = MockContext() -+ pkglist = ['pkg1', 'pkg2', 'pkg3'] -+ -+ dnfconfig._set_excluded_pkgs(context, pkglist, disable_plugins=None) -+ -+ expected_cmd = [ -+ 'dnf', 'config-manager', '--save', -+ '--setopt', 'exclude=pkg1,pkg2,pkg3' -+ ] -+ assert context.last_cmd == expected_cmd -+ -+ -+def test_set_excluded_pkgs_with_disabled_plugins(): -+ context = MockContext() -+ -+ dnfconfig._set_excluded_pkgs(context, ['pkg1'], disable_plugins=['plugin1', 'plugin2']) -+ -+ expected_cmd = [ -+ 'dnf', 'config-manager', '--save', -+ '--setopt', 'exclude=pkg1', -+ '--disableplugin', 'plugin1', -+ '--disableplugin', 'plugin2' -+ ] -+ assert context.last_cmd == expected_cmd -+ -+ -+def test_set_excluded_pkgs_fails(monkeypatch): -+ err = CalledProcessError( -+ 'Command failed', -+ ['dnf', 'config-manager', '--save'], -+ {'stdout': 'stdout output', 'stderr': 'stderr output', 'exit_code': 1} -+ ) -+ context = MockContext(should_raise=err) -+ -+ monkeypatch.setattr(api, 'current_logger', logger_mocked()) -+ -+ with pytest.raises(dnfconfig.CannotUpdateDNFConfig) as exc_info: -+ dnfconfig._set_excluded_pkgs(context, ['pkg1'], disable_plugins=None) -+ -+ assert 'Cannot update the DNF configuration' in str(exc_info.value) -+ assert exc_info.value.details['stdout'] == 'stdout output' -+ assert exc_info.value.details['stderr'] == 'stderr output' -+ -+ -+def test_exclude_leapp_rpms(monkeypatch): -+ """ -+ Test that exclude_leapp_rpms merges existing exclusions with leapp packages -+ """ -+ context = MockContext(stdout=[ -+ '[main]', -+ 'exclude = existing-pkg1, existing-pkg2' -+ ]) -+ monkeypatch.setattr(dnfconfig, 'get_leapp_packages', lambda: ['leapp', 'leapp-upgrade-el8toel9']) -+ -+ dnfconfig.exclude_leapp_rpms(context, disable_plugins=None) -+ -+ setopt_cmd = None -+ for cmd in context.commands: -+ if '--setopt' in cmd: -+ setopt_cmd = cmd -+ break -+ -+ assert setopt_cmd is not None -+ exclude_arg = set([arg for arg in setopt_cmd if arg.startswith('exclude=')][0].split('=')[1].split(',')) -+ expected_arg = set(['existing-pkg1', 'existing-pkg2', 'leapp', 'leapp-upgrade-el8toel9']) -+ assert expected_arg == exclude_arg -+ -+ -+def test_exclude_leapp_rpms_no_duplicates(monkeypatch): -+ context = MockContext(stdout=[ -+ '[main]', -+ 'exclude = leapp' -+ ]) -+ monkeypatch.setattr(dnfconfig, 'get_leapp_packages', lambda: ['leapp', 'leapp-upgrade-el8toel9']) -+ -+ dnfconfig.exclude_leapp_rpms(context, disable_plugins=None) -+ -+ setopt_cmd = None -+ for cmd in context.commands: -+ if '--setopt' in cmd: -+ setopt_cmd = cmd -+ break -+ -+ assert setopt_cmd is not None -+ exclude_arg = [arg for arg in setopt_cmd if arg.startswith('exclude=')][0] -+ packages = exclude_arg.replace('exclude=', '').split(',') -+ assert packages.count('leapp') == 1 --- -2.54.0 - diff --git a/0074-DOCSTRINGS-Document-DNF-related-exception-propagatio.patch b/0074-DOCSTRINGS-Document-DNF-related-exception-propagatio.patch deleted file mode 100644 index 80d0314..0000000 --- a/0074-DOCSTRINGS-Document-DNF-related-exception-propagatio.patch +++ /dev/null @@ -1,57 +0,0 @@ -From cf1a6cf5d890573a888a067196bc69fec3b23d4a Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Mon, 27 Apr 2026 20:45:31 +0000 -Subject: [PATCH 074/105] DOCSTRINGS: Document DNF related exception - propagation - -Update docstrings in actors that use dnflibs libraries to make -make clear what exceptions can be potentially raised: - * rpmscanner - * upgradeinitramfsgenerator - -Assisted-by: Claude Sonnet 4.5 -Signed-off-by: Petr Stodulka ---- - .../libraries/upgradeinitramfsgenerator.py | 8 ++++++++ - .../common/actors/rpmscanner/libraries/rpmscanner.py | 6 ++++++ - 2 files changed, 14 insertions(+) - -diff --git a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py -index aec1debf..24f35847 100644 ---- a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py -+++ b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py -@@ -169,6 +169,14 @@ def _get_dracut_modules(): - - - def _install_initram_deps(packages): -+ """ -+ Install initramfs dependencies into the target userspace. -+ -+ :param packages: List of package names to install -+ -+ .. seealso:: -+ :func:`leapp.libraries.common.dnflibs.dnfplugin.install_initramdisk_requirements` -+ """ - used_repos = api.consume(UsedTargetRepositories) - target_userspace_info = next(api.consume(TargetUserSpaceInfo), None) - -diff --git a/repos/system_upgrade/common/actors/rpmscanner/libraries/rpmscanner.py b/repos/system_upgrade/common/actors/rpmscanner/libraries/rpmscanner.py -index ebf17e19..100fb32c 100644 ---- a/repos/system_upgrade/common/actors/rpmscanner/libraries/rpmscanner.py -+++ b/repos/system_upgrade/common/actors/rpmscanner/libraries/rpmscanner.py -@@ -99,6 +99,12 @@ def get_package_repository_data(): - def map_modular_rpms_to_modules(): - """ - Map modular packages to the module streams they come from. -+ -+ :returns: Mapping of RPM NEVRA tuples to (module_name, stream) tuples -+ :rtype: dict -+ -+ .. seealso:: -+ :func:`leapp.libraries.common.dnflibs.dnfmodule.get_modules` - """ - modules = dnfmodule.get_modules() - # empty on RHEL 7 because of no modules --- -2.54.0 - diff --git a/0075-pulseaudiocheck-fix-for-users-with-None-home-directo.patch b/0075-pulseaudiocheck-fix-for-users-with-None-home-directo.patch deleted file mode 100644 index 72807cb..0000000 --- a/0075-pulseaudiocheck-fix-for-users-with-None-home-directo.patch +++ /dev/null @@ -1,87 +0,0 @@ -From 545f15a053b6dc588f41a5326964a9423d4a7857 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jozef=20Ba=C4=8D=C3=ADk?= -Date: Mon, 11 May 2026 12:21:31 +0200 -Subject: [PATCH 075/105] pulseaudiocheck: fix for users with "None" home - directory - -If the user has single entry in /etc/passwd without any colons, the reported -pw_dir is NoneType. This makes the os.path.join() crash as NoneType is -unexpected. -While technically, entry without the colons is considered invalid and not POSIX -compliant, it has been seen that some customers are using such entries and leapp -is already taking this possibility into account. -This commit modifies the behaviour of the scanpulseaudio actor, so such entries -are logged and skipped. - -Co-authored-by: Matej Matuska ---- - .../scanpulseaudio/libraries/scanpulseaudio.py | 4 ++++ - .../tests/test_scanpulseaudio.py | 18 ++++++++++++++---- - 2 files changed, 18 insertions(+), 4 deletions(-) - -diff --git a/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/scanpulseaudio/libraries/scanpulseaudio.py b/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/scanpulseaudio/libraries/scanpulseaudio.py -index e5c2be53..28cde062 100644 ---- a/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/scanpulseaudio/libraries/scanpulseaudio.py -+++ b/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/scanpulseaudio/libraries/scanpulseaudio.py -@@ -2,6 +2,7 @@ import os - import pwd - - from leapp.libraries.common.rpms import check_file_modification -+from leapp.libraries.stdlib import api - from leapp.models import PulseAudioConfiguration - - # System-wide PulseAudio configuration directory -@@ -54,6 +55,9 @@ def _get_user_config_dirs(): - """ - found = [] - for user in pwd.getpwall(): -+ if not user.pw_dir: -+ api.current_logger().debug('User "{}" has no valid home entry, skipping.'.format(user.pw_name)) -+ continue - pulse_dir = os.path.join(user.pw_dir, _USER_CONFIG_SUBDIR) - if os.path.isdir(pulse_dir) and os.listdir(pulse_dir): - found.append(pulse_dir) -diff --git a/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/scanpulseaudio/tests/test_scanpulseaudio.py b/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/scanpulseaudio/tests/test_scanpulseaudio.py -index f499aafe..e6b63a4c 100644 ---- a/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/scanpulseaudio/tests/test_scanpulseaudio.py -+++ b/repos/system_upgrade/el9toel10/actors/pulseaudiocheck/scanpulseaudio/tests/test_scanpulseaudio.py -@@ -1,5 +1,7 @@ - import os - -+import pytest -+ - from leapp.libraries.actor import scanpulseaudio - from leapp.libraries.actor.scanpulseaudio import _get_dropin_dirs_with_content, _get_user_config_dirs, scan_pulseaudio - -@@ -36,16 +38,24 @@ class TestGetUserConfigDirs: - monkeypatch.setattr(os.path, 'isdir', lambda _: False) - assert _get_user_config_dirs() == [] - -- def test_user_config_found(self, monkeypatch): -+ @pytest.mark.parametrize( -+ 'home_dir, expect', -+ [ -+ ('/home/testuser', ['/home/testuser/.config/pulse']), -+ ('', []), -+ ], -+ ) -+ def test_user_config_found(self, monkeypatch, home_dir, expect): - monkeypatch.setattr(os.path, 'isdir', lambda path: path == '/home/testuser/.config/pulse') - monkeypatch.setattr(os, 'listdir', lambda _: ['default.pa']) - - class FakeUser: -- pw_dir = '/home/testuser' -+ pw_name = 'testuser' -+ pw_dir = home_dir - -- monkeypatch.setattr(scanpulseaudio.pwd, 'getpwall', lambda: [FakeUser()]) -+ monkeypatch.setattr(scanpulseaudio.pwd, 'getpwall', lambda: [FakeUser]) - result = _get_user_config_dirs() -- assert result == ['/home/testuser/.config/pulse'] -+ assert result == expect - - - class TestScanPulseaudio: --- -2.54.0 - diff --git a/0076-fix-target_initramfs-always-regenerate-initramfs-153.patch b/0076-fix-target_initramfs-always-regenerate-initramfs-153.patch deleted file mode 100644 index e8f403d..0000000 --- a/0076-fix-target_initramfs-always-regenerate-initramfs-153.patch +++ /dev/null @@ -1,68 +0,0 @@ -From 03e67c0d42914b40cac1bff390bd27b9c7b91d04 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Michal=20He=C4=8Dko?= -Date: Wed, 13 May 2026 11:51:48 +0200 -Subject: [PATCH 076/105] fix(target_initramfs): always regenerate initramfs - (#1536) - -Remove conditions that would skip target initramfs regeneration if there -are no dracut or kernel modules to be included. Instead, regenerate the -target initramfs always, as there might be new configs written by us -after the upgrade RPM transaction installs the target kernel and -generates a corresponding initramfs. The initramfs generated during RPM -transaction, therefore lacks or contains invalid config files. - -Jira: RHEL-151509 ---- - .../libraries/targetinitramfsgenerator.py | 12 +++++++----- - .../tests/test_targetinitramfsgenerator.py | 5 ++--- - 2 files changed, 9 insertions(+), 8 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/initramfs/targetinitramfsgenerator/libraries/targetinitramfsgenerator.py b/repos/system_upgrade/common/actors/initramfs/targetinitramfsgenerator/libraries/targetinitramfsgenerator.py -index edfb42ce..e3b9352e 100644 ---- a/repos/system_upgrade/common/actors/initramfs/targetinitramfsgenerator/libraries/targetinitramfsgenerator.py -+++ b/repos/system_upgrade/common/actors/initramfs/targetinitramfsgenerator/libraries/targetinitramfsgenerator.py -@@ -97,14 +97,16 @@ def _get_modules(): - - - def process(): -+ """ -+ Regenerate target system initramfs. -+ -+ The initramfs is regenerated unconditionally always as there might be new configs -+ necessary for boot which are written by us after the RPM transaction installs -+ the target kernel (and generates the target initramfs for the first time). -+ """ - files = _get_files() - modules = _get_modules() - -- if not files and not modules['kernel'] and not modules['dracut']: -- api.current_logger().debug( -- 'No additional files or modules required to add into the target initramfs.') -- return -- - target_kernel_info = next(api.consume(InstalledTargetKernelInfo), None) - if not target_kernel_info: - raise StopActorExecutionError( -diff --git a/repos/system_upgrade/common/actors/initramfs/targetinitramfsgenerator/tests/test_targetinitramfsgenerator.py b/repos/system_upgrade/common/actors/initramfs/targetinitramfsgenerator/tests/test_targetinitramfsgenerator.py -index 4df9a485..d21cdce8 100644 ---- a/repos/system_upgrade/common/actors/initramfs/targetinitramfsgenerator/tests/test_targetinitramfsgenerator.py -+++ b/repos/system_upgrade/common/actors/initramfs/targetinitramfsgenerator/tests/test_targetinitramfsgenerator.py -@@ -71,13 +71,12 @@ def gen_InitrdIncludes(files): - - def test_no_includes(monkeypatch): - run_mocked = RunMocked() -- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[])) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[mk_kernel_info(KERNEL_VERSION)])) - monkeypatch.setattr(api, 'current_logger', logger_mocked()) - monkeypatch.setattr(targetinitramfsgenerator, 'run', run_mocked) - - targetinitramfsgenerator.process() -- assert NO_INCLUDE_MSG in api.current_logger.dbgmsg -- assert not run_mocked.called -+ assert run_mocked.called - - - TEST_CASES = [ --- -2.54.0 - diff --git a/0077-Add-AI-assistant-harness-with-AGENTS.md-and-task-ski.patch b/0077-Add-AI-assistant-harness-with-AGENTS.md-and-task-ski.patch deleted file mode 100644 index 552bb9b..0000000 --- a/0077-Add-AI-assistant-harness-with-AGENTS.md-and-task-ski.patch +++ /dev/null @@ -1,393 +0,0 @@ -From af876b2deb797606089ed309c551b674e2ba0345 Mon Sep 17 00:00:00 2001 -From: karolinku -Date: Mon, 11 May 2026 09:56:27 +0200 -Subject: [PATCH 077/105] Add AI assistant harness with AGENTS.md and task - skills - -Introduce an AI assistant harness to guide agents and human -developers when working with coding tools with this repo. -AGENTS.md serves as a compact always-loaded entrypoint with project -boundaries, architecture, phases, general rules and a definition of -done. Detailed procedure guidance is split into skill files to -minimize token consumption. -Skills added: -- leapp-test-runner: container-based testing, linting, failure triage -- leapp-unit-tests: test patterns, testutils API, mocking conventions -- leapp-actor-dev: actor structure, phase selection, placement rules ---- - AGENTS.md | 81 ++++++++++++++++++++++++++ - skills/leapp-actor-dev/SKILL.md | 95 +++++++++++++++++++++++++++++++ - skills/leapp-test-runner/SKILL.md | 68 ++++++++++++++++++++++ - skills/leapp-unit-tests/SKILL.md | 94 ++++++++++++++++++++++++++++++ - 4 files changed, 338 insertions(+) - create mode 100644 AGENTS.md - create mode 100644 skills/leapp-actor-dev/SKILL.md - create mode 100644 skills/leapp-test-runner/SKILL.md - create mode 100644 skills/leapp-unit-tests/SKILL.md - -diff --git a/AGENTS.md b/AGENTS.md -new file mode 100644 -index 00000000..5de224bc ---- /dev/null -+++ b/AGENTS.md -@@ -0,0 +1,81 @@ -+# AGENTS.md -+ -+## Project objective -+ -+[Leapp-repository](https://github.com/oamg/leapp-repository) contains the actors, models, and libraries for RHEL in-place upgrades (IPU). It runs on top of the [Leapp framework](https://github.com/oamg/leapp), which provides the actor runtime, workflow execution, messaging, and core CLI behavior. It's important to notice, that framework-level mechanics belong to leapp, while upgrade-path logic belong to leapp-repository. -+ -+## Architecture -+ -+Leapp IPU content is actor-based and message-driven: -+- Actors exchange typed messages (`consumes` / `produces`) through workflow phases. -+- Messages only flow forward: a producer must run before its consumer. Within the same phase, the framework resolves ordering automatically. -+- Prefer scanner/checker design: collect facts in one actor, evaluate in another. -+- Keep actor `process()` thin; place implementation logic in actor libraries for testability. -+ -+### Phase workflow (always apply) -+- `FactsCollectionPhase`: collect system facts and produce messages -+- `ChecksPhase`: consume previously collected facts, report/inhibit compatibility issues -+- If modification is required, do it in later phases (`ApplicationsPhase` or `ThirdPartyApplicationsPhase`). -+- `FinalPhase`: to perform actions after the upgraded system is booted. -+ -+For full phase details if ambiguous : -+- [Phases of the Upgrade Workflow](https://leapp-repository.readthedocs.io/latest/upgrade-architecture-and-workflow/phases-overview.html#phases-overview) -+ -+### Repository Layout -+ -+``` -+├── system_upgrade/ -+ ├── common/ # common repository/content used and accessible for all system upgrades -+ │ ├── actors// -+ │ │ ├── actor.py # Actor class definition -+ │ │ ├── libraries/ # Actor-private logic -+ │ │ └── tests/ # Actor tests (unit_test_*.py, test_*.py) -+ │ ├── models/ # Data models (consumed/produced by actors) -+ │ ├── topics/ # Message topics -+ │ └── libraries/ # Shared libraries (importable via leapp.libraries.common) -+ ├── el8toel9/ # content specific only for RHEL 8 -> 9 upgrades -+ └── el9toel10/ # content specific only for RHEL 9 -> 10 upgrades -+``` -+ -+ -+## General rules (always apply) -+ -+- Follow leapp specific guidelines: docs/source/contributing/coding-guidelines.md and project conventions first. -+- Follow Python coding guidelines: https://leapp.readthedocs.io/en/stable/contributing.html -+- Analyze and plan before introducing code. -+- Ask, don't assume. Be verbose when user input is unclear or can be interpreted in many ways. -+- Explain your decisions briefly; expand detail only when requested. -+- If multiple approaches or solution exists, pick simpler one. -+- Be ready to undo your changes. -+- Before introducing new code, check if it's not already solved in codebase. Avoid code duplication. -+- Try to write as simple code as possible. New code should easy to read and understand even for non-experienced developers. -+- Don't change code, which is not directly involved in given task. Don't try to improve unrelated code. -+- Don't perform git commit, git push, or create PRs without explicit user request. -+ -+ -+ -+## Common Commands -+ -+Quick reference (details in skills below): -+ -+```bash -+make lint # pylint + flake8 + isort -+TEST_CONTAINER=el9 make test_container # full CI-equivalent test -+ACTOR= make dev_test_no_lint # single-actor fast test -+``` -+ -+## Skills -+ -+Load the relevant skill for the task at hand: -+ -+- **Run tests / lint** → `skills/leapp-test-runner/` -+- **Write or fix unit tests** → `skills/leapp-unit-tests/` -+- **Develop or modify actors** → `skills/leapp-actor-dev/` -+ -+ -+## Definition of Done -+ -+- Scope is clear and limited to the requested change. -+- New or changed code is covered by unit tests. -+- Relevant checks were run (`make lint` and/or targeted tests) and results are reported. -+- Risks, assumptions, and follow-up work are explicitly called out -diff --git a/skills/leapp-actor-dev/SKILL.md b/skills/leapp-actor-dev/SKILL.md -new file mode 100644 -index 00000000..124f3e99 ---- /dev/null -+++ b/skills/leapp-actor-dev/SKILL.md -@@ -0,0 +1,95 @@ -+# Developing Leapp Actors -+ -+## Actor structure -+ -+Every actor follows layout: -+ -+``` -+repos/system_upgrade//actors// -+├── actor.py # Actor class (thin wrapper) -+├── libraries/ -+│ └── .py # Implementation logic -+└── tests/ -+ └── test_.py -+``` -+ -+Keep `actor.py` minimal — delegate the real implementation to the library for testability: -+ -+```python -+from leapp.actors import Actor -+from leapp.libraries.actor import -+from leapp.models import , -+from leapp.tags import , IPUWorkflowTag -+ -+ -+class MyActor(Actor): -+ """Brief description of what this actor does.""" -+ -+ name = '' -+ consumes = (,) -+ produces = (,) -+ tags = (IPUWorkflowTag, ) -+ -+ def process(self): -+ .process() -+``` -+ -+## Phase selection -+ -+| Intent | Phase tag | Rules | -+|--------|-----------|-------| -+| Collect system facts | `FactsPhaseTag` | Produce messages only, no decisions | -+| Check compatibility / inhibit | `ChecksPhaseTag` | Consume facts, no direct system interaction | -+| Modify application config | `ApplicationsPhaseTag` | Only after RPM upgrade is complete | -+| Modify third-party config | `ThirdPartyApplicationsPhaseTag` | Same as above, for non-Red Hat apps | -+ -+### Common multi-actor patterns -+ -+**Inhibiting an incompatible system** (2 actors): -+1. Scanner in `FactsCollectionPhase` — produces a facts message. -+2. Checker in `ChecksPhase` — consumes facts, creates report / inhibits. -+ -+**Modifying incompatible config** (3 actors): -+1. Scanner in `FactsCollectionPhase` — collects info. -+2. Checker in `ChecksPhase` — decides and reports planned change. -+3. Modifier in `ApplicationsPhase` — performs the change. -+ -+## Placement rules -+ -+- Reusable across upgrade paths → `repos/system_upgrade/common/` -+- Specific to one path → `repos/system_upgrade/el8toel9/` or `el9toel10/` -+- Same rule applies to models and shared libraries. -+ -+ -+## Before writing new code -+ -+- Search existing actors/models/libraries — reuse before creating. -+- Check if a model already provides the message you need (search model class names in `repos/`). -+- Discover `repos/system_upgrade/common/libraries/` for shared utilities. -+- Prefer using stdlib functions over shell commands. -+- If introduce envars, use LEAPP and LEAPP_DEVEL. -+- Write unit-testable code (see `skills/leapp-unit-tests/`). -+ -+ -+## Key constraints -+ -+- Avoid running code on a module level to avoid slow downs during this load phase -+- Never use `subprocess` — use `leapp.libraries.stdlib.run()` instead. -+- Never interact with the system during `ChecksPhase`. -+- Do not introduce new dependencies, unless it's strongly justified. -+ -+ -+## Python compatibility -+ -+| Repository | Required Python versions | -+|------------|--------------------------| -+| `common` | 3.6, 3.9, 3.12 | -+| `el8toel9` | 3.6, 3.9 | -+| `el9toel10`| 3.9, 3.12 | -+ -+## Reference -+ -+- [How to write an Actor for Leapp Upgrade](https://leapp-repository.readthedocs.io/latest/tutorials/howto-first-actor-upgrade.html) -+- [Phases of the Upgrade Workflow](https://leapp-repository.readthedocs.io/latest/upgrade-architecture-and-workflow/phases-overview.html) -+- [Coding guidelines](https://leapp-repository.readthedocs.io/latest/contributing/coding-guidelines.html) -+- [Best Practices for actor development](https://leapp.readthedocs.io/en/stable/best-practices.html) -diff --git a/skills/leapp-test-runner/SKILL.md b/skills/leapp-test-runner/SKILL.md -new file mode 100644 -index 00000000..579aeb5d ---- /dev/null -+++ b/skills/leapp-test-runner/SKILL.md -@@ -0,0 +1,68 @@ -+# Running Tests and Linters -+ -+All non-container commands (`make lint`, `make test_no_lint`, `make fast_lint`, `make dev_test_no_lint`) require a local virtualenv and `REPOSITORIES` envar (e.g. `REPOSITORIES=common,el9toel10`). Create the virtualenv with `make install-deps` (RHEL/CentOS) or `make install-deps-fedora` (Fedora) — the Makefile handles activation internally, don't source the venv manually. Set `PYTHON_VENV=pythonX.X` to choose the Python version (default: `python3.6`). Container commands are self-contained and recommended as the default. -+ -+## Container-based testing (CI-equivalent, no venv needed) -+ -+Three container options, each matching a target Python version: -+ -+| Container | Python | Used for | -+|-----------|--------|----------| -+| `el8` | 3.6 | `common`, `el8toel9` | -+| `el9` | 3.9 | `common`, `el8toel9`, `el9toel10` | -+| `f42` | 3.13 | lint-only (latest tooling) | -+ -+Pick container(s) based on which repositories your change touches: -+- `common` actors: test on `el8` **and** `el9` (both Python versions must pass). -+- `el8toel9` only: `el8` is sufficient, `el9` is a bonus. -+- `el9toel10` only: `el9`. -+ -+```bash -+# Full test (lint + unit tests) in container — recommended -+TEST_CONTAINER=el9 make test_container -+ -+# Tests only, skip lint — faster iteration -+TEST_CONTAINER=el9 make test_container_no_lint -+ -+# All containers at once -+make test_container_all -+``` -+ -+## Single-actor testing (fastest feedback, requires venv) -+ -+Use during development when iterating on one actor: -+ -+```bash -+ACTOR=checkmemory make dev_test_no_lint -+``` -+ -+## Linting -+ -+```bash -+# Lint in container (recommended, no venv needed) -+make lint_container -+ -+# Full lint locally (requires venv): pylint + flake8 + isort -+make lint -+ -+# Lint only local changes relative to main branch (requires venv) -+make fast_lint -+ -+# Auto-fix isort violations locally (requires venv) -+make lint_fix -+``` -+ -+## Choosing what to run -+ -+| Situation | Command | -+|-----------|---------| -+| Quick check during development | `ACTOR= make dev_test_no_lint` | -+| Before pushing | `TEST_CONTAINER=elX make test_container` | -+| Full CI confidence | `make test_container_all` | -+| Only lint issues | `make fast_lint` or `TEST_CONTAINER=elX make lint_container` | -+ -+## Interpreting failures -+ -+- **pylint / flake8**: fix the reported file and line. -+- **isort**: run `make lint_fix` to auto-sort imports, then verify. -+- **pytest**: read the traceback; check if the test uses `monkeypatch.setattr` correctly and if mocked models match current schema. -\ No newline at end of file -diff --git a/skills/leapp-unit-tests/SKILL.md b/skills/leapp-unit-tests/SKILL.md -new file mode 100644 -index 00000000..894d4271 ---- /dev/null -+++ b/skills/leapp-unit-tests/SKILL.md -@@ -0,0 +1,94 @@ -+# Writing Unit Tests -+ -+ -+## File placement -+ -+### Tests for actors -+ -+Tests live in the tests directory under an actor: -+ -+``` -+repos/system_upgrade//actors// -+├── actor.py -+├── libraries/.py -+└── tests/test_.py # <-- tests go here -+``` -+ -+### Tests for shared libraries -+ -+Tests for shared libraries are in the `tests` sub-directory next to the shared library, like: -+ -+``` -+repos/system_upgrade// -+├── .py -+├── tests/test_.py # <-- tests for libA go here -+├── /.py -+└── /tests/test_.py # <-- tests for libB go here -+``` -+ -+## Key testing utilities -+ -+As mocked objects, use functions and classes present in `leapp.libraries.common.testutils` - especially: -+ -+| Utility | Purpose | -+| ------------------------- | ---------------------------------------------------------- | -+| `CurrentActorMocked(...)` | Mock `api.current_actor` with configuration, version, etc. | -+| `produce_mocked()` | Capture `api.produce(...)` calls for assertions | -+| `create_report_mocked()` | Capture `reporting.create_report(...)` calls | -+| `logger_mocked()` | Capture log output | -+ -+ -+## Standard test pattern -+ -+Test the **library**, not the actor class directly: -+ -+```python -+from leapp.libraries.actor import -+from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked -+from leapp.libraries.stdlib import api -+from leapp.models import , -+ -+ -+def test_(monkeypatch): -+ # 1. Mock current_actor context -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) -+ -+ # 2. Mock consumed messages -+ monkeypatch.setattr(api, 'consume', lambda x: iter([(...)])) -+ -+ # 3. Mock produce -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ # 4. Call library function -+ .process() -+ -+ # 5. Assert on produced messages -+ assert api.produce.called -+ assert isinstance(api.produce.model_instances[0], ) -+``` -+ -+## Rules -+ -+ -+- Test the library, not `actor.py` — the `process()` method should be a thin wrapper. -+- Pass consumed messages via `CurrentActorMocked(msgs=[...])`. -+- Mock file I/O instead of creating temp files when possible. Use the `leapp_tmpdir` fixture when tempfiles are needed. -+- Use `monkeypatch.setattr` over `unittest.mock.patch` — monkeypatch is the project convention. -+- Cover at least: happy path, edge/error cases, and inhibitor conditions if applicable. -+- If you intentionally skip a test scenario, add a comment explaining why. -+- Use consistent string quoting within a file. -+- Use pytest.mark.parametrize where possible. -+- Never use type() when creating mock classes. -+- Use module-level constants with "_" prefix. -+ -+- Name test functions clearly after the behavior: `test_inhibits_when_fips_enabled` not `test_fips_1`. -+- Look at existing tests in `repos/system_upgrade/common/actors/` for project conventions before writing new ones. -+ -+## Running tests -+ -+See `skills/leapp-test-runner/` for full commands. Quick single-actor run: -+ -+```bash -+ACTOR= make dev_test_no_lint -+``` -+ --- -2.54.0 - diff --git a/0078-el8toel9-networkdeprecations-warn-about-ifcfg-device.patch b/0078-el8toel9-networkdeprecations-warn-about-ifcfg-device.patch deleted file mode 100644 index 44f2476..0000000 --- a/0078-el8toel9-networkdeprecations-warn-about-ifcfg-device.patch +++ /dev/null @@ -1,230 +0,0 @@ -From 3268f9a5cc41755bb2fecdbeb81f73dd788f74b4 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?=C3=8D=C3=B1igo=20Huguet?= -Date: Thu, 23 Apr 2026 09:25:39 +0200 -Subject: [PATCH 078/105] el8toel9: networkdeprecations: warn about ifcfg - devices not controlled by NM - -Network devices currently configured via ifcfg have to be configured by -NetworkManager in EL9. If NetworkManager is disabled, connectivity will -be lost. Generate a report with HIGH severity in that case. - -If devices are configured with NM_CONTROLLED=no, then ignore it, as they -are being explicitly excluded from NetworkManager control. - -Assisted-by: Claude 4.6 Opus - -Jira: https://redhat.atlassian.net/browse/RHEL-133966 ---- - .../actors/networkdeprecations/actor.py | 4 +- - .../libraries/networkdeprecations.py | 53 +++++++++++- - .../tests/unit_test_networkdeprecations.py | 82 ++++++++++++++++++- - 3 files changed, 135 insertions(+), 4 deletions(-) - -diff --git a/repos/system_upgrade/el8toel9/actors/networkdeprecations/actor.py b/repos/system_upgrade/el8toel9/actors/networkdeprecations/actor.py -index 3074a3c7..8515ec69 100644 ---- a/repos/system_upgrade/el8toel9/actors/networkdeprecations/actor.py -+++ b/repos/system_upgrade/el8toel9/actors/networkdeprecations/actor.py -@@ -1,6 +1,6 @@ - from leapp.actors import Actor - from leapp.libraries.actor import networkdeprecations --from leapp.models import IfCfg, NetworkManagerConnection, Report -+from leapp.models import IfCfg, NetworkManagerConnection, Report, SystemdServicesInfoSource - from leapp.tags import ChecksPhaseTag, IPUWorkflowTag - - -@@ -16,7 +16,7 @@ class CheckNetworkDeprecations(Actor): - """ - - name = "network_deprecations" -- consumes = (IfCfg, NetworkManagerConnection,) -+ consumes = (IfCfg, NetworkManagerConnection, SystemdServicesInfoSource,) - produces = (Report,) - tags = (ChecksPhaseTag, IPUWorkflowTag,) - -diff --git a/repos/system_upgrade/el8toel9/actors/networkdeprecations/libraries/networkdeprecations.py b/repos/system_upgrade/el8toel9/actors/networkdeprecations/libraries/networkdeprecations.py -index 92dfc51d..d4f2106b 100644 ---- a/repos/system_upgrade/el8toel9/actors/networkdeprecations/libraries/networkdeprecations.py -+++ b/repos/system_upgrade/el8toel9/actors/networkdeprecations/libraries/networkdeprecations.py -@@ -1,12 +1,13 @@ - from leapp import reporting - from leapp.libraries.stdlib import api --from leapp.models import IfCfg, NetworkManagerConnection -+from leapp.models import IfCfg, NetworkManagerConnection, SystemdServicesInfoSource - - FMT_LIST_SEPARATOR = '\n - ' - - - def process(): - wep_files = [] -+ nm_controlled_files = [] - - # Scan NetworkManager native keyfile connections - for nmconn in api.consume(NetworkManagerConnection): -@@ -26,6 +27,8 @@ def process(): - if ifcfg.secrets is not None: - props = props + ifcfg.secrets - -+ nm_controlled = True -+ - for prop in props: - name = prop.name - value = prop.value -@@ -40,6 +43,14 @@ def process(): - wep_files.append(ifcfg.filename) - continue - -+ # NM_CONTROLLED -+ if name == 'NM_CONTROLLED' and value.lower() in ("no", "false", "f", "n", "0"): -+ nm_controlled = False -+ continue -+ -+ if nm_controlled: -+ nm_controlled_files.append(ifcfg.filename) -+ - if wep_files: - title = 'Wireless networks using unsupported WEP encryption detected' - summary = ('The Wired Equivalent Privacy (WEP) algorithm used for' -@@ -65,3 +76,43 @@ def process(): - reporting.RelatedResource('file', fname) - for fname in wep_files - ]) -+ -+ if nm_controlled_files and _is_nm_service_disabled(): -+ title = 'ifcfg files expect NetworkManager but the service is disabled' -+ summary = ( -+ 'NetworkManager is disabled on the system, but there are ifcfg' -+ ' configuration files that do not set NM_CONTROLLED=no. After the' -+ ' upgrade, legacy network scripts will no longer be available and' -+ ' NetworkManager will be the only way to manage networking. These' -+ ' connections will not be active unless NetworkManager is enabled.' -+ ' Files with the problematic configuration:{}' -+ ).format( -+ ''.join(['{}{}'.format(FMT_LIST_SEPARATOR, f) for f in nm_controlled_files]) -+ ) -+ remediation = ( -+ 'Either enable the NetworkManager service before upgrading, or add' -+ ' NM_CONTROLLED=no to the ifcfg files that should not be managed' -+ ' by NetworkManager.' -+ ) -+ reporting.create_report([ -+ reporting.Title(title), -+ reporting.Summary(summary), -+ reporting.Remediation(hint=remediation), -+ reporting.Severity(reporting.Severity.HIGH), -+ reporting.Groups([reporting.Groups.NETWORK, reporting.Groups.SERVICES]), -+ reporting.Groups([reporting.Groups.INHIBITOR]), -+ reporting.RelatedResource('package', 'NetworkManager'), -+ ] + [ -+ reporting.RelatedResource('file', fname) -+ for fname in nm_controlled_files -+ ]) -+ -+ -+def _is_nm_service_disabled(): -+ services_info = next(api.consume(SystemdServicesInfoSource), None) -+ if not services_info: -+ return True -+ for svc in services_info.service_files: -+ if svc.name == 'NetworkManager.service': -+ return svc.state == 'disabled' -+ return True -diff --git a/repos/system_upgrade/el8toel9/actors/networkdeprecations/tests/unit_test_networkdeprecations.py b/repos/system_upgrade/el8toel9/actors/networkdeprecations/tests/unit_test_networkdeprecations.py -index 659ab993..e9fa69ac 100644 ---- a/repos/system_upgrade/el8toel9/actors/networkdeprecations/tests/unit_test_networkdeprecations.py -+++ b/repos/system_upgrade/el8toel9/actors/networkdeprecations/tests/unit_test_networkdeprecations.py -@@ -3,7 +3,9 @@ from leapp.models import ( - IfCfgProperty, - NetworkManagerConnection, - NetworkManagerConnectionProperty, -- NetworkManagerConnectionSetting -+ NetworkManagerConnectionSetting, -+ SystemdServiceFile, -+ SystemdServicesInfoSource - ) - from leapp.reporting import Report - from leapp.utils.report import is_inhibitor -@@ -122,3 +124,81 @@ def test_ifcfg_dynamic_wep(current_actor_context): - current_actor_context.run() - report_fields = current_actor_context.consume(Report)[0].report - assert is_inhibitor(report_fields) -+ -+ -+def _nm_service_info(state): -+ return SystemdServicesInfoSource(service_files=[ -+ SystemdServiceFile(name='NetworkManager.service', state=state), -+ ]) -+ -+ -+def test_nm_disabled_ifcfg_no_nm_controlled(current_actor_context): -+ """ -+ Report if NM is disabled and ifcfg files don't have NM_CONTROLLED=no. -+ """ -+ -+ current_actor_context.feed(_nm_service_info('disabled')) -+ current_actor_context.feed(IfCfg(filename='/NM/ifcfg-eth0', properties=( -+ IfCfgProperty(name='TYPE', value='Ethernet'), -+ ))) -+ current_actor_context.run() -+ reports = current_actor_context.consume(Report) -+ assert len(reports) == 1 -+ assert 'ifcfg files expect NetworkManager' in reports[0].report['title'] -+ -+ -+def test_nm_disabled_ifcfg_nm_controlled_no(current_actor_context): -+ """ -+ No report if NM is disabled but all ifcfg files have NM_CONTROLLED=no. -+ """ -+ -+ current_actor_context.feed(_nm_service_info('disabled')) -+ current_actor_context.feed(IfCfg(filename='/NM/ifcfg-eth0', properties=( -+ IfCfgProperty(name='TYPE', value='Ethernet'), -+ IfCfgProperty(name='NM_CONTROLLED', value='no'), -+ ))) -+ current_actor_context.run() -+ assert not current_actor_context.consume(Report) -+ -+ -+def test_nm_enabled_ifcfg_no_nm_controlled(current_actor_context): -+ """ -+ No report if NM is enabled, even if ifcfg files lack NM_CONTROLLED=no. -+ """ -+ -+ current_actor_context.feed(_nm_service_info('enabled')) -+ current_actor_context.feed(IfCfg(filename='/NM/ifcfg-eth0', properties=( -+ IfCfgProperty(name='TYPE', value='Ethernet'), -+ ))) -+ current_actor_context.run() -+ assert not current_actor_context.consume(Report) -+ -+ -+def test_nm_disabled_mixed_ifcfg(current_actor_context): -+ """ -+ Report only the ifcfg files that lack NM_CONTROLLED=no when NM is disabled. -+ """ -+ -+ current_actor_context.feed(_nm_service_info('disabled')) -+ current_actor_context.feed(IfCfg(filename='/NM/ifcfg-eth0', properties=( -+ IfCfgProperty(name='TYPE', value='Ethernet'), -+ ))) -+ current_actor_context.feed(IfCfg(filename='/NM/ifcfg-eth1', properties=( -+ IfCfgProperty(name='TYPE', value='Ethernet'), -+ IfCfgProperty(name='NM_CONTROLLED', value='no'), -+ ))) -+ current_actor_context.run() -+ reports = current_actor_context.consume(Report) -+ assert len(reports) == 1 -+ assert '/NM/ifcfg-eth0' in reports[0].report['summary'] -+ assert '/NM/ifcfg-eth1' not in reports[0].report['summary'] -+ -+ -+def test_nm_disabled_no_ifcfg(current_actor_context): -+ """ -+ No report if NM is disabled but there are no ifcfg files at all. -+ """ -+ -+ current_actor_context.feed(_nm_service_info('disabled')) -+ current_actor_context.run() -+ assert not current_actor_context.consume(Report) --- -2.54.0 - diff --git a/0079-Remove-enforcing-1-from-upgrade-and-target-kernel-bo.patch b/0079-Remove-enforcing-1-from-upgrade-and-target-kernel-bo.patch deleted file mode 100644 index e6898f3..0000000 --- a/0079-Remove-enforcing-1-from-upgrade-and-target-kernel-bo.patch +++ /dev/null @@ -1,441 +0,0 @@ -From 924276d11589cefba16901a30d7269062a06eb3b Mon Sep 17 00:00:00 2001 -From: Pradeep Jagtap -Date: Mon, 4 May 2026 14:07:53 +0530 -Subject: [PATCH 079/105] Remove enforcing=1 from upgrade and target kernel - boot entries during IPU - -Detect enforcing=1 on the running kernel (KernelCmdline) or in any bootloader -entry. Bootloader detection is collected by systemfacts via `grubby --info ALL` -and exposed as SELinuxFacts.enforcing_via_any_cmdline. - -When enforcing=1 is present, checkselinux (Checks phase) emits -UpgradeKernelCmdlineArgTasks to remove enforcing=1 from the interim upgrade -boot entry, and TargetKernelCmdlineArgTasks to remove it from the target kernel -entry and update the default kernel command line during finalization. - -An INFO report with remediation guidance to restore enforcing=1 after upgrade is also -produced. Original boot entries for existing kernels are not modified. - -JIRA: RHEL-162192 ---- - .../common/actors/checkselinux/actor.py | 15 +++- - .../checkselinux/libraries/checkselinux.py | 80 ++++++++++++++++++- - .../checkselinux/tests/test_checkselinux.py | 72 ++++++++++++++--- - .../systemfacts/libraries/systemfacts.py | 20 +++++ - .../tests/test_systemfacts_selinux.py | 61 +++++++++++++- - .../common/models/selinuxfacts.py | 4 + - 6 files changed, 236 insertions(+), 16 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/checkselinux/actor.py b/repos/system_upgrade/common/actors/checkselinux/actor.py -index 4e8fa7df..d618b5d5 100644 ---- a/repos/system_upgrade/common/actors/checkselinux/actor.py -+++ b/repos/system_upgrade/common/actors/checkselinux/actor.py -@@ -1,6 +1,15 @@ - from leapp.actors import Actor - from leapp.libraries.actor import checkselinux --from leapp.models import KernelCmdlineArg, Report, SELinuxFacts, SelinuxPermissiveDecision, SelinuxRelabelDecision -+from leapp.models import ( -+ KernelCmdline, -+ KernelCmdlineArg, -+ Report, -+ SELinuxFacts, -+ SelinuxPermissiveDecision, -+ SelinuxRelabelDecision, -+ TargetKernelCmdlineArgTasks, -+ UpgradeKernelCmdlineArgTasks -+) - from leapp.tags import ChecksPhaseTag, IPUWorkflowTag - - -@@ -13,12 +22,14 @@ class CheckSelinux(Actor): - """ - - name = 'check_se_linux' -- consumes = (SELinuxFacts,) -+ consumes = (SELinuxFacts, KernelCmdline) - produces = ( - KernelCmdlineArg, - Report, - SelinuxPermissiveDecision, - SelinuxRelabelDecision, -+ TargetKernelCmdlineArgTasks, -+ UpgradeKernelCmdlineArgTasks, - ) - tags = (ChecksPhaseTag, IPUWorkflowTag) - -diff --git a/repos/system_upgrade/common/actors/checkselinux/libraries/checkselinux.py b/repos/system_upgrade/common/actors/checkselinux/libraries/checkselinux.py -index dbd79adf..4f892777 100644 ---- a/repos/system_upgrade/common/actors/checkselinux/libraries/checkselinux.py -+++ b/repos/system_upgrade/common/actors/checkselinux/libraries/checkselinux.py -@@ -1,11 +1,85 @@ - from leapp import reporting -+from leapp.libraries.common.config import architecture - from leapp.libraries.common.config.version import get_target_major_version - from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.stdlib import api --from leapp.models import KernelCmdlineArg, SELinuxFacts, SelinuxPermissiveDecision, SelinuxRelabelDecision -+from leapp.models import ( -+ KernelCmdline, -+ KernelCmdlineArg, -+ SELinuxFacts, -+ SelinuxPermissiveDecision, -+ SelinuxRelabelDecision, -+ TargetKernelCmdlineArgTasks, -+ UpgradeKernelCmdlineArgTasks -+) - - DOC_URL = 'https://red.ht/rhel9-disabling-selinux' - -+_ENFORCING_ONE_ARG = KernelCmdlineArg(key='enforcing', value='1') -+ -+ -+def _proc_cmdline_has_enforcing_one(kernel_cmdline): -+ """ -+ Checks if the kernel command line contains enforcing=1. -+ """ -+ if not kernel_cmdline: -+ return False -+ for param in kernel_cmdline.parameters: -+ if param.key == 'enforcing' and param.value == '1': -+ return True -+ return False -+ -+ -+def _request_removal_of_enforcing_one(): -+ """ -+ Schedule stripping of enforcing=1 from the upgrade and target boot entries. -+ -+ The upgrade workflow runs with SELinux permissive, but if enforcing=1 is present on -+ the running kernel or in any bootloader entry it can override that. We produce: -+ -+ * ``UpgradeKernelCmdlineArgTasks`` so the interim upgrade boot entry drops enforcing=1 -+ (consumed by ``addupgradebootentry``). -+ * ``TargetKernelCmdlineArgTasks`` so the target kernel entry and the default kernel -+ command line configuration are updated during finalization (consumed by -+ ``kernelcmdlineconfig``), ensuring that future kernels also omit enforcing=1. -+ -+ Original boot entries for existing (non-target) kernels are left untouched. -+ -+ ``SELinuxFacts.enforcing_via_any_cmdline`` is populated earlier by the -+ ``systemfacts`` actor. -+ """ -+ arch = api.current_actor().configuration.architecture -+ arch_msg = ' Then make sure to run zipl to update the boot menu.' if arch == architecture.ARCH_S390X else '' -+ doc_url = 'https://red.ht/rhel-{}-configure-kernel-cmdline'.format(get_target_major_version()) -+ hint = ( -+ 'After the upgrade, add the `enforcing=1` kernel command line argument back if it is wanted.' -+ ' Run "grubby --update-kernel=ALL --args=enforcing=1" to enable enforcing on all boot entries' -+ f' and set it as default.{arch_msg}' -+ ' Follow the documentation in the attached link for more details.' -+ ) -+ -+ api.produce(UpgradeKernelCmdlineArgTasks(to_remove=[_ENFORCING_ONE_ARG])) -+ api.produce(TargetKernelCmdlineArgTasks(to_remove=[_ENFORCING_ONE_ARG])) -+ reporting.create_report([ -+ reporting.Title('Kernel boot parameter enforcing=1 will be removed'), -+ reporting.Summary( -+ 'The kernel parameter enforcing=1 forces SELinux enforcing mode at boot and overrides ' -+ 'the permissive configuration used during the upgrade. Leapp will remove enforcing=1 from ' -+ 'the upgrade and target kernel boot entries and update the default kernel command line ' -+ 'configuration so it does not persist after the upgrade. Existing boot entries for other ' -+ 'kernels will not be modified.' -+ ), -+ reporting.Remediation(hint=hint), -+ reporting.Severity(reporting.Severity.INFO), -+ reporting.Groups([ -+ reporting.Groups.SELINUX, -+ reporting.Groups.BOOT, -+ reporting.Groups.KERNEL, -+ reporting.Groups.POST, -+ ]), -+ reporting.ExternalLink(url=doc_url, title='Configuring kernel command line parameters'), -+ ]) -+ - - def process(): - facts = next(api.consume(SELinuxFacts), None) -@@ -49,6 +123,10 @@ def process(): - ]) - return - -+ kernel_cmdline = next(api.consume(KernelCmdline), None) -+ if _proc_cmdline_has_enforcing_one(kernel_cmdline) or facts.enforcing_via_any_cmdline: -+ _request_removal_of_enforcing_one() -+ - if conf_status in ('enforcing', 'permissive'): - api.produce(SelinuxRelabelDecision(set_relabel=True)) - reporting.create_report([ -diff --git a/repos/system_upgrade/common/actors/checkselinux/tests/test_checkselinux.py b/repos/system_upgrade/common/actors/checkselinux/tests/test_checkselinux.py -index 2a42b3ef..a0cd2ec9 100644 ---- a/repos/system_upgrade/common/actors/checkselinux/tests/test_checkselinux.py -+++ b/repos/system_upgrade/common/actors/checkselinux/tests/test_checkselinux.py -@@ -4,11 +4,24 @@ from leapp import reporting - from leapp.libraries.actor import checkselinux - from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked, produce_mocked - from leapp.libraries.stdlib import api --from leapp.models import Report, SELinuxFacts, SelinuxPermissiveDecision, SelinuxRelabelDecision --from leapp.snactor.fixture import current_actor_context -- -- --def create_selinuxfacts(static_mode, enabled, policy='targeted', mls_enabled=True): -+from leapp.models import ( -+ KernelCmdline, -+ KernelCmdlineArg, -+ SELinuxFacts, -+ SelinuxPermissiveDecision, -+ SelinuxRelabelDecision, -+ TargetKernelCmdlineArgTasks, -+ UpgradeKernelCmdlineArgTasks -+) -+ -+ -+def create_selinuxfacts( -+ static_mode, -+ enabled, -+ policy='targeted', -+ mls_enabled=True, -+ enforcing_via_any_cmdline=False, -+): - runtime_mode = static_mode if static_mode != 'disabled' else None - - return SELinuxFacts( -@@ -16,7 +29,8 @@ def create_selinuxfacts(static_mode, enabled, policy='targeted', mls_enabled=Tru - static_mode=static_mode, - enabled=enabled, - policy=policy, -- mls_enabled=mls_enabled -+ mls_enabled=mls_enabled, -+ enforcing_via_any_cmdline=enforcing_via_any_cmdline, - ) - - -@@ -25,7 +39,7 @@ def test_actor_schedule_relabelling(monkeypatch, mode): - - fact = create_selinuxfacts(static_mode=mode, enabled=True) - -- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[fact])) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[fact, KernelCmdline(parameters=[])])) - monkeypatch.setattr(api, 'produce', produce_mocked()) - monkeypatch.setattr(reporting, "create_report", create_report_mocked()) - -@@ -38,7 +52,7 @@ def test_actor_schedule_relabelling(monkeypatch, mode): - def test_actor_set_permissive(monkeypatch): - relabel = create_selinuxfacts(static_mode='enforcing', enabled=True) - -- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[relabel])) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[relabel, KernelCmdline(parameters=[])])) - monkeypatch.setattr(api, 'produce', produce_mocked()) - monkeypatch.setattr(reporting, "create_report", create_report_mocked()) - -@@ -56,7 +70,9 @@ def test_actor_selinux_disabled(monkeypatch, el8_to_el9): - target_ver = '8' if not el8_to_el9 else '9' - - monkeypatch.setattr(checkselinux, 'get_target_major_version', lambda: target_ver) -- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[disabled])) -+ monkeypatch.setattr( -+ api, 'current_actor', CurrentActorMocked(msgs=[disabled, KernelCmdline(parameters=[])]) -+ ) - monkeypatch.setattr(api, 'produce', produce_mocked()) - monkeypatch.setattr(reporting, "create_report", create_report_mocked()) - -@@ -67,3 +83,41 @@ def test_actor_selinux_disabled(monkeypatch, el8_to_el9): - else: - assert not api.produce.model_instances - assert reporting.create_report.called -+ -+ -+def test_enforcing_one_in_proc_cmdline_schedules_removal(monkeypatch): -+ fact = create_selinuxfacts(static_mode='permissive', enabled=True) -+ kcmd = KernelCmdline(parameters=[KernelCmdlineArg(key='enforcing', value='1')]) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[fact, kcmd])) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) -+ -+ checkselinux.process() -+ -+ upgrade_produced = [m for m in api.produce.model_instances if isinstance(m, UpgradeKernelCmdlineArgTasks)] -+ assert upgrade_produced -+ assert upgrade_produced[0].to_remove == [KernelCmdlineArg(key='enforcing', value='1')] -+ -+ target_produced = [m for m in api.produce.model_instances if isinstance(m, TargetKernelCmdlineArgTasks)] -+ assert target_produced -+ assert target_produced[0].to_remove == [KernelCmdlineArg(key='enforcing', value='1')] -+ -+ -+def test_enforcing_one_only_in_bootloader_schedules_removal(monkeypatch): -+ fact = create_selinuxfacts( -+ static_mode='permissive', enabled=True, enforcing_via_any_cmdline=True -+ ) -+ kcmd = KernelCmdline(parameters=[KernelCmdlineArg(key='ro')]) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[fact, kcmd])) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) -+ -+ checkselinux.process() -+ -+ upgrade_produced = [m for m in api.produce.model_instances if isinstance(m, UpgradeKernelCmdlineArgTasks)] -+ assert upgrade_produced -+ assert upgrade_produced[0].to_remove == [KernelCmdlineArg(key='enforcing', value='1')] -+ -+ target_produced = [m for m in api.produce.model_instances if isinstance(m, TargetKernelCmdlineArgTasks)] -+ assert target_produced -+ assert target_produced[0].to_remove == [KernelCmdlineArg(key='enforcing', value='1')] -diff --git a/repos/system_upgrade/common/actors/systemfacts/libraries/systemfacts.py b/repos/system_upgrade/common/actors/systemfacts/libraries/systemfacts.py -index ba7bdb82..578f96cb 100644 ---- a/repos/system_upgrade/common/actors/systemfacts/libraries/systemfacts.py -+++ b/repos/system_upgrade/common/actors/systemfacts/libraries/systemfacts.py -@@ -230,6 +230,24 @@ def get_repositories_status(): - }) - - -+def _bootloader_entries_contain_enforcing_one(): -+ """ -+ True if grubby reports enforcing=1 in any boot loader entry's kernel arguments. -+ """ -+ try: -+ out = run(['/usr/sbin/grubby', '--info', 'ALL'], split=False)['stdout'] -+ except (OSError, CalledProcessError): -+ api.current_logger().debug( -+ 'grubby --info ALL failed; assuming no bootloader enforcing=1', exc_info=True -+ ) -+ return False -+ -+ for line in out.splitlines(): -+ if line.startswith('args=') and 'enforcing=1' in line[len('args='):].strip('"').split(): -+ return True -+ return False -+ -+ - def get_selinux_status(): - """ Get SELinux status information """ - # will be None if something went wrong or contain SELinuxFacts otherwise -@@ -259,6 +277,8 @@ def get_selinux_status(): - outdata['static_mode'] = 'disabled' - outdata['policy'] = 'targeted' - -+ outdata['enforcing_via_any_cmdline'] = _bootloader_entries_contain_enforcing_one() -+ - res = SELinuxFacts(**outdata) - return res - -diff --git a/repos/system_upgrade/common/actors/systemfacts/tests/test_systemfacts_selinux.py b/repos/system_upgrade/common/actors/systemfacts/tests/test_systemfacts_selinux.py -index d36900bd..85a3ff3d 100644 ---- a/repos/system_upgrade/common/actors/systemfacts/tests/test_systemfacts_selinux.py -+++ b/repos/system_upgrade/common/actors/systemfacts/tests/test_systemfacts_selinux.py -@@ -2,6 +2,7 @@ import warnings - - import pytest - -+from leapp.libraries.actor import systemfacts - from leapp.libraries.actor.systemfacts import get_selinux_status - from leapp.models import SELinuxFacts - -@@ -20,6 +21,23 @@ reason_to_skip_msg = "Selinux is not available" - # FIXME: create valid tests... - - -+def _run_result(stdout): -+ return {'stdout': stdout, 'stderr': '', 'signal': None, 'exit_code': 0, 'pid': 1} -+ -+ -+@pytest.fixture(autouse=True) -+def stub_grubby_info_all_without_enforcing(monkeypatch): -+ """ -+ Avoid invoking real grubby from get_selinux_status(); default: no enforcing=1 in boot entries. -+ """ -+ -+ def mocked_run(cmd, split=False, **dummy_kwargs): -+ assert cmd == ['/usr/sbin/grubby', '--info', 'ALL'], f"Unexpected grubby cmd: {cmd}" -+ return _run_result('index=0\nkernel="/boot/vmlinuz-1"\nargs="ro rhgb quiet"\nroot="UUID=xxx"\n') -+ -+ monkeypatch.setattr(systemfacts, 'run', mocked_run) -+ -+ - @pytest.mark.skipif(no_selinux, reason=reason_to_skip_msg) - def test_selinux_enabled_enforcing(monkeypatch): - """ -@@ -34,7 +52,8 @@ def test_selinux_enabled_enforcing(monkeypatch): - 'mls_enabled': True, - 'enabled': True, - 'runtime_mode': 'enforcing', -- 'static_mode': 'enforcing'} -+ 'static_mode': 'enforcing', -+ 'enforcing_via_any_cmdline': False} - assert SELinuxFacts(**expected_data) == get_selinux_status() - - -@@ -52,7 +71,8 @@ def test_selinux_enabled_permissive(monkeypatch): - 'mls_enabled': True, - 'enabled': True, - 'runtime_mode': 'permissive', -- 'static_mode': 'permissive'} -+ 'static_mode': 'permissive', -+ 'enforcing_via_any_cmdline': False} - assert SELinuxFacts(**expected_data) == get_selinux_status() - - -@@ -70,7 +90,8 @@ def test_selinux_disabled(monkeypatch): - 'mls_enabled': False, - 'enabled': False, - 'runtime_mode': 'permissive', -- 'static_mode': 'permissive'} -+ 'static_mode': 'permissive', -+ 'enforcing_via_any_cmdline': False} - assert SELinuxFacts(**expected_data) == get_selinux_status() - - -@@ -93,6 +114,38 @@ def test_selinux_disabled_no_config_file(monkeypatch): - 'mls_enabled': False, - 'enabled': False, - 'runtime_mode': 'permissive', -- 'static_mode': 'disabled'} -+ 'static_mode': 'disabled', -+ 'enforcing_via_any_cmdline': False} - - assert SELinuxFacts(**expected_data) == get_selinux_status() -+ -+ -+@pytest.mark.skipif(no_selinux, reason=reason_to_skip_msg) -+@pytest.mark.parametrize('grubby_stdout,expected', [ -+ ('index=0\nargs="ro enforcing=1"\n', True), -+ ('index=0\nargs="ro enforcing=1 rhgb quiet"\n', True), -+ ('index=0\nargs="enforcing=1"\n', True), -+ ('index=0\nargs="ro enforcing=1"\nindex=1\nargs="ro rhgb quiet"\n', True), -+ ('index=0\nargs="ro rhgb quiet"\nindex=1\nargs="ro enforcing=1"\n', True), -+ ('index=0\nargs="ro enforcing=0"\n', False), -+ ('index=0\nargs="ro rhgb quiet"\n', False), -+ ('index=0\nargs="ro fooenforcing=1"\n', False), -+ ('index=0\nargs="ro enforcing=1bar"\n', False), -+ ('index=0\nargs="ro"\n', False), -+ ('index=0\nargs=""\n', False), -+]) -+def test_bootloader_enforcing_one_detected(monkeypatch, grubby_stdout, expected): -+ monkeypatch.setattr(selinux, 'is_selinux_mls_enabled', lambda: 1) -+ monkeypatch.setattr(selinux, 'security_getenforce', lambda: 1) -+ monkeypatch.setattr(selinux, 'selinux_getenforcemode', lambda: [0, 1]) -+ monkeypatch.setattr(selinux, 'is_selinux_enabled', lambda: 1) -+ monkeypatch.setattr(selinux, 'selinux_getpolicytype', lambda: [0, 'targeted']) -+ -+ def mocked_run(cmd, split=False, **dummy_kwargs): -+ assert cmd == ['/usr/sbin/grubby', '--info', 'ALL'], f"Unexpected grubby cmd: {cmd}" -+ return _run_result(grubby_stdout) -+ -+ monkeypatch.setattr(systemfacts, 'run', mocked_run) -+ -+ fact = get_selinux_status() -+ assert fact.enforcing_via_any_cmdline is expected -diff --git a/repos/system_upgrade/common/models/selinuxfacts.py b/repos/system_upgrade/common/models/selinuxfacts.py -index 12b0cf8a..9376e4b1 100644 ---- a/repos/system_upgrade/common/models/selinuxfacts.py -+++ b/repos/system_upgrade/common/models/selinuxfacts.py -@@ -10,3 +10,7 @@ class SELinuxFacts(Model): - enabled = fields.Boolean() - policy = fields.StringEnum(['targeted', 'minimum', 'mls']) - mls_enabled = fields.Boolean() -+ enforcing_via_any_cmdline = fields.Boolean(default=False) -+ """ -+ True when any bootloader entries have set enforcing=1. -+ """ --- -2.54.0 - diff --git a/0080-Fix-utils-actor_path.py-crash-on-invalid-repo-paths.patch b/0080-Fix-utils-actor_path.py-crash-on-invalid-repo-paths.patch deleted file mode 100644 index 68aec88..0000000 --- a/0080-Fix-utils-actor_path.py-crash-on-invalid-repo-paths.patch +++ /dev/null @@ -1,71 +0,0 @@ -From fdc5e80760f08bbef095713d7d592758f551d6b8 Mon Sep 17 00:00:00 2001 -From: Matej Matuska -Date: Thu, 21 May 2026 13:23:02 +0200 -Subject: [PATCH 080/105] Fix utils/actor_path.py crash on invalid repo paths - -When a scan of a path which does not contain a valid leapp repository is -attempted in utils/actor.py, it crashes. For example, run with -REPOSITORIES=common,invalid: - -$ ACTOR=mysql_check REPOSITORIES=common,invalid make test -ERROR: Unknown error: 'name' -ERROR: Possibly you need newer version of the leapp framework -ERROR: e.g.: rm -rf .tut && make install-deps-fedora -Traceback (most recent call last): - File "utils/actor_path.py", line 50, in - main() - File "utils/actor_path.py", line 33, in main - manager.add_repo(scan_repo(os.path.join(SYSUPG_REPO, repo))) - File "/home/user/leapp-repository/tut/lib/python3.6/site-packages/leapp/repository/scan.py", line 70, in scan_repo - return scan(Repository(path), path) - File "/home/user/leapp-repository/tut/lib/python3.6/site-packages/leapp/repository/__init__.py", line 40, in __init__ - self.name = get_repository_name(directory) - File "/home/user/leapp-repository/tut/lib/python3.6/site-packages/leapp/utils/repository.py", line 96, in get_repository_name - return get_repository_metadata(path)['name'] -KeyError: 'name' - -With this patch, there is an error with suggestion: -$ ACTOR=mysql_check REPOSITORIES=common,invalid make test -ERROR: Failed to scan repository at repos/system_upgrade/invalid, make sure there is a valid leapp repository at the path. -. tut/bin/activate; \ -echo "--- Linting ... ---" && \ -SEARCH_PATH="" && \ -echo "Using search path '${SEARCH_PATH}'" && \ -echo "--- Running pylint ---" && \ -bash -c "[[ ! -z '${SEARCH_PATH}' ]] && find ${SEARCH_PATH} -name '*.py' | sort -u | xargs pylint -j0 " && \ -echo "--- Running flake8 ---" && \ -bash -c "[[ ! -z '${SEARCH_PATH}' ]] && flake8 ${SEARCH_PATH} " ---- Linting ... --- -Using search path '' ---- Running pylint --- -make: *** [Makefile:343: lint] Error 1 ---- - utils/actor_path.py | 12 +++++++++++- - 1 file changed, 11 insertions(+), 1 deletion(-) - -diff --git a/utils/actor_path.py b/utils/actor_path.py -index 3c61ce79..68b70da6 100755 ---- a/utils/actor_path.py -+++ b/utils/actor_path.py -@@ -30,7 +30,17 @@ def main(): - # the scanning and resolving done below - manager = RepositoryManager() - for repo in repos: -- manager.add_repo(scan_repo(os.path.join(SYSUPG_REPO, repo))) -+ repo_path = os.path.join(SYSUPG_REPO, repo) -+ try: -+ repo_obj = scan_repo(repo_path) -+ except KeyError: # this is not a nice API, should handle this better in the framework -+ sys.stderr.write( -+ "ERROR: Failed to scan repository at {}, make sure there" -+ " is a valid leapp repository at the path.\n".format(repo_path) -+ ) -+ return -+ -+ manager.add_repo(repo_obj) - _resolve_repository_links(manager=manager, include_locals=True) - manager.load() - else: --- -2.54.0 - diff --git a/0081-fix-breadcrumbs-use-distro-aware-OS-names-for-source.patch b/0081-fix-breadcrumbs-use-distro-aware-OS-names-for-source.patch deleted file mode 100644 index 27cb56a..0000000 --- a/0081-fix-breadcrumbs-use-distro-aware-OS-names-for-source.patch +++ /dev/null @@ -1,119 +0,0 @@ -From 1797c35db27e5af2d54598a05ded4fd55aed3765 Mon Sep 17 00:00:00 2001 -From: Pradeep Jagtap -Date: Thu, 14 May 2026 13:41:34 +0530 -Subject: [PATCH 081/105] fix(breadcrumbs): use distro-aware OS names for - source and target - -The original code hardcoded "Red Hat Enterprise Linux" for both -source_os and target_os in /etc/migration-results breadcrumbs. -With multiple distros now supported (CentOS Stream, AlmaLinux, etc.), -this produced incorrect records for non-RHEL upgrades. - -- Add DISTRO_NAMES mapping in command_utils.py for CLI-layer - use, with a sync note referencing distro_id_to_pretty_name() in - the repo library (which cannot be imported from the CLI layer). - -- Replace inline "Red Hat Enterprise Linux" formatting with a single - _get_os_name(ipu_cfg, direction) helper that resolves the display - name from IPUConfig distro/version metadata. - -- Return 'N/A' when distro or version data is missing instead of - silently assuming RHEL. - -JIRA: RHEL-156580 ---- - commands/command_utils.py | 14 ++++++++++++ - commands/upgrade/breadcrumbs.py | 22 +++++++++++++++---- - .../system_upgrade/common/libraries/distro.py | 4 ++++ - 3 files changed, 36 insertions(+), 4 deletions(-) - -diff --git a/commands/command_utils.py b/commands/command_utils.py -index 735144f8..20ae1b48 100644 ---- a/commands/command_utils.py -+++ b/commands/command_utils.py -@@ -39,6 +39,20 @@ class DistroIDs(str, Enum): - ALMALINUX = 'almalinux' - - -+DISTRO_NAMES = { -+ DistroIDs.RHEL: 'Red Hat Enterprise Linux', -+ DistroIDs.CENTOS: 'CentOS Stream', -+ DistroIDs.ALMALINUX: 'AlmaLinux', -+} -+""" -+Maps distro IDs to their user-facing display names (matching NAME in /etc/os-release). -+ -+NOTE: keep in sync with distro_id_to_pretty_name() in -+repos/system_upgrade/common/libraries/distro.py the repo library -+is not importable from the CLI layer. -+""" -+ -+ - _DISTRO_VERSION_FORMATS = { - DistroIDs.RHEL: VersionFormats.MAJOR_MINOR, - DistroIDs.CENTOS: VersionFormats.MAJOR_ONLY, -diff --git a/commands/upgrade/breadcrumbs.py b/commands/upgrade/breadcrumbs.py -index 95a551c3..f5ca92e9 100644 ---- a/commands/upgrade/breadcrumbs.py -+++ b/commands/upgrade/breadcrumbs.py -@@ -6,6 +6,7 @@ from functools import wraps - from itertools import chain - - from leapp import FULL_VERSION -+from leapp.cli.commands.command_utils import DISTRO_NAMES - from leapp.libraries.stdlib.call import _call - from leapp.utils.audit import get_messages - -@@ -15,6 +16,20 @@ except ImportError: - JSONDecodeError = ValueError - - -+def _get_os_name(ipu_cfg, direction): -+ """ -+ Determine the OS display name from IPUConfig data. -+ -+ :param direction: 'source' or 'target' -+ """ -+ distro = (ipu_cfg.get('distro') or {}).get(direction, '') -+ version = (ipu_cfg.get('version') or {}).get(direction, '') -+ if not distro: -+ return 'N/A' -+ distro_name = DISTRO_NAMES.get(distro, 'unknown') -+ return '{} {}'.format(distro_name, version or 'N/A') -+ -+ - def runs_in_container(): - """ - Check if the current process is running inside a container -@@ -95,10 +110,9 @@ class _BreadCrumbs: - self._crumbs['run_id'] = os.environ.get('LEAPP_EXECUTION_ID', 'N/A') - self._crumbs['leapp_file_changes'].extend(self._verify_leapp_pkgs()) - messages = get_messages(('IPUConfig',), self._crumbs['run_id']) -- versions = json.loads((messages or [{}])[0].get('message', {}).get( -- 'data', '{}')).get('version', {'target': 'N/A', 'source': 'N/A'}) -- self._crumbs['target_os'] = 'Red Hat Enterprise Linux {target}'.format(**versions) -- self._crumbs['source_os'] = 'Red Hat Enterprise Linux {source}'.format(**versions) -+ ipu_cfg = json.loads((messages or [{}])[0].get('message', {}).get('data', '{}')) -+ self._crumbs['source_os'] = _get_os_name(ipu_cfg, 'source') -+ self._crumbs['target_os'] = _get_os_name(ipu_cfg, 'target') - self._crumbs['activity_ended'] = datetime.datetime.utcnow().isoformat() + 'Z' - self._crumbs['env'] = {k: v for k, v in os.environ.items() if k.startswith('LEAPP_')} - try: -diff --git a/repos/system_upgrade/common/libraries/distro.py b/repos/system_upgrade/common/libraries/distro.py -index 1a5e3923..78ac79ca 100644 ---- a/repos/system_upgrade/common/libraries/distro.py -+++ b/repos/system_upgrade/common/libraries/distro.py -@@ -227,6 +227,10 @@ def distro_id_to_pretty_name(distro_id): - Get pretty name for the given distro id. - - The pretty name is what is found in the NAME field of /etc/os-release. -+ -+ NOTE: a parallel mapping exists in commands/command_utils.py -+ (DISTRO_NAMES) for use in the CLI layer which cannot import -+ repo libraries. Keep both in sync when adding a new distro. - """ - return { - "rhel": "Red Hat Enterprise Linux", --- -2.54.0 - diff --git a/0082-leapp_resume.service-use-install_exec_t-SELinux-cont.patch b/0082-leapp_resume.service-use-install_exec_t-SELinux-cont.patch deleted file mode 100644 index 652b8ad..0000000 --- a/0082-leapp_resume.service-use-install_exec_t-SELinux-cont.patch +++ /dev/null @@ -1,58 +0,0 @@ -From 09846327822b5f3ac6f021daed866990bde5f92b Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Fri, 27 Mar 2026 18:12:03 +0100 -Subject: [PATCH 082/105] leapp_resume.service: use install_exec_t SELinux - context - -The original fix to deal with AVC during execution of leapp when -booting to the upgraded system appeared to leave still a space to -generate new AVC messages during some operations. From our side, -the only way how we could be sure that we do not hit any additional -error on top of what root user would see, is to set an unconfined -context. - -However, after discussion with SELinux guys, we decided to not do that -yet. It can be actually valuable when discovering potential problems -in applications - and there is no harm to the upgrade process as -the system is in the permissive mode. - -Currently dealing with the RH Insights client, it has been -discovered that bug is in new SELinux policies for the client and not -in leapp-repository. But after the discussion, we are going to switch -the SELinux type context to `install_exec_t` -which should fit better to the upgrade process than the original one. -We will monitor the situation around AVC messages and make possible -further changes based on the feedback. - -Jira: RHEL-161684 -Signed-off-by: Petr Stodulka ---- - .../createresumeservice/files/leapp_resume.service | 13 ++++++++++--- - 1 file changed, 10 insertions(+), 3 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/createresumeservice/files/leapp_resume.service b/repos/system_upgrade/common/actors/createresumeservice/files/leapp_resume.service -index 749b4b84..e31b5eb5 100644 ---- a/repos/system_upgrade/common/actors/createresumeservice/files/leapp_resume.service -+++ b/repos/system_upgrade/common/actors/createresumeservice/files/leapp_resume.service -@@ -8,8 +8,15 @@ Wants=network-online.target - - [Service] - Type=oneshot --# FIXME: this is temporary workaround for Python3 --ExecStart=/bin/sh -c "/root/tmp_leapp_py3/leapp3 upgrade --resume" -+# NOTE(pstodulk): After discussion with the SELinux team, we try to stay -+# out of unconfined mode for now. -+#SELinuxContext=unconfined_u:unconfined_r:unconfined_t:s0 -+ -+# NOTE(pstodulk): Correction of the selinux type context. This one should fit to leapp -+# Ignore the exit code as this cmd is expected to fail if selinux is disabled -+ExecStartPre=-chcon -t install_exec_t /root/tmp_leapp_py3/leapp3 -+ExecStart=/root/tmp_leapp_py3/leapp3 upgrade --resume - StandardOutput=journal+console --# FIXME: this shouldn't be needed, but Satellite upgrade runs installer, and that's slow -+ -+# NOTE(pstodulk): some actions can take a lot of time (e.g. satellite installer) - TimeoutStartSec=infinity --- -2.54.0 - diff --git a/0083-config-rhui-Add-config-for-setting-rpm-gpg-keys-to-r.patch b/0083-config-rhui-Add-config-for-setting-rpm-gpg-keys-to-r.patch deleted file mode 100644 index 85dfb0f..0000000 --- a/0083-config-rhui-Add-config-for-setting-rpm-gpg-keys-to-r.patch +++ /dev/null @@ -1,380 +0,0 @@ -From b70ff330e9c6b9e570f24c63b0a28760c5e900b6 Mon Sep 17 00:00:00 2001 -From: Matej Matuska -Date: Tue, 12 May 2026 19:11:26 +0200 -Subject: [PATCH 083/105] config(rhui): Add config for setting rpm gpg keys to - remove - -Add a config option obsolete_gpg_keys to allow users to specify obsolete -rpm gpg keys to be removed during the upgrade. The removeobsoletegpgkeys -then checks the config and produces the appropriate DNFWorkaround -messages to remove them, similar to distro keys. - -This can be prefilled in the config shipped by leapp-rhui-* packages to -remove RPM GPG keys for cloud vendor keys, such as those which are used -to sign the RHUI client rpm. - -Jira: RHEL-121979 ---- - .../actors/removeobsoletegpgkeys/actor.py | 5 + - .../libraries/removeobsoleterpmgpgkeys.py | 56 +++++++- - .../tests/test_removeobsoleterpmgpgkeys.py | 130 ++++++++++++++++-- - repos/system_upgrade/common/configs/rhui.py | 27 +++- - 4 files changed, 204 insertions(+), 14 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/removeobsoletegpgkeys/actor.py b/repos/system_upgrade/common/actors/removeobsoletegpgkeys/actor.py -index 58b15a84..f3694776 100644 ---- a/repos/system_upgrade/common/actors/removeobsoletegpgkeys/actor.py -+++ b/repos/system_upgrade/common/actors/removeobsoletegpgkeys/actor.py -@@ -1,4 +1,5 @@ - from leapp.actors import Actor -+from leapp.configs.common.rhui import all_rhui_cfg - from leapp.libraries.actor import removeobsoleterpmgpgkeys - from leapp.models import DNFWorkaround, InstalledRPM - from leapp.tags import FactsPhaseTag, IPUWorkflowTag -@@ -17,10 +18,14 @@ class RemoveObsoleteGpgKeys(Actor): - - If converting, the obsolete keys are all of the keys provided by the - vendor of the source distribution. - -+ Additionally, if RHUI configuration is active (use_config=True), GPG keys -+ specified in the RHUI obsolete_gpg_keys configuration will also be removed. -+ - A DNFWorkaround is registered to actually remove the keys. - """ - - name = "remove_obsolete_gpg_keys" -+ config_schemas = all_rhui_cfg - consumes = (InstalledRPM,) - produces = (DNFWorkaround,) - tags = (FactsPhaseTag, IPUWorkflowTag) -diff --git a/repos/system_upgrade/common/actors/removeobsoletegpgkeys/libraries/removeobsoleterpmgpgkeys.py b/repos/system_upgrade/common/actors/removeobsoletegpgkeys/libraries/removeobsoleterpmgpgkeys.py -index 7d047395..1db6a2ed 100644 ---- a/repos/system_upgrade/common/actors/removeobsoletegpgkeys/libraries/removeobsoleterpmgpgkeys.py -+++ b/repos/system_upgrade/common/actors/removeobsoletegpgkeys/libraries/removeobsoleterpmgpgkeys.py -@@ -1,5 +1,8 @@ - import itertools -+import re - -+from leapp.configs.common.rhui import RHUI_CONFIG_SECTION, RhuiObsoleteGpgKeys, RhuiUseConfig -+from leapp.exceptions import StopActorExecutionError - from leapp.libraries.common.config import get_source_distro_id, get_target_distro_id - from leapp.libraries.common.config.version import get_target_major_version - from leapp.libraries.common.distro import get_distribution_data -@@ -7,6 +10,15 @@ from leapp.libraries.common.rpms import has_package - from leapp.libraries.stdlib import api - from leapp.models import DNFWorkaround, InstalledRPM - -+_GPG_PUBKEY_NVR_FORMAT = re.compile(r"^gpg-pubkey-[0-9a-f]{8}-[0-9a-f]{8}$") -+ -+ -+def _is_valid_pubkey_nvr(name): -+ """ -+ Validate if a string is a valid gpg key RPM NVR -+ """ -+ return _GPG_PUBKEY_NVR_FORMAT.match(name) is not None -+ - - def _is_key_installed(key): - """ -@@ -50,17 +62,51 @@ def _get_source_distro_keys(): - ] - - -+def _get_rhui_configured_keys(): -+ """ -+ Get obsolete cloud vendor keys specified in RHUI configuration. -+ -+ Returns keys only when RHUI use_config is True, ensuring this is opt-in behavior. -+ Only returns keys that are actually installed on the system. -+ """ -+ rhui_config = api.current_actor().config[RHUI_CONFIG_SECTION] -+ -+ # Only apply RHUI keys when the leapp RHUI configuration is set -+ if not rhui_config[RhuiUseConfig.name]: -+ api.current_logger().debug("RHUI config disabled, ignoring obsolete RPM GPG keys") -+ return [] -+ -+ configured_keys = rhui_config[RhuiObsoleteGpgKeys.name] -+ -+ for key in configured_keys: -+ if not _is_valid_pubkey_nvr(key): -+ raise StopActorExecutionError( -+ "Provided RHUI config contains invalid value for setting {}".format( -+ RhuiObsoleteGpgKeys.name -+ ), -+ details={ -+ "details": "Entry {} is not a in valid RPM GPG name".format(key), -+ "hint": ( -+ "The expected format is: gpg-pubkey--," -+ " for example: gpg-pubkey-d4082792-5b32db75" -+ ), -+ }, -+ ) -+ -+ return [key for key in configured_keys if _is_key_installed(key)] -+ -+ - def register_dnfworkaround(keys): - api.produce( - DNFWorkaround( - display_name="remove obsolete RPM GPG keys from RPM DB", - script_path=api.current_actor().get_common_tool_path("removerpmgpgkeys"), -- script_args=keys, -+ script_args=list(keys), - ) - ) - - --def process(): -+def _get_all_obsolete_keys(): - if get_source_distro_id() == get_target_distro_id(): - # only upgrading - remove keys obsoleted in previous versions - keys = _get_obsolete_keys() -@@ -68,5 +114,11 @@ def process(): - # also converting - we need to remove all keys from the source distro - keys = _get_source_distro_keys() - -+ keys.extend(_get_rhui_configured_keys()) -+ return set(keys) -+ -+ -+def process(): -+ keys = _get_all_obsolete_keys() - if keys: - register_dnfworkaround(keys) -diff --git a/repos/system_upgrade/common/actors/removeobsoletegpgkeys/tests/test_removeobsoleterpmgpgkeys.py b/repos/system_upgrade/common/actors/removeobsoletegpgkeys/tests/test_removeobsoleterpmgpgkeys.py -index 8b9b842b..ff6b77ba 100644 ---- a/repos/system_upgrade/common/actors/removeobsoletegpgkeys/tests/test_removeobsoleterpmgpgkeys.py -+++ b/repos/system_upgrade/common/actors/removeobsoletegpgkeys/tests/test_removeobsoleterpmgpgkeys.py -@@ -3,10 +3,12 @@ import unittest.mock as mock - - import pytest - -+from leapp.configs.common.rhui import RhuiObsoleteGpgKeys, RhuiUseConfig -+from leapp.exceptions import StopActorExecutionError - from leapp.libraries.actor import removeobsoleterpmgpgkeys - from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked - from leapp.libraries.stdlib import api --from leapp.models import InstalledRPM, RPM -+from leapp.models import DNFWorkaround, InstalledRPM, RPM - - _CUR_DIR = os.path.dirname(os.path.abspath(__file__)) - -@@ -15,6 +17,40 @@ def common_folder_path_mocked(folder): - return os.path.join(_CUR_DIR, "../../../files/", folder) - - -+@pytest.mark.parametrize( -+ "key, is_valid", -+ [ -+ # too short -+ ("gpg-pubkey-10-10", False), -+ ("gpg-pubkey-888-abc", False), -+ ("gpg-d4082792-5b32db75j", False), -+ # too long ver -+ ("gpg-pubkey-1234456789-b32db75j8", False), -+ # end bound -+ ("gpg-pubkey-12345678-123456789", False), -+ # start bound -+ ("aaagpg-pubkey-12345678-12345678", False), -+ # invalid format -+ ("gpg-12345678-12345678", False), -+ ("pubkey-12345678-12345678", False), -+ ("gpg-pubkey-5b32db75j", False), -+ ("gpg-pubkey-d40827925b32db75j", False), -+ # non hex -+ ("gpg-pubkey-abcdefgh-aaaaaaaa", False), -+ ("gpg-pubkey-12345678-hhhhhhhh", False), -+ # uppercase -+ ("gpg-pubkey-DEADBEEF-12345678", False), -+ ("gpg-pubkey-12345678-DEADBEEF", False), -+ # Ok -+ ("gpg-pubkey-12345678-deadbeef", True), -+ ("gpg-pubkey-d4082792-5b32db75", True), -+ ("gpg-pubkey-2fa658e0-45700c69", True), -+ ], -+) -+def test_is_valid_pubkey_nvr(key, is_valid): -+ assert removeobsoleterpmgpgkeys._is_valid_pubkey_nvr(key) == is_valid -+ -+ - def test_is_key_installed(monkeypatch): - installed_rpms = InstalledRPM( - items=[ -@@ -170,27 +206,47 @@ def test_get_source_distro_keys(monkeypatch, distro, expected): - ) - def test_workaround_should_register(monkeypatch, keys, should_register): - monkeypatch.setattr( -- removeobsoleterpmgpgkeys, "_get_obsolete_keys", lambda: keys -+ removeobsoleterpmgpgkeys, "_get_all_obsolete_keys", lambda: keys - ) - monkeypatch.setattr(api, "produce", produce_mocked()) - monkeypatch.setattr(api, "current_actor", CurrentActorMocked()) - - removeobsoleterpmgpgkeys.process() - assert api.produce.called == should_register -+ if should_register: -+ inst = api.produce.model_instances[0] -+ assert isinstance(inst, DNFWorkaround) -+ assert inst.script_args == keys - - --def test_process(monkeypatch): -+@pytest.mark.parametrize( -+ "obsolete_keys, src_distro_keys, rhui_keys", -+ [ -+ ( -+ ["gpg-pubkey-12345678-abcdefgh"], -+ ["gpg-pubkey-87654321-hgfedcba"], -+ ["gpg-pubkey-12344321-abecedaa"], -+ ), -+ # test if the keys are deduplicated -+ ( -+ ["gpg-pubkey-12345678-abcdefgh"], -+ ["gpg-pubkey-12345678-abcdefgh"], -+ ["gpg-pubkey-12345678-abcdefgh"], -+ ), -+ ], -+) -+def test_process(monkeypatch, obsolete_keys, src_distro_keys, rhui_keys): - """ - Test that the correct path is taken depending on whether also converting - """ -- obsolete = ["gpg-pubkey-12345678-abcdefgh"] -- source_distro = ["gpg-pubkey-87654321-hgfedcba"] -- - monkeypatch.setattr( -- removeobsoleterpmgpgkeys, "_get_obsolete_keys", lambda: obsolete -+ removeobsoleterpmgpgkeys, "_get_obsolete_keys", lambda: obsolete_keys - ) - monkeypatch.setattr( -- removeobsoleterpmgpgkeys, "_get_source_distro_keys", lambda: source_distro, -+ removeobsoleterpmgpgkeys, "_get_source_distro_keys", lambda: src_distro_keys, -+ ) -+ monkeypatch.setattr( -+ removeobsoleterpmgpgkeys, "_get_rhui_configured_keys", lambda: rhui_keys - ) - - # upgrade only path -@@ -202,7 +258,7 @@ def test_process(monkeypatch): - ): - removeobsoleterpmgpgkeys.process() - removeobsoleterpmgpgkeys.register_dnfworkaround.assert_called_once_with( -- obsolete -+ set(obsolete_keys + rhui_keys) - ) - - # upgrade + conversion paths -@@ -214,7 +270,7 @@ def test_process(monkeypatch): - ): - removeobsoleterpmgpgkeys.process() - removeobsoleterpmgpgkeys.register_dnfworkaround.assert_called_once_with( -- source_distro -+ set(src_distro_keys + rhui_keys) - ) - - monkeypatch.setattr( -@@ -225,5 +281,57 @@ def test_process(monkeypatch): - ): - removeobsoleterpmgpgkeys.process() - removeobsoleterpmgpgkeys.register_dnfworkaround.assert_called_once_with( -- source_distro -+ set(src_distro_keys + rhui_keys) - ) -+ -+ -+@pytest.mark.parametrize( -+ "use_config, configured_keys, expected_in_result", -+ [ -+ # RHUI config enabled with keys -+ (True, ["gpg-pubkey-aaaaaaaa-11111111", "gpg-pubkey-bbbbbbbb-22222222"], True), -+ # RHUI config disabled (keys should NOT be included) -+ (False, ["gpg-pubkey-12345678-abcdefgh"], False), -+ # Empty key list -+ (True, [], False), -+ ] -+) -+def test_get_rhui_configured_keys(monkeypatch, use_config, configured_keys, expected_in_result): -+ """Test that RHUI configured keys are returned only when use_config=True""" -+ rhui_config = {} -+ if use_config is not None: -+ rhui_config[RhuiUseConfig.name] = use_config -+ if configured_keys is not None: -+ rhui_config[RhuiObsoleteGpgKeys.name] = configured_keys -+ -+ all_config = {'rhui': rhui_config} -+ -+ monkeypatch.setattr( -+ api, "current_actor", CurrentActorMocked(config=all_config) -+ ) -+ monkeypatch.setattr( -+ removeobsoleterpmgpgkeys, "_is_key_installed", lambda key: key in configured_keys -+ ) -+ -+ result = removeobsoleterpmgpgkeys._get_rhui_configured_keys() -+ -+ if expected_in_result: -+ assert set(result) == set(configured_keys) -+ else: -+ assert result == [] -+ -+ -+def test_get_rhui_configured_keys_raises_on_invalid(monkeypatch): -+ """Test that RHUI configured keys are returned only when use_config=True""" -+ rhui_config = { -+ RhuiUseConfig.name: True, -+ RhuiObsoleteGpgKeys.name: ["gpg-pubkey-10-10"], -+ } -+ all_config = {'rhui': rhui_config} -+ -+ monkeypatch.setattr( -+ api, "current_actor", CurrentActorMocked(config=all_config) -+ ) -+ -+ with pytest.raises(StopActorExecutionError): -+ removeobsoleterpmgpgkeys._get_rhui_configured_keys() -diff --git a/repos/system_upgrade/common/configs/rhui.py b/repos/system_upgrade/common/configs/rhui.py -index ade9bab9..4972effe 100644 ---- a/repos/system_upgrade/common/configs/rhui.py -+++ b/repos/system_upgrade/common/configs/rhui.py -@@ -116,6 +116,30 @@ class RhuiTargetRepositoriesToUse(Config): - default = list() - - -+class RhuiObsoleteGpgKeys(Config): -+ section = RHUI_CONFIG_SECTION -+ name = "obsolete_gpg_keys" -+ type_ = fields.List(fields.String()) -+ description = """ -+ List of obsolete cloud vendor GPG keys to remove from the RPM database during the upgrade. -+ -+ These are GPG keys used by cloud providers to sign RHUI client packages. These are NOT -+ Red Hat distribution keys. -+ -+ Each key should be specified in the format of an NVR of the RPM metapackage for the given key after -+ it's imported to rpmdb: -+ gpg-pubkey-- -+ For example: "gpg-pubkey-d4082792-5b32db75" -+ -+ These keys will be removed in addition to any keys marked as obsolete in the -+ distribution-specific configuration. Only keys that are actually installed -+ will be removed. -+ -+ Note: This setting only takes effect when use_config is set to True. -+ """ -+ default = list() -+ -+ - all_rhui_cfg = ( - RhuiTargetPkgs, - RhuiUpgradeFiles, -@@ -123,5 +147,6 @@ all_rhui_cfg = ( - RhuiCloudProvider, - RhuiCloudVariant, - RhuiSourcePkgs, -- RhuiUseConfig -+ RhuiUseConfig, -+ RhuiObsoleteGpgKeys - ) --- -2.54.0 - diff --git a/0084-Add-inhibitor-for-invalid-fstab-entries-on-pseudo-fi.patch b/0084-Add-inhibitor-for-invalid-fstab-entries-on-pseudo-fi.patch deleted file mode 100644 index 9f749f1..0000000 --- a/0084-Add-inhibitor-for-invalid-fstab-entries-on-pseudo-fi.patch +++ /dev/null @@ -1,274 +0,0 @@ -From 078a619c24ed1250f0d5f13c96412a051884a289 Mon Sep 17 00:00:00 2001 -From: Pradeep Jagtap -Date: Wed, 20 May 2026 15:45:13 +0530 -Subject: [PATCH 084/105] Add inhibitor for invalid fstab entries on - pseudo-filesystem mountpoints - -Detect /etc/fstab entries that incorrectly define pseudo-filesystem -mountpoints (/proc, /sys, /dev/shm, /run, etc.) with block devices or -wrong filesystem types. Such configurations have been invalid since -RHEL 7 but are typically ignored during normal boot. During the upgrade -the system applies these entries as configured, leading to failures. - -Use an allowlist of valid virtual filesystem sources and types so -entries referencing UUID=, LABEL=, PARTUUID=, or /dev/* paths are -correctly detected and inhibited. - -JIRA: RHEL-17842 ---- - .../actors/checkfstabapifsoverride/actor.py | 26 ++++ - .../libraries/checkfstabapifsoverride.py | 80 ++++++++++++ - .../tests/test_checkfstabapifsoverride.py | 121 ++++++++++++++++++ - 3 files changed, 227 insertions(+) - create mode 100644 repos/system_upgrade/common/actors/checkfstabapifsoverride/actor.py - create mode 100644 repos/system_upgrade/common/actors/checkfstabapifsoverride/libraries/checkfstabapifsoverride.py - create mode 100644 repos/system_upgrade/common/actors/checkfstabapifsoverride/tests/test_checkfstabapifsoverride.py - -diff --git a/repos/system_upgrade/common/actors/checkfstabapifsoverride/actor.py b/repos/system_upgrade/common/actors/checkfstabapifsoverride/actor.py -new file mode 100644 -index 00000000..5747f0fb ---- /dev/null -+++ b/repos/system_upgrade/common/actors/checkfstabapifsoverride/actor.py -@@ -0,0 +1,26 @@ -+from leapp.actors import Actor -+from leapp.libraries.actor.checkfstabapifsoverride import check_fstab_api_fs_override -+from leapp.models import StorageInfo -+from leapp.reporting import Report -+from leapp.tags import ChecksPhaseTag, IPUWorkflowTag -+ -+ -+class CheckFstabApiFsOverride(Actor): -+ """ -+ Inhibit upgrade if /etc/fstab contains invalid entries for pseudo-filesystem mountpoints. -+ -+ Paths like /proc, /sys, /dev/shm, /run are managed by the kernel and -+ systemd and have specific requirements for their fstab entries. Defining -+ them with block devices (via /dev/*, UUID=, LABEL=, etc.) or wrong -+ filesystem types has been invalid since RHEL 7, but such configurations -+ are typically ignored during normal boot. During the upgrade, these entries -+ are applied as configured, which may lead to failures. This actor ensures -+ users correct these entries before proceeding. -+ """ -+ name = 'check_fstab_api_fs_override' -+ consumes = (StorageInfo,) -+ produces = (Report,) -+ tags = (ChecksPhaseTag, IPUWorkflowTag) -+ -+ def process(self): -+ check_fstab_api_fs_override() -diff --git a/repos/system_upgrade/common/actors/checkfstabapifsoverride/libraries/checkfstabapifsoverride.py b/repos/system_upgrade/common/actors/checkfstabapifsoverride/libraries/checkfstabapifsoverride.py -new file mode 100644 -index 00000000..8fce90fd ---- /dev/null -+++ b/repos/system_upgrade/common/actors/checkfstabapifsoverride/libraries/checkfstabapifsoverride.py -@@ -0,0 +1,80 @@ -+from leapp import reporting -+from leapp.libraries.stdlib import api, format_list -+from leapp.models import StorageInfo -+ -+API_FS_EXPECTED_TYPES = { -+ '/dev/shm': {'tmpfs'}, -+ '/dev/pts': {'devpts'}, -+ '/dev/mqueue': {'mqueue'}, -+ '/dev/hugepages': {'hugetlbfs'}, -+ '/proc': {'proc'}, -+ '/sys': {'sysfs'}, -+ '/sys/fs/cgroup': {'cgroup', 'cgroup2', 'tmpfs'}, -+ '/sys/fs/selinux': {'selinuxfs'}, -+ '/sys/kernel/debug': {'debugfs'}, -+ '/sys/kernel/security': {'securityfs'}, -+ '/run': {'tmpfs'}, -+ '/run/lock': {'tmpfs'}, -+} -+ -+ -+def check_fstab_api_fs_override(): -+ """ -+ Check fstab entries targeting pseudo-filesystem mountpoints for invalid sources or types. -+ -+ An entry is flagged if its mount point is a known pseudo-filesystem path and -+ either the filesystem type or the source device does not match the expected -+ virtual filesystem set. The 'none' pseudo-source is also accepted. -+ """ -+ storage_info = next(api.consume(StorageInfo), None) -+ if not storage_info: -+ return -+ -+ bad_entries = [] -+ for entry in storage_info.fstab: -+ mount_point = entry.fs_file -+ if mount_point != '/': -+ mount_point = mount_point.rstrip('/') -+ -+ expected = API_FS_EXPECTED_TYPES.get(mount_point) -+ if not expected: -+ continue -+ -+ if entry.fs_vfstype not in expected or entry.fs_spec not in (expected | {'none'}): -+ bad_entries.append(entry) -+ -+ if not bad_entries: -+ return -+ -+ entries_text = format_list( -+ [e.fs_file for e in bad_entries], -+ ) -+ -+ reporting.create_report([ -+ reporting.Title( -+ 'Detected invalid entries in /etc/fstab for pseudo-filesystem mountpoints' -+ ), -+ reporting.Summary( -+ 'Detected /etc/fstab entries that incorrectly define pseudo-filesystem ' -+ 'mountpoints. These mountpoints (e.g. /proc, /sys, /dev/shm, /run) are ' -+ 'managed by the kernel and systemd, and their fstab entries must use the ' -+ 'correct virtual filesystem type and source. Defining them with a block ' -+ 'device (via /dev/*, UUID=, LABEL=, etc.) or a wrong filesystem type is ' -+ 'invalid. Such configurations were common on RHEL 6 and earlier and are ' -+ 'typically ignored during normal boot. However, during the upgrade the ' -+ 'system applies fstab entries as configured, which may lead to failures.\n' -+ 'Problematic entries: {}'.format(entries_text) -+ ), -+ reporting.Remediation( -+ hint=( -+ 'Remove or correct the problematic entries in /etc/fstab. If an entry ' -+ 'is required (e.g. to set specific mount options for compliance), ensure ' -+ 'both the source and the filesystem type match the expected virtual ' -+ "filesystem (e.g. 'tmpfs /dev/shm tmpfs defaults 0 0').\n" -+ 'After editing, run "mount -a" to verify.' -+ ) -+ ), -+ reporting.RelatedResource('file', '/etc/fstab'), -+ reporting.Severity(reporting.Severity.HIGH), -+ reporting.Groups([reporting.Groups.FILESYSTEM, reporting.Groups.INHIBITOR]), -+ ]) -diff --git a/repos/system_upgrade/common/actors/checkfstabapifsoverride/tests/test_checkfstabapifsoverride.py b/repos/system_upgrade/common/actors/checkfstabapifsoverride/tests/test_checkfstabapifsoverride.py -new file mode 100644 -index 00000000..1616021f ---- /dev/null -+++ b/repos/system_upgrade/common/actors/checkfstabapifsoverride/tests/test_checkfstabapifsoverride.py -@@ -0,0 +1,121 @@ -+import pytest -+ -+from leapp import reporting -+from leapp.libraries.actor.checkfstabapifsoverride import check_fstab_api_fs_override -+from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked -+from leapp.libraries.stdlib import api -+from leapp.models import FstabEntry, StorageInfo -+ -+ -+def _fstab(fs_spec, fs_file, fs_vfstype, fs_mntops='defaults'): -+ return FstabEntry( -+ fs_spec=fs_spec, fs_file=fs_file, fs_vfstype=fs_vfstype, -+ fs_mntops=fs_mntops, fs_freq='0', fs_passno='0', -+ ) -+ -+ -+# --- Entries that SHOULD inhibit (invalid API fs overrides) --- -+_SHM_LVM = _fstab('/dev/mapper/vg00-shm', '/dev/shm', 'xfs') -+_SHM_TMPFS_LABEL = _fstab('LABEL=mydata', '/dev/shm', 'tmpfs') -+_SHM_LVM_TRAILING = _fstab('/dev/mapper/vg00-shm', '/dev/shm/', 'xfs') -+_PROC_EXT4 = _fstab('/dev/sda5', '/proc', 'ext4') -+_SYS_LVM = _fstab('/dev/mapper/vg00-sys', '/sys', 'xfs') -+_SYS_LVM_SYSFS = _fstab('/dev/mapper/vg00-sys', '/sys', 'sysfs') -+_SYS_UUID = _fstab('UUID=023e51f5-b590-4de5-ab21-cf33ecasasas', '/sys', 'xfs') -+_SYS_UUID_SYSFS = _fstab('UUID=023e51f5-b590-4de5-ab21-cf33ecasasas', '/sys', 'sysfs') -+_RUN_PARTUUID = _fstab('PARTUUID=abcd-1234', '/run', 'ext4') -+_RUN_UUID = _fstab('UUID=aabbccdd-1234-5678-9012-aabbccddeeff', '/run', 'ext4') -+_SHM_UUID = _fstab('UUID=12345678-abcd-ef01-2345-6789abcdef01', '/dev/shm', 'xfs') -+_PROC_BIND = _fstab('/some/dir', '/proc', 'none', fs_mntops='bind') -+ -+# --- Entries that should NOT inhibit --- -+_SHM_TMPFS = _fstab('tmpfs', '/dev/shm', 'tmpfs') -+_PROC_PROC = _fstab('proc', '/proc', 'proc') -+_SYS_SYSFS = _fstab('sysfs', '/sys', 'sysfs') -+_PROC_NONE = _fstab('none', '/proc', 'proc') -+_RUN_TMPFS = _fstab('tmpfs', '/run', 'tmpfs') -+_HOME_EXT4 = _fstab('/dev/mapper/fedora-home', '/home', 'ext4') -+_ROOT_XFS = _fstab('/dev/mapper/fedora-root', '/', 'xfs') -+_VAR_UUID = _fstab('UUID=aabbccdd-1122-3344-5566-778899aabbcc', '/var', 'xfs') -+_OPT_LABEL = _fstab('LABEL=optdata', '/opt', 'ext4') -+_DATA_PARTUUID = _fstab('PARTUUID=abcd-5678', '/data', 'xfs') -+_BOOT_UUID = _fstab('UUID=01234567-89ab-cdef-0123-456789abcdef', '/boot', 'ext2') -+_MNT_LABEL = _fstab('LABEL=backup', '/mnt/backup', 'ext4') -+_SRV_LVM = _fstab('/dev/mapper/vg00-srv', '/srv', 'xfs') -+_HOME_BIND = _fstab('/exports/home', '/home', 'none', fs_mntops='bind') -+_MNT_BIND = _fstab('/data/shared', '/mnt/shared', 'none', fs_mntops='bind') -+ -+ -+@pytest.mark.parametrize( -+ ('fstab_entries', 'should_inhibit'), -+ [ -+ # Invalid overrides of pseudo-filesystem mountpoints -+ ([_SHM_LVM], True), -+ ([_PROC_EXT4], True), -+ ([_SHM_LVM_TRAILING], True), -+ ([_SYS_LVM], True), -+ ([_SYS_LVM_SYSFS], True), -+ ([_SYS_UUID], True), -+ ([_SYS_UUID_SYSFS], True), -+ ([_SHM_TMPFS_LABEL], True), -+ ([_RUN_PARTUUID], True), -+ ([_RUN_UUID], True), -+ ([_SHM_UUID], True), -+ ([_PROC_BIND], True), -+ # Valid pseudo-filesystem entries -+ ([_SHM_TMPFS, _PROC_PROC, _SYS_SYSFS], False), -+ ([_PROC_NONE], False), -+ ([_RUN_TMPFS], False), -+ # Normal mounts (not pseudo-fs paths) with various source types -+ ([_HOME_EXT4, _ROOT_XFS], False), -+ ([_VAR_UUID], False), -+ ([_OPT_LABEL], False), -+ ([_DATA_PARTUUID], False), -+ ([_BOOT_UUID], False), -+ ([_MNT_LABEL], False), -+ ([_SRV_LVM], False), -+ # Bind mounts on normal paths -+ ([_HOME_BIND], False), -+ ([_MNT_BIND], False), -+ # Mixed valid entries -+ ([_HOME_EXT4, _VAR_UUID, _BOOT_UUID, _SHM_TMPFS], False), -+ # Empty fstab -+ ([], False), -+ ], -+) -+def test_check_fstab_api_fs_override(monkeypatch, fstab_entries, should_inhibit): -+ created_reports = create_report_mocked() -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked( -+ msgs=[StorageInfo(fstab=fstab_entries)] -+ )) -+ monkeypatch.setattr(reporting, 'create_report', created_reports) -+ -+ check_fstab_api_fs_override() -+ -+ assert bool(created_reports.called) == should_inhibit -+ -+ -+def test_report_lists_only_problematic_entries(monkeypatch): -+ created_reports = create_report_mocked() -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked( -+ msgs=[StorageInfo(fstab=[_SHM_LVM, _PROC_EXT4, _HOME_EXT4])] -+ )) -+ monkeypatch.setattr(reporting, 'create_report', created_reports) -+ -+ check_fstab_api_fs_override() -+ -+ assert created_reports.called == 1 -+ summary = created_reports.reports[0]['summary'] -+ assert '/dev/shm' in summary -+ assert '/proc' in summary -+ assert '/home' not in summary -+ -+ -+def test_no_storage_info(monkeypatch): -+ created_reports = create_report_mocked() -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) -+ monkeypatch.setattr(reporting, 'create_report', created_reports) -+ -+ check_fstab_api_fs_override() -+ -+ assert not created_reports.called --- -2.54.0 - diff --git a/0085-lib-version-Remove-deprecated-is_rhel_realtime-funct.patch b/0085-lib-version-Remove-deprecated-is_rhel_realtime-funct.patch deleted file mode 100644 index 1b5b6d3..0000000 --- a/0085-lib-version-Remove-deprecated-is_rhel_realtime-funct.patch +++ /dev/null @@ -1,81 +0,0 @@ -From 83a3b58313331ff906e6a5823d23d4ac83779699 Mon Sep 17 00:00:00 2001 -From: Matej Matuska -Date: Fri, 29 May 2026 13:32:34 +0200 -Subject: [PATCH 085/105] lib/version: Remove deprecated is_rhel_realtime - function - -The function is deprecated long enough and also importing version lib in -kernel lib would cause unnecessarty circular imports. ---- - .../libraries/config/tests/test_version.py | 13 ----------- - .../common/libraries/config/version.py | 23 ------------------- - 2 files changed, 36 deletions(-) - -diff --git a/repos/system_upgrade/common/libraries/config/tests/test_version.py b/repos/system_upgrade/common/libraries/config/tests/test_version.py -index f36dbc5f..c7054893 100644 ---- a/repos/system_upgrade/common/libraries/config/tests/test_version.py -+++ b/repos/system_upgrade/common/libraries/config/tests/test_version.py -@@ -132,19 +132,6 @@ def test_is_supported_version(monkeypatch, result, src_ver, is_saphana): - assert version.is_supported_version() == result - - --@pytest.mark.parametrize('result,kernel,release_id', [ -- (False, '3.10.0-790.el7.x86_64', 'rhel'), -- (False, '3.10.0-790.el7.x86_64', 'fedora'), -- (False, '3.10.0-790.35.2.rt666.1133.el7.x86_64', 'fedora'), -- (True, '3.10.0-790.35.2.rt666.1133.el7.x86_64', 'rhel'), --]) --@suppress_deprecation(version.is_rhel_realtime) --def test_is_rhel_realtime(monkeypatch, result, kernel, release_id): -- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(src_ver='7.9', kernel=kernel, -- release_id=release_id)) -- assert version.is_rhel_realtime() == result -- -- - @pytest.mark.parametrize('result,sys_version', [ - ('7', '7.1.0'), - ('7', '7.9'), -diff --git a/repos/system_upgrade/common/libraries/config/version.py b/repos/system_upgrade/common/libraries/config/version.py -index 5c1e30c6..8eb02ef1 100644 ---- a/repos/system_upgrade/common/libraries/config/version.py -+++ b/repos/system_upgrade/common/libraries/config/version.py -@@ -3,7 +3,6 @@ import re - - import six - --from leapp.libraries.common import kernel as kernel_lib - from leapp.libraries.stdlib import api - from leapp.utils.deprecation import deprecated - -@@ -335,28 +334,6 @@ def is_rhel_alt(): - return conf.os_release.release_id == 'rhel' and conf.kernel[0] == '4' - - --@deprecated(since='2023-08-15', message='This information is now provided by KernelInfo message.') --def is_rhel_realtime(): -- """ -- Check whether the original system is RHEL Real Time. -- -- Currently the check is based on the release of the original booted kernel. -- In case of RHEL, we are sure the release contains the ".rt" string and -- non-realtime kernels don't. Let's use this minimalistic check for now. -- In future, we could detect whether the system is preemptive or not based -- on properties of the kernel (e.g. uname -a tells that information). -- -- :return: `True` if the orig system is RHEL RT and `False` otherwise. -- :rtype: bool -- """ -- conf = api.current_actor().configuration -- if conf.os_release.release_id != 'rhel': -- return False -- -- kernel_type = kernel_lib.determine_kernel_type_from_uname(get_source_version(), conf.kernel) -- return kernel_type == kernel_lib.KernelType.REALTIME -- -- - def is_supported_version(): - """ - Verify if the current system version is supported for the upgrade. --- -2.54.0 - diff --git a/0086-lib-kernel-Fix-rt-kernel-detection-on-centos.patch b/0086-lib-kernel-Fix-rt-kernel-detection-on-centos.patch deleted file mode 100644 index 126178d..0000000 --- a/0086-lib-kernel-Fix-rt-kernel-detection-on-centos.patch +++ /dev/null @@ -1,142 +0,0 @@ -From 68e286f5d36fc76ce06e8feff2fe418c96f3a9f5 Mon Sep 17 00:00:00 2001 -From: Matej Matuska -Date: Fri, 29 May 2026 13:33:23 +0200 -Subject: [PATCH 086/105] lib/kernel: Fix rt kernel detection on centos - -The kernel detection function determine_kernel_type_from_uname() didn't -take major-only versions as used for CentOS Stream into account and -therefore it misidentified rt kernels as "ordinary" on CentOS Stream -systems. - -The function is modified to use matches_version() from the version lib -which already handles the versions comparison for CentOS Stream as. - -Jira: RHEL-161560 ---- - .../common/libraries/config/version.py | 13 ++++++ - .../system_upgrade/common/libraries/kernel.py | 10 ++--- - .../common/libraries/tests/test_kernel_lib.py | 43 ++++++++++++++----- - 3 files changed, 49 insertions(+), 17 deletions(-) - -diff --git a/repos/system_upgrade/common/libraries/config/version.py b/repos/system_upgrade/common/libraries/config/version.py -index 8eb02ef1..faba3e68 100644 ---- a/repos/system_upgrade/common/libraries/config/version.py -+++ b/repos/system_upgrade/common/libraries/config/version.py -@@ -171,6 +171,19 @@ def matches_version(match_list, detected): - """ - Check if the `detected` version meets the criteria specified in `match_list`. - -+ For CentOS Stream (which natively uses only major versions), the match list -+ and the detected version are automatically adjusted to ensure intuitive evaluation: -+ -+ - If `match_list` contains only major versions, the comparison is restricted -+ to the major version component: -+ version.matches_version(['> 8', '<= 9'], '9.5') # True -+ -+ - If `match_list` contains major.minor versions but `detected` is a major-only -+ version, the function resolves the input to its defined "virtual version" -+ before evaluation: -+ # e.g., if the virtual version for '9' is defined as '9.5' -+ version.matches_version(['> 8.10', '<= 9.7'], '9') # True -+ - :param match_list: specification of versions to check against - :type match_list: list or tuple of strings in one of the two following forms: - ``['>'|'>='|'<'|'<='] .`` form, where elements are ANDed, -diff --git a/repos/system_upgrade/common/libraries/kernel.py b/repos/system_upgrade/common/libraries/kernel.py -index fc8c1167..0a160e7f 100644 ---- a/repos/system_upgrade/common/libraries/kernel.py -+++ b/repos/system_upgrade/common/libraries/kernel.py -@@ -1,6 +1,7 @@ - from collections import namedtuple - - from leapp.exceptions import StopActorExecutionError -+from leapp.libraries.common.config.version import matches_version - from leapp.libraries.stdlib import api, CalledProcessError, run - - KernelPkgInfo = namedtuple('KernelPkgInfo', ('name', 'version', 'release', 'arch', 'nevra')) -@@ -14,6 +15,8 @@ class KernelType: - REALTIME = 'realtime' - - -+# TODO: rename rhel_version to something distro agnostic in the new function -+# when this is deprecated - def determine_kernel_type_from_uname(rhel_version, kernel_uname_r): - """ - Determine kernel type from given kernel release (uname-r). -@@ -23,12 +26,7 @@ def determine_kernel_type_from_uname(rhel_version, kernel_uname_r): - :returns: Kernel type based on a given uname_r - :rtype: KernelType - """ -- version_fragments = rhel_version.split('.') -- major_ver = version_fragments[0] -- minor_ver = version_fragments[1] if len(version_fragments) > 1 else '0' -- rhel_version = (major_ver, minor_ver) -- -- if rhel_version <= ('9', '2'): -+ if matches_version(['<= 9.2'], rhel_version): - uname_r_infixes = { - '.rt': KernelType.REALTIME - } -diff --git a/repos/system_upgrade/common/libraries/tests/test_kernel_lib.py b/repos/system_upgrade/common/libraries/tests/test_kernel_lib.py -index a6696a3c..1687d0dc 100644 ---- a/repos/system_upgrade/common/libraries/tests/test_kernel_lib.py -+++ b/repos/system_upgrade/common/libraries/tests/test_kernel_lib.py -@@ -5,23 +5,44 @@ import pytest - from leapp.exceptions import StopActorExecutionError - from leapp.libraries.common import kernel as kernel_lib - from leapp.libraries.common.kernel import KernelType -+from leapp.libraries.common.testutils import CurrentActorMocked -+from leapp.libraries.stdlib import api - - - @pytest.mark.parametrize( -- ('rhel_version', 'uname_r', 'expected_kernel_type'), -+ ('version', 'uname_r', 'expected_kernel_type'), - ( -- ('7.9', '3.10.0-1160.el7.x86_64', KernelType.ORDINARY), -- ('7.9', '3.10.0-1160.rt56.1131.el7.x86_64', KernelType.REALTIME), -- ('8.7', '4.18.0-425.3.1.el8.x86_64', KernelType.ORDINARY), -- ('8.7', '4.18.0-425.3.1.rt7.213.el8.x86_64', KernelType.REALTIME), -- ('9.2', '5.14.0-284.11.1.el9_2.x86_64', KernelType.ORDINARY), -- ('9.2', '5.14.0-284.11.1.rt14.296.el9_2.x86_64', KernelType.REALTIME), -- ('9.3', '5.14.0-354.el9.x86_64', KernelType.ORDINARY), -- ('9.3', '5.14.0-354.el9.x86_64+rt', KernelType.REALTIME), -+ # (version, virtual_version) -+ (('7.9', None), '3.10.0-1160.el7.x86_64', KernelType.ORDINARY), -+ (('7.9', None), '3.10.0-1160.rt56.1131.el7.x86_64', KernelType.REALTIME), -+ (('8.7', None), '4.18.0-425.3.1.el8.x86_64', KernelType.ORDINARY), -+ (('8.7', None), '4.18.0-425.3.1.rt7.213.el8.x86_64', KernelType.REALTIME), -+ (('9.2', None), '5.14.0-284.11.1.el9_2.x86_64', KernelType.ORDINARY), -+ (('9.2', None), '5.14.0-284.11.1.rt14.296.el9_2.x86_64', KernelType.REALTIME), -+ (('9.3', None), '5.14.0-354.el9.x86_64', KernelType.ORDINARY), -+ (('9.3', None), '5.14.0-354.el9.x86_64+rt', KernelType.REALTIME), -+ # centos -+ (('8', '8.7'), '4.18.0-425.3.1.el8.x86_64', KernelType.ORDINARY), -+ (('8', '8.7'), '4.18.0-425.3.1.rt7.213.el8.x86_64', KernelType.REALTIME), -+ (('9', '9.2'), '5.14.0-284.11.1.el9_2.x86_64', KernelType.ORDINARY), -+ (('9', '9.2'), '5.14.0-284.11.1.rt14.296.el9_2.x86_64', KernelType.REALTIME), -+ (('9', '9.3'), '5.14.0-354.el9.x86_64', KernelType.ORDINARY), -+ (('9', '9.3'), '5.14.0-354.el9.x86_64+rt', KernelType.REALTIME), - ) - ) --def test_determine_kernel_type_from_uname(rhel_version, uname_r, expected_kernel_type): -- kernel_type = kernel_lib.determine_kernel_type_from_uname(rhel_version, uname_r) -+def test_determine_kernel_type_from_uname(monkeypatch, version, uname_r, expected_kernel_type): -+ real_ver, virtual_ver = version -+ # needed to for lookups of virtual versions in matches_version -+ actor_mock = CurrentActorMocked( -+ release_id='centos' if '.' not in real_ver else 'rhel', -+ src_ver=real_ver, -+ dst_ver="irrelevant", -+ virtual_source_version=virtual_ver or real_ver, -+ virtual_target_version="irrelevant", -+ ) -+ monkeypatch.setattr(api, 'current_actor', actor_mock) -+ -+ kernel_type = kernel_lib.determine_kernel_type_from_uname(real_ver, uname_r) - assert kernel_type == expected_kernel_type - - --- -2.54.0 - diff --git a/0087-Add-CLAUDE.md-and-YAML-frontmatter-to-skills-for-Cla.patch b/0087-Add-CLAUDE.md-and-YAML-frontmatter-to-skills-for-Cla.patch deleted file mode 100644 index 00a91cf..0000000 --- a/0087-Add-CLAUDE.md-and-YAML-frontmatter-to-skills-for-Cla.patch +++ /dev/null @@ -1,78 +0,0 @@ -From 1093f58a66a6221519714332d09126c76c5712c1 Mon Sep 17 00:00:00 2001 -From: karolinku -Date: Thu, 28 May 2026 17:11:17 +0200 -Subject: [PATCH 087/105] Add CLAUDE.md and YAML frontmatter to skills for - Claude Code compatibility - -CLAUDE.md imports AGENTS.md so Claude Code uses the same instructions -as other agents without duplicating content. YAML frontmatter (name, -description) is added to each SKILL.md to satisfy Claude Code's skill -metadata requirements. Add symlink ../skills -> .claude/skills to -enable Claude's auto-discovery of skills. ---- - .claude/skills | 1 + - CLAUDE.md | 1 + - skills/leapp-actor-dev/SKILL.md | 5 +++++ - skills/leapp-test-runner/SKILL.md | 5 +++++ - skills/leapp-unit-tests/SKILL.md | 5 +++++ - 5 files changed, 17 insertions(+) - create mode 120000 .claude/skills - create mode 100644 CLAUDE.md - -diff --git a/.claude/skills b/.claude/skills -new file mode 120000 -index 00000000..42c5394a ---- /dev/null -+++ b/.claude/skills -@@ -0,0 +1 @@ -+../skills -\ No newline at end of file -diff --git a/CLAUDE.md b/CLAUDE.md -new file mode 100644 -index 00000000..141137e7 ---- /dev/null -+++ b/CLAUDE.md -@@ -0,0 +1 @@ -+@import AGENTS.md -diff --git a/skills/leapp-actor-dev/SKILL.md b/skills/leapp-actor-dev/SKILL.md -index 124f3e99..5bfdc97a 100644 ---- a/skills/leapp-actor-dev/SKILL.md -+++ b/skills/leapp-actor-dev/SKILL.md -@@ -1,3 +1,8 @@ -+--- -+name: leapp-actor-dev -+description: Develop or modify leapp-repository actors. Covers actor structure, phase selection, placement rules, and key constraints. -+--- -+ - # Developing Leapp Actors - - ## Actor structure -diff --git a/skills/leapp-test-runner/SKILL.md b/skills/leapp-test-runner/SKILL.md -index 579aeb5d..18640e3f 100644 ---- a/skills/leapp-test-runner/SKILL.md -+++ b/skills/leapp-test-runner/SKILL.md -@@ -1,3 +1,8 @@ -+--- -+name: leapp-test-runner -+description: Run tests and linters for leapp-repository. Covers container-based CI testing, single-actor fast tests, and lint commands. -+--- -+ - # Running Tests and Linters - - All non-container commands (`make lint`, `make test_no_lint`, `make fast_lint`, `make dev_test_no_lint`) require a local virtualenv and `REPOSITORIES` envar (e.g. `REPOSITORIES=common,el9toel10`). Create the virtualenv with `make install-deps` (RHEL/CentOS) or `make install-deps-fedora` (Fedora) — the Makefile handles activation internally, don't source the venv manually. Set `PYTHON_VENV=pythonX.X` to choose the Python version (default: `python3.6`). Container commands are self-contained and recommended as the default. -diff --git a/skills/leapp-unit-tests/SKILL.md b/skills/leapp-unit-tests/SKILL.md -index 894d4271..e5189f31 100644 ---- a/skills/leapp-unit-tests/SKILL.md -+++ b/skills/leapp-unit-tests/SKILL.md -@@ -1,3 +1,8 @@ -+--- -+name: leapp-unit-tests -+description: Write or fix unit tests for leapp-repository actors and shared libraries. Covers test placement, mocking patterns, and project conventions. -+--- -+ - # Writing Unit Tests - - --- -2.54.0 - diff --git a/0088-Add-inhibitor-for-incorrect-var-run-symlink-configur.patch b/0088-Add-inhibitor-for-incorrect-var-run-symlink-configur.patch deleted file mode 100644 index 28ae3fa..0000000 --- a/0088-Add-inhibitor-for-incorrect-var-run-symlink-configur.patch +++ /dev/null @@ -1,249 +0,0 @@ -From 342e14b4e05524fd0925aeba1f3294b3cbd8efcf Mon Sep 17 00:00:00 2001 -From: Pradeep Jagtap -Date: Mon, 1 Jun 2026 16:38:46 +0530 -Subject: [PATCH 088/105] Add inhibitor for incorrect /var/run symlink - configuration - -In modern Linux systems that use systemd, /run is a tmpfs filesystem -managed by systemd and /var/run must be a symbolic link pointing to -../run for compatibility. Inhibit the upgrade if /var/run is not -configured as expected, as this can cause boot failures, service -startup failures, or login issues. - -Extended the FileInfo model with a real_path field and add /var/run to -the scansourcefiles tracked files list. The ChecksPhase actor consumes -the TrackedFilesInfoSource message to detect this unsupported -configuration and inhibits the upgrade. - -JIRA: RHEL-76846 ---- - .../common/actors/checkvarrunsymlink/actor.py | 25 ++++++++++ - .../libraries/checkvarrunsymlink.py | 49 +++++++++++++++++++ - .../tests/test_checkvarrunsymlink.py | 41 ++++++++++++++++ - .../libraries/scansourcefiles.py | 5 +- - .../tests/unit_test_scansourcefiles.py | 23 +++++---- - .../common/models/trackedfiles.py | 5 ++ - 6 files changed, 138 insertions(+), 10 deletions(-) - create mode 100644 repos/system_upgrade/common/actors/checkvarrunsymlink/actor.py - create mode 100644 repos/system_upgrade/common/actors/checkvarrunsymlink/libraries/checkvarrunsymlink.py - create mode 100644 repos/system_upgrade/common/actors/checkvarrunsymlink/tests/test_checkvarrunsymlink.py - -diff --git a/repos/system_upgrade/common/actors/checkvarrunsymlink/actor.py b/repos/system_upgrade/common/actors/checkvarrunsymlink/actor.py -new file mode 100644 -index 00000000..512ed1a8 ---- /dev/null -+++ b/repos/system_upgrade/common/actors/checkvarrunsymlink/actor.py -@@ -0,0 +1,25 @@ -+from leapp.actors import Actor -+from leapp.libraries.actor import checkvarrunsymlink -+from leapp.models import TrackedFilesInfoSource -+from leapp.reporting import Report -+from leapp.tags import ChecksPhaseTag, IPUWorkflowTag -+ -+ -+class CheckVarRunSymlink(Actor): -+ """ -+ Check that /var/run is a symlink pointing to /run. -+ -+ In modern Linux systems that use systemd, /run is a tmpfs filesystem -+ managed by systemd and /var/run must be a symbolic link pointing to -+ ../run for compatibility. Inhibit the upgrade if /var/run is not -+ configured as expected, as this can cause boot failures, service -+ startup failures, or login issues. -+ """ -+ -+ name = 'check_var_run_symlink' -+ consumes = (TrackedFilesInfoSource,) -+ produces = (Report,) -+ tags = (ChecksPhaseTag, IPUWorkflowTag) -+ -+ def process(self): -+ checkvarrunsymlink.process() -diff --git a/repos/system_upgrade/common/actors/checkvarrunsymlink/libraries/checkvarrunsymlink.py b/repos/system_upgrade/common/actors/checkvarrunsymlink/libraries/checkvarrunsymlink.py -new file mode 100644 -index 00000000..e2b198c1 ---- /dev/null -+++ b/repos/system_upgrade/common/actors/checkvarrunsymlink/libraries/checkvarrunsymlink.py -@@ -0,0 +1,49 @@ -+from leapp import reporting -+from leapp.libraries.stdlib import api -+from leapp.models import TrackedFilesInfoSource -+ -+VAR_RUN_PATH = '/var/run' -+ -+ -+def _get_real_path(tracked_files): -+ for finfo in tracked_files.files: -+ if finfo.path == VAR_RUN_PATH: -+ return finfo.real_path -+ return None -+ -+ -+def process(): -+ tracked_files = next(api.consume(TrackedFilesInfoSource), None) -+ if not tracked_files: -+ api.current_logger().warning( -+ 'The TrackedFilesInfoSource message is missing. Skipping the check of /var/run symlink.' -+ ) -+ return -+ -+ real_path = _get_real_path(tracked_files) -+ if real_path is None or real_path == '/run': -+ return -+ -+ reporting.create_report([ -+ reporting.Title('/var/run is not correctly configured'), -+ reporting.Summary( -+ 'In modern Linux systems that use systemd, /run is a tmpfs filesystem' -+ ' managed by systemd and /var/run must be a symbolic link pointing to' -+ ' ../run for compatibility. The current system does not have /var/run' -+ ' configured as expected. This can cause boot failures, service startup' -+ ' failures, or login issues both on the current system and after an' -+ ' in-place upgrade.' -+ ), -+ reporting.Remediation( -+ hint=( -+ 'Back up current /var/run directory if needed and replace it by' -+ ' symbolic link pointing to "../run".' -+ ), -+ commands=[ -+ ['bash', '-c', 'mv /var/run /tmp/var_run.bak && ln -s ../run /var/run'], -+ ] -+ ), -+ reporting.Severity(reporting.Severity.HIGH), -+ reporting.Groups([reporting.Groups.FILESYSTEM, reporting.Groups.INHIBITOR]), -+ reporting.RelatedResource('directory', VAR_RUN_PATH), -+ ]) -diff --git a/repos/system_upgrade/common/actors/checkvarrunsymlink/tests/test_checkvarrunsymlink.py b/repos/system_upgrade/common/actors/checkvarrunsymlink/tests/test_checkvarrunsymlink.py -new file mode 100644 -index 00000000..2df218e8 ---- /dev/null -+++ b/repos/system_upgrade/common/actors/checkvarrunsymlink/tests/test_checkvarrunsymlink.py -@@ -0,0 +1,41 @@ -+import pytest -+ -+from leapp import reporting -+from leapp.libraries.actor import checkvarrunsymlink -+from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked -+from leapp.libraries.stdlib import api -+from leapp.models import FileInfo, TrackedFilesInfoSource -+from leapp.utils.report import is_inhibitor -+ -+ -+def _msg_varrun(real_path): -+ return TrackedFilesInfoSource(files=[ -+ FileInfo(path='/var/run', exists=True, is_modified=False, real_path=real_path) -+ ]) -+ -+ -+@pytest.mark.parametrize('real_path, should_inhibit', [ -+ ('/run', False), -+ ('/var/run', True), -+ ('/tmp/foo', True), -+]) -+def test_check_var_run_symlink(monkeypatch, real_path, should_inhibit): -+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked( -+ msgs=[_msg_varrun(real_path)] -+ )) -+ -+ checkvarrunsymlink.process() -+ -+ assert bool(reporting.create_report.called) == should_inhibit -+ if should_inhibit: -+ assert is_inhibitor(reporting.create_report.report_fields) -+ -+ -+def test_check_var_run_missing_message(monkeypatch): -+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[])) -+ -+ checkvarrunsymlink.process() -+ -+ assert not reporting.create_report.called -diff --git a/repos/system_upgrade/common/actors/scansourcefiles/libraries/scansourcefiles.py b/repos/system_upgrade/common/actors/scansourcefiles/libraries/scansourcefiles.py -index 16c0e8aa..7c3ddbe3 100644 ---- a/repos/system_upgrade/common/actors/scansourcefiles/libraries/scansourcefiles.py -+++ b/repos/system_upgrade/common/actors/scansourcefiles/libraries/scansourcefiles.py -@@ -10,6 +10,7 @@ from leapp.models import FileInfo, TrackedFilesInfoSource - TRACKED_FILES = { - 'common': [ - '/etc/pki/tls/openssl.cnf', -+ '/var/run', - ], - '8': [ - ], -@@ -56,10 +57,12 @@ def is_modified(input_file): - - - def scan_file(input_file): -+ exists = os.path.exists(input_file) - data = { - 'path': input_file, -- 'exists': os.path.exists(input_file), -+ 'exists': exists, - 'rpm_name': _get_rpm_name(input_file), -+ 'real_path': os.path.realpath(input_file) if exists else '', - } - - if data['rpm_name']: -diff --git a/repos/system_upgrade/common/actors/scansourcefiles/tests/unit_test_scansourcefiles.py b/repos/system_upgrade/common/actors/scansourcefiles/tests/unit_test_scansourcefiles.py -index 40ae2408..ed262126 100644 ---- a/repos/system_upgrade/common/actors/scansourcefiles/tests/unit_test_scansourcefiles.py -+++ b/repos/system_upgrade/common/actors/scansourcefiles/tests/unit_test_scansourcefiles.py -@@ -66,22 +66,27 @@ def test_get_rpm_name_error(monkeypatch): - - - @pytest.mark.parametrize( -- ('input_file', 'exists', 'rpm_name', 'is_modified'), -+ ('input_file', 'exists', 'rpm_name', 'is_modified', 'real_path'), - ( -- ('/not_existing_file', False, '', False), -- ('/not_existing_file_rpm_owned', False, 'rpm', False), -- ('/not_existing_file_rpm_owned_modified', False, 'rpm', True), -- ('/existing_file_not_modified', True, '', False), -- ('/existing_file_owned_by_rpm_not_modified', True, 'rpm', False), -- ('/existing_file_owned_by_rpm_modified', True, 'rpm', True), -+ ('/not_existing_file', False, '', False, ''), -+ ('/not_existing_file_rpm_owned', False, 'rpm', False, ''), -+ ('/not_existing_file_rpm_owned_modified', False, 'rpm', True, ''), -+ ('/existing_file_not_modified', True, '', False, '/existing_file_not_modified'), -+ ('/existing_file_owned_by_rpm_not_modified', True, 'rpm', False, '/existing_file_owned_by_rpm_not_modified'), -+ ('/existing_file_owned_by_rpm_modified', True, 'rpm', True, '/existing_file_owned_by_rpm_modified'), -+ ('/existing_symlink', True, '', False, '/target'), - ) - ) --def test_scan_file(monkeypatch, input_file, exists, rpm_name, is_modified): -+def test_scan_file(monkeypatch, input_file, exists, rpm_name, is_modified, real_path): - monkeypatch.setattr(scansourcefiles, 'is_modified', lambda _: is_modified) - monkeypatch.setattr(scansourcefiles, '_get_rpm_name', lambda _: rpm_name) - monkeypatch.setattr(os.path, 'exists', lambda _: exists) -+ monkeypatch.setattr(os.path, 'realpath', lambda _: real_path) - -- expected_model_output = FileInfo(path=input_file, exists=exists, rpm_name=rpm_name, is_modified=is_modified) -+ expected_model_output = FileInfo( -+ path=input_file, exists=exists, rpm_name=rpm_name, -+ is_modified=is_modified, real_path=real_path -+ ) - assert scansourcefiles.scan_file(input_file) == expected_model_output - - -diff --git a/repos/system_upgrade/common/models/trackedfiles.py b/repos/system_upgrade/common/models/trackedfiles.py -index f7c2c809..a3f6e9f5 100644 ---- a/repos/system_upgrade/common/models/trackedfiles.py -+++ b/repos/system_upgrade/common/models/trackedfiles.py -@@ -40,6 +40,11 @@ class FileInfo(Model): - In such a case the value is always false. - """ - -+ real_path = fields.String(default="") -+ """ -+ The resolved (real) path of the file. Empty string if path does not exist. -+ """ -+ - - class TrackedFilesInfoSource(Model): - """ --- -2.54.0 - diff --git a/0089-checkvarrunsymlink-Log-warning-when-var-run-is-missi.patch b/0089-checkvarrunsymlink-Log-warning-when-var-run-is-missi.patch deleted file mode 100644 index 0a786c2..0000000 --- a/0089-checkvarrunsymlink-Log-warning-when-var-run-is-missi.patch +++ /dev/null @@ -1,34 +0,0 @@ -From c5ef9301838854790d01c2cca086e80f8a781018 Mon Sep 17 00:00:00 2001 -From: Pradeep Jagtap -Date: Wed, 10 Jun 2026 11:42:53 +0530 -Subject: [PATCH 089/105] checkvarrunsymlink: Log warning when /var/run is - missing from tracked files - -Address review feedback from #1546: emit a warning when /var/run -is unexpectedly absent from TrackedFilesInfoSource instead of -silently skipping the check. ---- - .../checkvarrunsymlink/libraries/checkvarrunsymlink.py | 7 ++++++- - 1 file changed, 6 insertions(+), 1 deletion(-) - -diff --git a/repos/system_upgrade/common/actors/checkvarrunsymlink/libraries/checkvarrunsymlink.py b/repos/system_upgrade/common/actors/checkvarrunsymlink/libraries/checkvarrunsymlink.py -index e2b198c1..4e6f030e 100644 ---- a/repos/system_upgrade/common/actors/checkvarrunsymlink/libraries/checkvarrunsymlink.py -+++ b/repos/system_upgrade/common/actors/checkvarrunsymlink/libraries/checkvarrunsymlink.py -@@ -21,7 +21,12 @@ def process(): - return - - real_path = _get_real_path(tracked_files) -- if real_path is None or real_path == '/run': -+ if real_path is None: -+ api.current_logger().warning( -+ '/var/run not found in TrackedFilesInfoSource. Skipping the symlink check.' -+ ) -+ return -+ if real_path == '/run': - return - - reporting.create_report([ --- -2.54.0 - diff --git a/0090-Add-scandnfcomps-actor-to-scan-installed-DNF-comps.patch b/0090-Add-scandnfcomps-actor-to-scan-installed-DNF-comps.patch deleted file mode 100644 index a16f8d4..0000000 --- a/0090-Add-scandnfcomps-actor-to-scan-installed-DNF-comps.patch +++ /dev/null @@ -1,419 +0,0 @@ -From a9e5a3e2343654504accb11f5d1305cacce22a90 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Tue, 9 Jun 2026 10:46:56 +0000 -Subject: [PATCH 090/105] Add scandnfcomps actor to scan installed DNF comps - -The actor scan installed DNF environments and groups (DNF comps) -which are represented by new InstalledDNFComps model. - -The scan is performed using initialized dnf.Base to load all -available groups and envs and then it checks what is installed based -on the history. - -About each group and environment, only ID and and (pretty) name -are collected. Skipping the scan of other data as we do not have a -use for that at this moment. - -Unit-tests and part of docstrings are assisted by AI. All the other -code had to be manually updated unfortunately. - -Assisted-By: Claude Opus 4.6 -Signed-off-by: Petr Stodulka ---- - .../common/actors/scandnfcomps/actor.py | 22 +++ - .../scandnfcomps/libraries/scandnfcomps.py | 87 +++++++++ - .../scandnfcomps/tests/test_scandnfcomps.py | 175 ++++++++++++++++++ - .../system_upgrade/common/models/dnfcomps.py | 76 ++++++++ - 4 files changed, 360 insertions(+) - create mode 100644 repos/system_upgrade/common/actors/scandnfcomps/actor.py - create mode 100644 repos/system_upgrade/common/actors/scandnfcomps/libraries/scandnfcomps.py - create mode 100644 repos/system_upgrade/common/actors/scandnfcomps/tests/test_scandnfcomps.py - create mode 100644 repos/system_upgrade/common/models/dnfcomps.py - -diff --git a/repos/system_upgrade/common/actors/scandnfcomps/actor.py b/repos/system_upgrade/common/actors/scandnfcomps/actor.py -new file mode 100644 -index 00000000..ddebfce0 ---- /dev/null -+++ b/repos/system_upgrade/common/actors/scandnfcomps/actor.py -@@ -0,0 +1,22 @@ -+from leapp.actors import Actor -+from leapp.libraries.actor import scandnfcomps -+from leapp.models import InstalledDNFComps -+from leapp.tags import FactsPhaseTag, IPUWorkflowTag -+ -+ -+class ScanDNFComps(Actor): -+ """ -+ Scan installed DNF comps (environments and groups). -+ -+ Collects information about DNF package environments and groups that are -+ currently installed on the source system and produce InstalledDNFComps -+ message. -+ """ -+ -+ name = 'scan_dnf_comps' -+ consumes = () -+ produces = (InstalledDNFComps,) -+ tags = (FactsPhaseTag, IPUWorkflowTag) -+ -+ def process(self): -+ scandnfcomps.process() -diff --git a/repos/system_upgrade/common/actors/scandnfcomps/libraries/scandnfcomps.py b/repos/system_upgrade/common/actors/scandnfcomps/libraries/scandnfcomps.py -new file mode 100644 -index 00000000..31b07bc0 ---- /dev/null -+++ b/repos/system_upgrade/common/actors/scandnfcomps/libraries/scandnfcomps.py -@@ -0,0 +1,87 @@ -+import warnings -+ -+from leapp.exceptions import StopActorExecutionError -+from leapp.libraries.common.dnflibs import create_dnf_base, DNFRepoError -+from leapp.libraries.stdlib import api -+from leapp.models import DNFEnvironment, DNFGroup, InstalledDNFComps -+ -+try: -+ import dnf -+except ImportError: -+ # NOTE(pstodulk): To stay consistent with other DNF related actors, but -+ # I guess we can get rid of it nowadays. -+ dnf = None -+ warnings.warn('Could not import the `dnf` python module.', ImportWarning) -+ -+ -+def _get_installed_groups(base): -+ """ -+ Query installed DNF comps groups via the DNF base object. -+ -+ Iterates over all available comps groups and filters to those recorded -+ as installed in the DNF history. -+ -+ :param base: Initialized dnf.Base object with filled sack -+ :type base: dnf.Base -+ :returns: List of DNFGroup model instances sorted by group id -+ :rtype: List[DNFGroup] -+ """ -+ groups = [] -+ for grp in base.comps.groups_iter(): -+ if not base.history.group.get(grp.id): -+ # not installed -+ continue -+ groups.append(DNFGroup( -+ id=grp.id, -+ name=grp.ui_name, -+ )) -+ return sorted(groups, key=lambda x: x.id) -+ -+ -+def _get_installed_environments(base): -+ """ -+ Query installed DNF comps environments via the DNF base object. -+ -+ Iterates over all available comps environments and filters to those -+ recorded as installed in the DNF history. -+ -+ :param base: Initialized dnf.Base object with filled sack -+ :type base: dnf.Base -+ :returns: List of DNFEnvironment model instances sorted by environment id -+ :rtype: List[DNFEnvironment] -+ """ -+ environments = [] -+ for env in base.comps.environments_iter(): -+ if not base.history.env.get(env.id): -+ continue -+ environments.append(DNFEnvironment( -+ id=env.id, -+ name=env.ui_name, -+ )) -+ return sorted(environments, key=lambda x: x.id) -+ -+ -+def process(): -+ """ -+ Scan installed DNF comps and produce InstalledDNFComps message. -+ -+ .. seealso:: -+ :func:`create_dnf_base` for exceptions raised when creating dnf.Base -+ """ -+ if not dnf: -+ api.current_logger().debug('DNF is not available, skipping DNF comps scan.') -+ return -+ -+ try: -+ base = create_dnf_base() -+ except DNFRepoError as e: -+ e.details['details'] = e.message -+ raise StopActorExecutionError( -+ message='Cannot obtain information about DNF Groups and Environments', -+ details=e.details -+ ) -+ -+ api.produce(InstalledDNFComps( -+ environments=_get_installed_environments(base), -+ groups=_get_installed_groups(base), -+ )) -diff --git a/repos/system_upgrade/common/actors/scandnfcomps/tests/test_scandnfcomps.py b/repos/system_upgrade/common/actors/scandnfcomps/tests/test_scandnfcomps.py -new file mode 100644 -index 00000000..9641c918 ---- /dev/null -+++ b/repos/system_upgrade/common/actors/scandnfcomps/tests/test_scandnfcomps.py -@@ -0,0 +1,175 @@ -+import pytest -+ -+from leapp.exceptions import StopActorExecutionError -+from leapp.libraries.actor import scandnfcomps -+from leapp.libraries.common.dnflibs import DNFRepoError -+from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked, produce_mocked -+from leapp.libraries.stdlib import api -+from leapp.models import DNFEnvironment, DNFGroup, InstalledDNFComps -+ -+ -+class MockCompsGroup(): -+ def __init__(self, gid, ui_name): -+ self.id = gid -+ self.ui_name = ui_name -+ -+ -+class MockCompsEnvironment(): -+ def __init__(self, env_id, ui_name): -+ self.id = env_id -+ self.ui_name = ui_name -+ -+ -+class MockComps(): -+ def __init__(self, groups=None, environments=None): -+ self._groups = groups or [] -+ self._environments = environments or [] -+ -+ def groups_iter(self): -+ return iter(self._groups) -+ -+ def environments_iter(self): -+ return iter(self._environments) -+ -+ -+class MockHistory(): -+ -+ class _Lookup(): -+ """ -+ Simulates base.history.group or base.history.env -+ -+ group & env have have same "API" that we use, so no need to create -+ a different class for each one. -+ """ -+ -+ def __init__(self, installed_ids): -+ self._installed = set(installed_ids) -+ -+ def get(self, item_id): -+ return item_id in self._installed -+ -+ def __init__(self, installed_group_ids=None, installed_env_ids=None): -+ self.group = MockHistory._Lookup(installed_group_ids or []) -+ self.env = MockHistory._Lookup(installed_env_ids or []) -+ -+ -+class MockDNFBase(): -+ def __init__(self, comps=None, history=None): -+ self.comps = comps or MockComps() -+ self.history = history or MockHistory() -+ -+ -+def test_process_with_groups_and_environments(monkeypatch): -+ comps = MockComps( -+ groups=[ -+ MockCompsGroup('core', 'Core'), -+ MockCompsGroup('base', 'Base'), -+ ], -+ environments=[ -+ MockCompsEnvironment('minimal-environment', 'Minimal Install'), -+ ], -+ ) -+ history = MockHistory( -+ installed_group_ids=['core', 'base'], -+ installed_env_ids=['minimal-environment'], -+ ) -+ base = MockDNFBase(comps=comps, history=history) -+ -+ mocked_producer = produce_mocked() -+ monkeypatch.setattr(scandnfcomps, 'create_dnf_base', lambda: base) -+ monkeypatch.setattr(scandnfcomps, 'dnf', True) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) -+ monkeypatch.setattr(api, 'produce', mocked_producer) -+ -+ scandnfcomps.process() -+ -+ assert mocked_producer.called == 1 -+ msg = mocked_producer.model_instances[0] -+ assert isinstance(msg, InstalledDNFComps) -+ assert len(msg.groups) == 2 -+ assert msg.groups[0].id == 'base' -+ assert msg.groups[0].name == 'Base' -+ assert msg.groups[1].id == 'core' -+ assert msg.groups[1].name == 'Core' -+ assert len(msg.environments) == 1 -+ assert msg.environments[0].id == 'minimal-environment' -+ assert msg.environments[0].name == 'Minimal Install' -+ -+ -+def test_process_filters_not_installed(monkeypatch): -+ """Only groups/environments recorded as installed in history are returned.""" -+ comps = MockComps( -+ groups=[ -+ MockCompsGroup('core', 'Core'), -+ MockCompsGroup('base', 'Base'), -+ MockCompsGroup('extra', 'Extra'), -+ ], -+ environments=[ -+ MockCompsEnvironment('minimal-environment', 'Minimal Install'), -+ MockCompsEnvironment('server-product-environment', 'Server'), -+ ], -+ ) -+ history = MockHistory( -+ installed_group_ids=['core'], -+ installed_env_ids=['server-product-environment'], -+ ) -+ base = MockDNFBase(comps=comps, history=history) -+ -+ mocked_producer = produce_mocked() -+ monkeypatch.setattr(scandnfcomps, 'create_dnf_base', lambda: base) -+ monkeypatch.setattr(scandnfcomps, 'dnf', True) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) -+ monkeypatch.setattr(api, 'produce', mocked_producer) -+ -+ scandnfcomps.process() -+ -+ assert mocked_producer.called == 1 -+ msg = mocked_producer.model_instances[0] -+ assert len(msg.groups) == 1 -+ assert msg.groups[0].id == 'core' -+ assert len(msg.environments) == 1 -+ assert msg.environments[0].id == 'server-product-environment' -+ -+ -+def test_process_empty_comps(monkeypatch): -+ base = MockDNFBase() -+ -+ mocked_producer = produce_mocked() -+ monkeypatch.setattr(scandnfcomps, 'create_dnf_base', lambda: base) -+ monkeypatch.setattr(scandnfcomps, 'dnf', True) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) -+ monkeypatch.setattr(api, 'produce', mocked_producer) -+ -+ scandnfcomps.process() -+ -+ assert mocked_producer.called == 1 -+ msg = mocked_producer.model_instances[0] -+ assert isinstance(msg, InstalledDNFComps) -+ assert msg.groups == [] -+ assert msg.environments == [] -+ -+ -+def test_process_no_dnf(monkeypatch): -+ mocked_producer = produce_mocked() -+ monkeypatch.setattr(scandnfcomps, 'dnf', None) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) -+ monkeypatch.setattr(api, 'produce', mocked_producer) -+ -+ scandnfcomps.process() -+ assert mocked_producer.called == 0 -+ -+ -+def test_process_dnf_repo_error(monkeypatch): -+ def raise_repo_error(): -+ raise DNFRepoError( -+ message='DNF failed to load repositories: repo error', -+ details={'hint': 'Ensure the myrepo repository definition is correct.'} -+ ) -+ -+ monkeypatch.setattr(scandnfcomps, 'create_dnf_base', raise_repo_error) -+ monkeypatch.setattr(scandnfcomps, 'dnf', True) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ with pytest.raises(StopActorExecutionError, match='Cannot obtain information about DNF Groups and Environments'): -+ scandnfcomps.process() -diff --git a/repos/system_upgrade/common/models/dnfcomps.py b/repos/system_upgrade/common/models/dnfcomps.py -new file mode 100644 -index 00000000..896f832d ---- /dev/null -+++ b/repos/system_upgrade/common/models/dnfcomps.py -@@ -0,0 +1,76 @@ -+from leapp.models import fields, Model -+from leapp.topics import SystemFactsTopic -+ -+ -+class DNFGroup(Model): -+ """ -+ Provide information about a single installed DNF comps group. -+ -+ This model is not expected to be produced or consumed by any actors as -+ a standalone message. It is always part of InstalledDNFComps message. -+ """ -+ -+ topic = SystemFactsTopic -+ -+ id = fields.String() -+ """ -+ The DNF comps group identifier (e.g. "core", "base"). -+ """ -+ -+ name = fields.String() -+ """ -+ Human-readable display name of the group. -+ """ -+ -+ # NOTE(pstodulk): possible extension if needed in future, but not implemented now: -+ # (( to list installed packages, split by various groups )) -+ # mandatory_packages = fields.List(fields.String, default=[]) -+ # default_packages = fields.List(fields.String, default=[]) -+ # conditional_packages = fields.List(fields.String, default=[]) -+ # optional_packages = fields.List(fields.String, default=[]) -+ -+ -+class DNFEnvironment(Model): -+ """ -+ Provide information about a single installed DNF comps environment. -+ -+ This model is not expected to be produced or consumed by any actors as -+ a standalone message. It is always part of InstalledDNFComps message. -+ """ -+ -+ topic = SystemFactsTopic -+ -+ id = fields.String() -+ """ -+ The DNF comps environment identifier (e.g. "minimal-environment"). -+ """ -+ -+ name = fields.String() -+ """ -+ Human-readable display name of the environment. -+ """ -+ -+ # NOTE(pstodulk): possible extension if needed in future, but not implemented now: -+ # mandatory_groups = fields.List(fields.String(), default=[]) -+ # optional_groups = fields.List(fields.String(), default=[]) -+ -+ -+class InstalledDNFComps(Model): -+ """ -+ Provide information about installed DNF comps on the source system. -+ -+ Contains lists of installed DNF package environments and groups as -+ tracked by the DNF history database. -+ """ -+ -+ topic = SystemFactsTopic -+ -+ environments = fields.List(fields.Model(DNFEnvironment), default=[]) -+ """ -+ List of installed DNF comps environments. -+ """ -+ -+ groups = fields.List(fields.Model(DNFGroup), default=[]) -+ """ -+ List of installed DNF comps groups. -+ """ --- -2.54.0 - diff --git a/0091-IPU-9-10-Fix-the-upgrade-of-Graphical-Server.patch b/0091-IPU-9-10-Fix-the-upgrade-of-Graphical-Server.patch deleted file mode 100644 index b9ee78b..0000000 --- a/0091-IPU-9-10-Fix-the-upgrade-of-Graphical-Server.patch +++ /dev/null @@ -1,165 +0,0 @@ -From 63ed8d0297197fab585f5c268638b20c07ea428e Mon Sep 17 00:00:00 2001 -From: Tomas Popela -Date: Mon, 9 Mar 2026 10:58:44 +0100 -Subject: [PATCH 091/105] IPU 9 -> 10: Fix the upgrade of Graphical Server - -Prior to RHEL 10 we didn't have a way to enforce some of the desktop -environment changes to be only Server specific (such as disabling -the power button handling or disabling automatic suspendand) hence -we had to enable everything for everyone. - -In RHEL 10 we've added a new subpackage of gnome-settings-daemon, the -gnome-settings-daemon-server-defaults which is only preinstalled on -"Server with GUI" product and on nothing else. But this only works for -clean installations as the comps groups are not synced when doing the -upgrades with Leapp. To fix that we've created this Leapp actor which -will detect if the graphical-server-environment comps group was -installed and only in the case it will preinstall the server-defaults -subpackage. - -Assisted-By: Claude Opus 4.6 -Co-Authored-by: Petr Stodulka - -Jira: RHEL-148697 ---- - .../actors/gnomesettingsdaemoncheck/actor.py | 22 ++++++++ - .../libraries/gnomesettingsdaemoncheck.py | 36 +++++++++++++ - .../tests/test_gnomesettingsdaemoncheck.py | 54 +++++++++++++++++++ - 3 files changed, 112 insertions(+) - create mode 100644 repos/system_upgrade/el9toel10/actors/gnomesettingsdaemoncheck/actor.py - create mode 100644 repos/system_upgrade/el9toel10/actors/gnomesettingsdaemoncheck/libraries/gnomesettingsdaemoncheck.py - create mode 100644 repos/system_upgrade/el9toel10/actors/gnomesettingsdaemoncheck/tests/test_gnomesettingsdaemoncheck.py - -diff --git a/repos/system_upgrade/el9toel10/actors/gnomesettingsdaemoncheck/actor.py b/repos/system_upgrade/el9toel10/actors/gnomesettingsdaemoncheck/actor.py -new file mode 100644 -index 00000000..3056c242 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/gnomesettingsdaemoncheck/actor.py -@@ -0,0 +1,22 @@ -+from leapp.actors import Actor -+from leapp.libraries.actor import gnomesettingsdaemoncheck -+from leapp.models import InstalledDNFComps, RpmTransactionTasks -+from leapp.tags import ChecksPhaseTag, IPUWorkflowTag -+ -+ -+class GnomeSettingsDaemonCheck(Actor): -+ """ -+ Install gnome-settings-daemon-server-defaults on graphical server upgrades. -+ -+ If the graphical-server-environment comps environment group was installed -+ on the source RHEL 9 system, schedules the installation of the -+ gnome-settings-daemon-server-defaults package during the upgrade to RHEL 10. -+ """ -+ -+ name = 'gnome_settings_daemon_check' -+ consumes = (InstalledDNFComps,) -+ produces = (RpmTransactionTasks,) -+ tags = (ChecksPhaseTag, IPUWorkflowTag) -+ -+ def process(self): -+ gnomesettingsdaemoncheck.process() -diff --git a/repos/system_upgrade/el9toel10/actors/gnomesettingsdaemoncheck/libraries/gnomesettingsdaemoncheck.py b/repos/system_upgrade/el9toel10/actors/gnomesettingsdaemoncheck/libraries/gnomesettingsdaemoncheck.py -new file mode 100644 -index 00000000..6d2ff111 ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/gnomesettingsdaemoncheck/libraries/gnomesettingsdaemoncheck.py -@@ -0,0 +1,36 @@ -+from leapp.libraries.stdlib import api -+from leapp.models import InstalledDNFComps, RpmTransactionTasks -+ -+GSD_SERVER_DEFAULTS_PKG = 'gnome-settings-daemon-server-defaults' -+GRAPHICAL_SERVER_ENV = 'graphical-server-environment' -+ -+ -+def _is_graphical_server_environment_installed(installed_comps): -+ """ -+ Return True if the `GRAPHICAL_SERVER_ENV` comps environment group -+ is installed on the source system, False otherwise. -+ """ -+ return any(env.id == GRAPHICAL_SERVER_ENV for env in installed_comps.environments) -+ -+ -+def process(): -+ installed_comps = next(api.consume(InstalledDNFComps), None) -+ if not installed_comps: -+ api.current_logger().warning( -+ 'Missing information about installed DNF comps: No InstalledDNFComps message.' -+ ) -+ return -+ -+ if not _is_graphical_server_environment_installed(installed_comps): -+ api.current_logger().debug( -+ 'The {} comps environment is not installed; skipping.' -+ .format(GRAPHICAL_SERVER_ENV) -+ ) -+ return -+ -+ api.current_logger().info( -+ 'The {} comps environment is installed.' -+ ' Scheduling installation of {} for the upgrade.' -+ .format(GRAPHICAL_SERVER_ENV, GSD_SERVER_DEFAULTS_PKG) -+ ) -+ api.produce(RpmTransactionTasks(to_install=[GSD_SERVER_DEFAULTS_PKG])) -diff --git a/repos/system_upgrade/el9toel10/actors/gnomesettingsdaemoncheck/tests/test_gnomesettingsdaemoncheck.py b/repos/system_upgrade/el9toel10/actors/gnomesettingsdaemoncheck/tests/test_gnomesettingsdaemoncheck.py -new file mode 100644 -index 00000000..53681afd ---- /dev/null -+++ b/repos/system_upgrade/el9toel10/actors/gnomesettingsdaemoncheck/tests/test_gnomesettingsdaemoncheck.py -@@ -0,0 +1,54 @@ -+import pytest -+ -+from leapp.libraries.actor import gnomesettingsdaemoncheck -+from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked -+from leapp.libraries.stdlib import api -+from leapp.models import DNFEnvironment, InstalledDNFComps, RpmTransactionTasks -+ -+ -+def _installed_comps(env_ids=None): -+ envs = [DNFEnvironment(id=eid, name=eid) for eid in (env_ids or [])] -+ return InstalledDNFComps(environments=envs) -+ -+ -+@pytest.mark.parametrize('env_ids,expected', [ -+ ([gnomesettingsdaemoncheck.GRAPHICAL_SERVER_ENV], True), -+ ([gnomesettingsdaemoncheck.GRAPHICAL_SERVER_ENV, 'minimal-environment'], True), -+ (['minimal-environment'], False), -+ ([], False), -+]) -+def test_is_graphical_server_environment_installed(env_ids, expected): -+ comps = _installed_comps(env_ids) -+ assert gnomesettingsdaemoncheck._is_graphical_server_environment_installed(comps) is expected -+ -+ -+def test_process_produces_install_task(monkeypatch): -+ comps = _installed_comps([gnomesettingsdaemoncheck.GRAPHICAL_SERVER_ENV]) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[comps])) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ gnomesettingsdaemoncheck.process() -+ -+ assert api.produce.called == 1 -+ task = api.produce.model_instances[0] -+ assert isinstance(task, RpmTransactionTasks) -+ assert gnomesettingsdaemoncheck.GSD_SERVER_DEFAULTS_PKG in task.to_install -+ -+ -+def test_process_no_install_when_env_not_present(monkeypatch): -+ comps = _installed_comps(['minimal-environment']) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[comps])) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ gnomesettingsdaemoncheck.process() -+ -+ assert api.produce.called == 0 -+ -+ -+def test_process_no_install_when_no_comps_message(monkeypatch): -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[])) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ gnomesettingsdaemoncheck.process() -+ -+ assert api.produce.called == 0 --- -2.54.0 - diff --git a/0092-Bump-leapp-framework.patch b/0092-Bump-leapp-framework.patch deleted file mode 100644 index 85ef5a6..0000000 --- a/0092-Bump-leapp-framework.patch +++ /dev/null @@ -1,26 +0,0 @@ -From d976a0824d09a02c639a5648427150b77d01d394 Mon Sep 17 00:00:00 2001 -From: Matej Matuska -Date: Thu, 11 Jun 2026 09:53:17 +0200 -Subject: [PATCH 092/105] Bump leapp-framework - -I forgot to bump it here and also in the framework with b70ff3. ---- - packaging/leapp-repository.spec | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/packaging/leapp-repository.spec b/packaging/leapp-repository.spec -index 70b1d7d3..627fd11a 100644 ---- a/packaging/leapp-repository.spec -+++ b/packaging/leapp-repository.spec -@@ -120,7 +120,7 @@ Requires: leapp-repository-dependencies = %{leapp_repo_deps} - - # IMPORTANT: this is capability provided by the leapp framework rpm. - # Check that 'version' instead of the real framework rpm version. --Requires: leapp-framework >= 6.5, leapp-framework < 7 -+Requires: leapp-framework >= 6.6, leapp-framework < 7 - - # Since we provide sub-commands for the leapp utility, we expect the leapp - # tool to be installed as well. --- -2.54.0 - diff --git a/0093-raid-mdadm-include-configuration-in-initramfs.patch b/0093-raid-mdadm-include-configuration-in-initramfs.patch deleted file mode 100644 index ea5ee18..0000000 --- a/0093-raid-mdadm-include-configuration-in-initramfs.patch +++ /dev/null @@ -1,491 +0,0 @@ -From 28fd5ea3fccb9d62bb7de90916eaa7ee8943a50e Mon Sep 17 00:00:00 2001 -From: Michal Hecko -Date: Thu, 28 May 2026 15:30:14 +0200 -Subject: [PATCH 093/105] raid(mdadm): include configuration in initramfs - -Add code to handle detection of mdraid arrays based on the outputs of -`mdadm --detail --scan`. The code is structured to handle raids in -general (there is also dm-raid), i.e., it follows the usual pattern: -scan, check; boot entry addition is not being modified for now. New -model RAIDInfo is introduced, which provides basic information about -RAID technologies used. To activate all arrays during boot, it is -necessary to have rd.md.uuid=UUID on the kernel cmdline for every RAID -array. - -Jira-ref: RHEL-36249 ---- - .../common/actors/checkraid/actor.py | 22 ++++ - .../actors/checkraid/libraries/checkraid.py | 33 +++++ - .../actors/checkraid/tests/test_checkraid.py | 112 ++++++++++++++++ - .../upgradeinitramfsgenerator/actor.py | 2 + - .../libraries/upgradeinitramfsgenerator.py | 5 + - .../common/actors/scanraid/actor.py | 21 +++ - .../actors/scanraid/libraries/scanraid.py | 40 ++++++ - .../actors/scanraid/tests/test_scanraid.py | 122 ++++++++++++++++++ - .../system_upgrade/common/models/raidinfo.py | 19 +++ - 9 files changed, 376 insertions(+) - create mode 100644 repos/system_upgrade/common/actors/checkraid/actor.py - create mode 100644 repos/system_upgrade/common/actors/checkraid/libraries/checkraid.py - create mode 100644 repos/system_upgrade/common/actors/checkraid/tests/test_checkraid.py - create mode 100644 repos/system_upgrade/common/actors/scanraid/actor.py - create mode 100644 repos/system_upgrade/common/actors/scanraid/libraries/scanraid.py - create mode 100644 repos/system_upgrade/common/actors/scanraid/tests/test_scanraid.py - create mode 100644 repos/system_upgrade/common/models/raidinfo.py - -diff --git a/repos/system_upgrade/common/actors/checkraid/actor.py b/repos/system_upgrade/common/actors/checkraid/actor.py -new file mode 100644 -index 00000000..7af1124f ---- /dev/null -+++ b/repos/system_upgrade/common/actors/checkraid/actor.py -@@ -0,0 +1,22 @@ -+from leapp.actors import Actor -+from leapp.libraries.actor import checkraid -+from leapp.models import RAIDInfo, TargetUserSpaceUpgradeTasks -+from leapp.tags import ChecksPhaseTag, IPUWorkflowTag -+ -+ -+class CheckRaid(Actor): -+ """ -+ Ensure RAID configuration files are available in the target userspace. -+ -+ If mdadm software RAID is in use, copies present mdadm configuration -+ files and directories into the target userspace container so that -+ dracut can include them in the upgrade initramfs. -+ """ -+ -+ name = 'check_raid' -+ consumes = (RAIDInfo,) -+ produces = (TargetUserSpaceUpgradeTasks,) -+ tags = (ChecksPhaseTag, IPUWorkflowTag) -+ -+ def process(self): -+ checkraid.process() -diff --git a/repos/system_upgrade/common/actors/checkraid/libraries/checkraid.py b/repos/system_upgrade/common/actors/checkraid/libraries/checkraid.py -new file mode 100644 -index 00000000..dbd02f54 ---- /dev/null -+++ b/repos/system_upgrade/common/actors/checkraid/libraries/checkraid.py -@@ -0,0 +1,33 @@ -+import os -+ -+from leapp.libraries.stdlib import api -+from leapp.models import CopyFile, RAIDInfo, TargetUserSpaceUpgradeTasks -+ -+# Host paths for mdadm configuration (see mdadm.conf(5) FILES section). -+MDADM_CONFIG_PATHS = ( -+ '/etc/mdadm.conf', -+ '/etc/mdadm.conf.d', -+ '/etc/mdadm/mdadm.conf', -+ '/etc/mdadm/mdadm.conf.d', -+) -+ -+ -+def _mdadm_config_paths_present(): -+ for path in MDADM_CONFIG_PATHS: -+ if os.path.isfile(path) or os.path.isdir(path): -+ yield path -+ -+ -+def process(): -+ raid_info = next(api.consume(RAIDInfo), None) -+ if not raid_info or not raid_info.md_arrays: -+ return -+ -+ copy_files = [CopyFile(src=path) for path in _mdadm_config_paths_present()] -+ if not copy_files: -+ api.current_logger().warning( -+ 'mdadm software RAID is in use but no mdadm configuration was found under: %s', -+ ', '.join(MDADM_CONFIG_PATHS), -+ ) -+ else: -+ api.produce(TargetUserSpaceUpgradeTasks(copy_files=copy_files)) -diff --git a/repos/system_upgrade/common/actors/checkraid/tests/test_checkraid.py b/repos/system_upgrade/common/actors/checkraid/tests/test_checkraid.py -new file mode 100644 -index 00000000..02a6122a ---- /dev/null -+++ b/repos/system_upgrade/common/actors/checkraid/tests/test_checkraid.py -@@ -0,0 +1,112 @@ -+import os -+ -+from leapp.libraries.actor import checkraid -+from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked -+from leapp.libraries.stdlib import api -+from leapp.models import MDArray, RAIDInfo, TargetUserSpaceUpgradeTasks -+ -+ -+def _mock_path_checks(monkeypatch, files=(), directories=()): -+ def isfile(path): -+ return path in files -+ -+ def isdir(path): -+ return path in directories -+ -+ monkeypatch.setattr(os.path, 'isfile', isfile) -+ monkeypatch.setattr(os.path, 'isdir', isdir) -+ -+ -+def _md_arrays(*uuids): -+ return [MDArray(uuid=uuid) for uuid in uuids] -+ -+ -+def test_md_arrays_with_conf_dir(monkeypatch): -+ _mock_path_checks( -+ monkeypatch, -+ files=('/etc/mdadm.conf',), -+ directories=('/etc/mdadm.conf.d',), -+ ) -+ -+ msgs = [RAIDInfo(md_arrays=_md_arrays('aaa:bbb'))] -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ checkraid.process() -+ -+ assert api.produce.called == 1 -+ copy_tasks = [m for m in api.produce.model_instances if isinstance(m, TargetUserSpaceUpgradeTasks)] -+ assert len(copy_tasks) == 1 -+ assert [task.src for task in copy_tasks[0].copy_files] == [ -+ '/etc/mdadm.conf', -+ '/etc/mdadm.conf.d', -+ ] -+ -+ -+def test_md_arrays_without_conf_dir(monkeypatch): -+ _mock_path_checks(monkeypatch, files=('/etc/mdadm.conf',)) -+ -+ msgs = [RAIDInfo(md_arrays=_md_arrays('aaa:bbb'))] -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ checkraid.process() -+ -+ copy_tasks = [m for m in api.produce.model_instances if isinstance(m, TargetUserSpaceUpgradeTasks)] -+ assert len(copy_tasks) == 1 -+ assert [task.src for task in copy_tasks[0].copy_files] == ['/etc/mdadm.conf'] -+ -+ -+def test_md_arrays_alternative_mdadm_conf(monkeypatch): -+ _mock_path_checks( -+ monkeypatch, -+ files=('/etc/mdadm/mdadm.conf',), -+ directories=('/etc/mdadm/mdadm.conf.d',), -+ ) -+ -+ msgs = [RAIDInfo(md_arrays=_md_arrays('aaa:bbb'))] -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ checkraid.process() -+ -+ copy_tasks = [m for m in api.produce.model_instances if isinstance(m, TargetUserSpaceUpgradeTasks)] -+ assert len(copy_tasks) == 1 -+ assert [task.src for task in copy_tasks[0].copy_files] == [ -+ '/etc/mdadm/mdadm.conf', -+ '/etc/mdadm/mdadm.conf.d', -+ ] -+ -+ -+def test_md_arrays_no_config_paths(monkeypatch): -+ _mock_path_checks(monkeypatch) -+ -+ msgs = [RAIDInfo(md_arrays=_md_arrays('aaa:bbb'))] -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ checkraid.process() -+ -+ copy_tasks = [m for m in api.produce.model_instances if isinstance(m, TargetUserSpaceUpgradeTasks)] -+ assert not copy_tasks -+ assert not api.produce.called -+ -+ -+def test_no_raid_info(monkeypatch): -+ msgs = [] -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ checkraid.process() -+ -+ assert not api.produce.called -+ -+ -+def test_no_md_arrays(monkeypatch): -+ msgs = [RAIDInfo(md_arrays=[])] -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ checkraid.process() -+ -+ assert not api.produce.called -diff --git a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/actor.py b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/actor.py -index c0c93036..32016ab0 100644 ---- a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/actor.py -+++ b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/actor.py -@@ -7,6 +7,7 @@ from leapp.models import ( - FIPSInfo, - LiveModeConfig, - LVMConfig, -+ RaidInfo, - TargetOSInstallationImage, - TargetUserSpaceInfo, - TargetUserSpaceUpgradeTasks, -@@ -33,6 +34,7 @@ class UpgradeInitramfsGenerator(Actor): - FIPSInfo, - LiveModeConfig, - LVMConfig, -+ RaidInfo, - RequiredUpgradeInitramPackages, # deprecated - TargetOSInstallationImage, - TargetUserSpaceInfo, -diff --git a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py -index 24f35847..f2b45ae7 100644 ---- a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py -+++ b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py -@@ -14,6 +14,7 @@ from leapp.models import ( - BootContent, - LiveModeConfig, - LVMConfig, -+ RaidInfo, - TargetOSInstallationImage, - TargetUserSpaceInfo, - TargetUserSpaceUpgradeTasks, -@@ -385,6 +386,10 @@ def generate_initram_disk(context): - if next(api.consume(LVMConfig), None): - env_variables.append('LEAPP_DRACUT_LVMCONF="1"') - -+ raid_info = next(api.consume(RaidInfo), None) -+ if raid_info and raid_info.md_arrays: -+ env_variables.append('LEAPP_DRACUT_MDADMCONF="1"') -+ - env_variables = ' '.join(env_variables) - env_variables = env_variables.format( - kernel_version=_get_target_kernel_version(context), -diff --git a/repos/system_upgrade/common/actors/scanraid/actor.py b/repos/system_upgrade/common/actors/scanraid/actor.py -new file mode 100644 -index 00000000..62234b93 ---- /dev/null -+++ b/repos/system_upgrade/common/actors/scanraid/actor.py -@@ -0,0 +1,21 @@ -+from leapp.actors import Actor -+from leapp.libraries.actor import scanraid -+from leapp.models import DistributionSignedRPM, RAIDInfo -+from leapp.tags import FactsPhaseTag, IPUWorkflowTag -+ -+ -+class ScanRaid(Actor): -+ """ -+ Detect whether software RAID is in use on the system. -+ -+ Checks if the mdadm package is installed and whether any MD arrays -+ are currently assembled and active by scanning mdadm configuration. -+ """ -+ -+ name = 'scan_raid' -+ consumes = (DistributionSignedRPM,) -+ produces = (RAIDInfo,) -+ tags = (FactsPhaseTag, IPUWorkflowTag) -+ -+ def process(self): -+ scanraid.process() -diff --git a/repos/system_upgrade/common/actors/scanraid/libraries/scanraid.py b/repos/system_upgrade/common/actors/scanraid/libraries/scanraid.py -new file mode 100644 -index 00000000..a810a238 ---- /dev/null -+++ b/repos/system_upgrade/common/actors/scanraid/libraries/scanraid.py -@@ -0,0 +1,40 @@ -+import re -+ -+from leapp.libraries.common.rpms import has_package -+from leapp.libraries.stdlib import api, CalledProcessError, run -+from leapp.models import DistributionSignedRPM, MDArray, RAIDInfo -+ -+MDADM_SCAN_CMD = ['mdadm', '--detail', '--scan', '--verbose'] -+UUID_PATTERN = re.compile(r'UUID=([0-9a-fA-F:]+)') -+ -+ -+def _scan_md_array_uuids(): -+ try: -+ result = run(MDADM_SCAN_CMD) -+ except CalledProcessError as err: -+ api.current_logger().warning('Failed to scan mdadm arrays: %s', err) -+ return [] -+ -+ uuids = [] -+ for line in result['stdout'].splitlines(): -+ if not line.startswith('ARRAY '): -+ continue -+ match = UUID_PATTERN.search(line) -+ if match: -+ uuids.append(match.group(1)) -+ -+ return uuids -+ -+ -+def process(): -+ if not has_package(DistributionSignedRPM, 'mdadm'): -+ api.current_logger().debug('The mdadm package is not installed. Skipping.') -+ return -+ -+ uuids = _scan_md_array_uuids() -+ if not uuids: -+ api.current_logger().debug('No active mdadm software RAID arrays found.') -+ return -+ -+ api.current_logger().info('Detected active mdadm software RAID arrays.') -+ api.produce(RAIDInfo(md_arrays=[MDArray(uuid=uuid) for uuid in uuids])) -diff --git a/repos/system_upgrade/common/actors/scanraid/tests/test_scanraid.py b/repos/system_upgrade/common/actors/scanraid/tests/test_scanraid.py -new file mode 100644 -index 00000000..255be095 ---- /dev/null -+++ b/repos/system_upgrade/common/actors/scanraid/tests/test_scanraid.py -@@ -0,0 +1,122 @@ -+import pytest -+ -+from leapp.libraries.actor import scanraid -+from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked, produce_mocked -+from leapp.libraries.stdlib import api, CalledProcessError -+from leapp.models import DistributionSignedRPM, MDArray, RAIDInfo, RPM -+ -+_MDADM_RPM = RPM( -+ name='mdadm', -+ version='4.2', -+ release='1.el9', -+ epoch='0', -+ packager='', -+ arch='x86_64', -+ pgpsig='RSA/SHA256, Mon 01 Jan 1970 00:00:00 AM -03, Key ID 199e2f91fd431d51' -+) -+ -+MDADM_SCAN_WITH_ARRAY = ( -+ 'ARRAY /dev/md0 level=raid1 num-devices=2 metadata=1.2 ' -+ 'name=localhost.localdomain:0 UUID=c4acea6e:d56e1598:91822e3f:fb26832c\n' -+) -+ -+MDADM_SCAN_WITH_TWO_ARRAYS = ( -+ 'ARRAY /dev/md0 metadata=1.2 UUID=c4acea6e:d56e1598:91822e3f:fb26832c\n' -+ 'ARRAY /dev/md1 metadata=1.2 UUID=5eb8cf98:a8d13c2e:3e91b4ca:2e1ac678\n' -+) -+ -+ -+class RunMocked: -+ -+ def __init__(self, stdout='', raise_err=False): -+ self.called = 0 -+ self.args = None -+ self.stdout = stdout -+ self.raise_err = raise_err -+ -+ def __call__(self, args, encoding=None): -+ self.called += 1 -+ self.args = args -+ if self.raise_err: -+ raise CalledProcessError( -+ message='A Leapp Command Error occurred.', -+ command=args, -+ result={'signal': None, 'exit_code': 1, 'pid': 0, 'stdout': 'fake', 'stderr': 'fake'} -+ ) -+ assert args == scanraid.MDADM_SCAN_CMD -+ return {'stdout': self.stdout} -+ -+ -+def test_mdadm_not_installed(monkeypatch): -+ msgs = [DistributionSignedRPM(items=[])] -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ scanraid.process() -+ -+ assert not api.produce.called -+ -+ -+def test_mdadm_installed_with_active_arrays(monkeypatch): -+ run_mocked = RunMocked(stdout=MDADM_SCAN_WITH_ARRAY) -+ monkeypatch.setattr(scanraid, 'run', run_mocked) -+ -+ msgs = [DistributionSignedRPM(items=[_MDADM_RPM])] -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ scanraid.process() -+ -+ assert run_mocked.called == 1 -+ assert api.produce.called == 1 -+ assert len(api.produce.model_instances) == 1 -+ produced = api.produce.model_instances[0] -+ assert isinstance(produced, RAIDInfo) -+ assert produced.md_arrays == [MDArray(uuid='c4acea6e:d56e1598:91822e3f:fb26832c')] -+ -+ -+def test_mdadm_installed_with_multiple_arrays(monkeypatch): -+ run_mocked = RunMocked(stdout=MDADM_SCAN_WITH_TWO_ARRAYS) -+ monkeypatch.setattr(scanraid, 'run', run_mocked) -+ -+ msgs = [DistributionSignedRPM(items=[_MDADM_RPM])] -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ scanraid.process() -+ -+ produced = api.produce.model_instances[0] -+ assert [md_array.uuid for md_array in produced.md_arrays] == [ -+ 'c4acea6e:d56e1598:91822e3f:fb26832c', -+ '5eb8cf98:a8d13c2e:3e91b4ca:2e1ac678', -+ ] -+ -+ -+def test_mdadm_installed_no_active_arrays(monkeypatch): -+ run_mocked = RunMocked(stdout='') -+ monkeypatch.setattr(scanraid, 'run', run_mocked) -+ -+ msgs = [DistributionSignedRPM(items=[_MDADM_RPM])] -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ scanraid.process() -+ -+ assert run_mocked.called == 1 -+ assert not api.produce.called -+ -+ -+def test_mdadm_installed_scan_failure(monkeypatch): -+ run_mocked = RunMocked(raise_err=True) -+ monkeypatch.setattr(scanraid, 'run', run_mocked) -+ monkeypatch.setattr(api, 'current_logger', logger_mocked()) -+ -+ msgs = [DistributionSignedRPM(items=[_MDADM_RPM])] -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ scanraid.process() -+ -+ assert run_mocked.called == 1 -+ assert not api.produce.called -+ assert api.current_logger.warnmsg -diff --git a/repos/system_upgrade/common/models/raidinfo.py b/repos/system_upgrade/common/models/raidinfo.py -new file mode 100644 -index 00000000..69188f1e ---- /dev/null -+++ b/repos/system_upgrade/common/models/raidinfo.py -@@ -0,0 +1,19 @@ -+from leapp.models import fields, Model -+from leapp.topics import SystemInfoTopic -+ -+ -+class MDArray(Model): -+ """Information about a single mdadm software RAID array.""" -+ -+ topic = SystemInfoTopic -+ -+ uuid = fields.String() -+ """UUID of the mdadm array.""" -+ -+ -+class RAIDInfo(Model): -+ """Information about RAID usage on the source system.""" -+ topic = SystemInfoTopic -+ -+ md_arrays = fields.List(fields.Model(MDArray), default=[]) -+ """List of active mdadm software RAID arrays.""" --- -2.54.0 - diff --git a/0094-feat-upgrade_initramfs_gen-write-rd.md.uuids-into-cm.patch b/0094-feat-upgrade_initramfs_gen-write-rd.md.uuids-into-cm.patch deleted file mode 100644 index 1235977..0000000 --- a/0094-feat-upgrade_initramfs_gen-write-rd.md.uuids-into-cm.patch +++ /dev/null @@ -1,235 +0,0 @@ -From 19384aabcb467ed49e337809db9a0fd305bc3136 Mon Sep 17 00:00:00 2001 -From: Michal Hecko -Date: Thu, 11 Jun 2026 12:18:22 +0200 -Subject: [PATCH 094/105] feat(upgrade_initramfs_gen): write rd.md.uuids into - cmdline.d - -To avoid inflating the kernel cmdline, the rd.md.uuid args required to -activate all md arrays during boot are written into -/etc/cmdline.d/50-leapp.conf inside the target userspace. Dracut then -loads these args during boot, avoiding boot loader limitations. - -Jira-ref: RHEL-36249 ---- - .../upgradeinitramfsgenerator/actor.py | 4 +- - .../libraries/upgradeinitramfsgenerator.py | 40 +++++++++- - .../unit_test_upgradeinitramfsgenerator.py | 74 +++++++++++++++++++ - 3 files changed, 113 insertions(+), 5 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/actor.py b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/actor.py -index 32016ab0..ee6df26f 100644 ---- a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/actor.py -+++ b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/actor.py -@@ -7,7 +7,7 @@ from leapp.models import ( - FIPSInfo, - LiveModeConfig, - LVMConfig, -- RaidInfo, -+ RAIDInfo, - TargetOSInstallationImage, - TargetUserSpaceInfo, - TargetUserSpaceUpgradeTasks, -@@ -34,7 +34,7 @@ class UpgradeInitramfsGenerator(Actor): - FIPSInfo, - LiveModeConfig, - LVMConfig, -- RaidInfo, -+ RAIDInfo, - RequiredUpgradeInitramPackages, # deprecated - TargetOSInstallationImage, - TargetUserSpaceInfo, -diff --git a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py -index f2b45ae7..f9a00eec 100644 ---- a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py -+++ b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py -@@ -14,7 +14,7 @@ from leapp.models import ( - BootContent, - LiveModeConfig, - LVMConfig, -- RaidInfo, -+ RAIDInfo, - TargetOSInstallationImage, - TargetUserSpaceInfo, - TargetUserSpaceUpgradeTasks, -@@ -25,6 +25,7 @@ from leapp.utils.deprecation import suppress_deprecation - - INITRAM_GEN_SCRIPT_NAME = 'generate-initram.sh' - DRACUT_DIR = '/dracut' -+LEAPP_CMDLINE_CONF_PATH = '/etc/cmdline.d/50-leapp.conf' - DEDICATED_LEAPP_PART_URL = 'https://access.redhat.com/solutions/7011704' - - -@@ -329,6 +330,32 @@ def _check_free_space(context): - ) - - -+def write_md_uuid_cmdline_conf(context): -+ """ -+ Write /etc/cmdline.d/50-leapp.conf into the target userspace when MD RAID is in use. -+ -+ :returns: Path of the written file, or None if no file was written. -+ :rtype: str or None -+ """ -+ raid_info = next(api.consume(RAIDInfo), None) -+ if not raid_info or not raid_info.md_arrays: -+ return None -+ -+ md_uuids = ['rd.md.uuid={}'.format(md_array.uuid) for md_array in raid_info.md_arrays] -+ content = '{}\n'.format(' '.join(md_uuids)) -+ -+ context.makedirs(os.path.dirname(LEAPP_CMDLINE_CONF_PATH), exists_ok=True) -+ with context.open(LEAPP_CMDLINE_CONF_PATH, mode='w') as cmdline_conf: -+ cmdline_conf.write(content) -+ -+ api.current_logger().debug( -+ 'Written {path} with content: {content}'.format( -+ path=LEAPP_CMDLINE_CONF_PATH, content=content.rstrip('\n') -+ ) -+ ) -+ return LEAPP_CMDLINE_CONF_PATH -+ -+ - InitramfsIncludes = namedtuple('InitramfsIncludes', ('files', 'dracut_modules', 'kernel_modules')) - - -@@ -375,6 +402,10 @@ def generate_initram_disk(context): - def fmt_module_list(module_list): - return ','.join(mod.name for mod in module_list) - -+ md_cmdline_conf = write_md_uuid_cmdline_conf(context) -+ if md_cmdline_conf: -+ initramfs_includes.files.append(md_cmdline_conf) -+ - env_variables = [ - 'LEAPP_KERNEL_VERSION={kernel_version}', - 'LEAPP_ADD_DRACUT_MODULES="{dracut_modules}"', -@@ -386,8 +417,7 @@ def generate_initram_disk(context): - if next(api.consume(LVMConfig), None): - env_variables.append('LEAPP_DRACUT_LVMCONF="1"') - -- raid_info = next(api.consume(RaidInfo), None) -- if raid_info and raid_info.md_arrays: -+ if md_cmdline_conf: - env_variables.append('LEAPP_DRACUT_MDADMCONF="1"') - - env_variables = ' '.join(env_variables) -@@ -453,6 +483,10 @@ def _generate_livemode_initramfs(context, userspace_initramfs_dest, target_kerne - copy_dracut_modules(context, initramfs_includes.dracut_modules) - copy_kernel_modules(context, initramfs_includes.kernel_modules) - -+ md_cmdline_conf = write_md_uuid_cmdline_conf(context) -+ if md_cmdline_conf: -+ initramfs_includes.files.append(md_cmdline_conf) -+ - dracut_modules = ['livenet', 'dmsquash-live'] + [mod.name for mod in initramfs_includes.dracut_modules] - - cmd = ['dracut', '--verbose', '--compress', 'xz', -diff --git a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/tests/unit_test_upgradeinitramfsgenerator.py b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/tests/unit_test_upgradeinitramfsgenerator.py -index 165a4df0..b1960578 100644 ---- a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/tests/unit_test_upgradeinitramfsgenerator.py -+++ b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/tests/unit_test_upgradeinitramfsgenerator.py -@@ -17,6 +17,8 @@ from leapp.models import ( # isort:skip - CopyFile, - DracutModule, - KernelModule, -+ MDArray, -+ RAIDInfo, - TargetUserSpaceUpgradeTasks, - UpgradeInitramfsTasks, - ) -@@ -89,6 +91,7 @@ class MockedContext: - self.called_copy_to = [] - self.called_call = [] - self.called_makedirs = [] -+ self.written_files = {} - self.content = set() - self.base_dir = "/base/dir" - """ -@@ -135,6 +138,25 @@ class MockedContext: - def full_path(self, path): - return os.path.join(self.base_dir, os.path.abspath(path).lstrip('/')) - -+ def open(self, path, *args, mode='r', **kwargs): # pylint: disable=no-self-use -+ if 'w' not in mode: -+ raise NotImplementedError('MockedContext.open only supports write mode') -+ -+ written_files = self.written_files -+ contents = [] -+ -+ class _Writer: -+ def write(self, data): # pylint: disable=no-self-use -+ contents.append(data) -+ -+ def __enter__(self): -+ return self -+ -+ def __exit__(self, *dummy): -+ written_files[path] = ''.join(contents) -+ -+ return _Writer() -+ - - class MockedLogger(logger_mocked): - -@@ -186,6 +208,58 @@ def _sort_files(copy_files): - return sorted(copy_files, key=lambda x: (x.src, x.dst)) - - -+@pytest.mark.parametrize('raid_info,expected_path,expected_content', [ -+ (None, None, None), -+ (RAIDInfo(md_arrays=[]), None, None), -+ ( -+ RAIDInfo(md_arrays=[MDArray(uuid='aaa:bbb')]), -+ upgradeinitramfsgenerator.LEAPP_CMDLINE_CONF_PATH, -+ 'rd.md.uuid=aaa:bbb\n', -+ ), -+ ( -+ RAIDInfo(md_arrays=[MDArray(uuid='aaa:bbb'), MDArray(uuid='ccc:ddd')]), -+ upgradeinitramfsgenerator.LEAPP_CMDLINE_CONF_PATH, -+ 'rd.md.uuid=aaa:bbb rd.md.uuid=ccc:ddd\n', -+ ), -+]) -+def test_write_md_uuid_cmdline_conf(monkeypatch, raid_info, expected_path, expected_content): -+ context = MockedContext() -+ msgs = [raid_info] if raid_info is not None else [] -+ monkeypatch.setattr(upgradeinitramfsgenerator.api, 'current_actor', CurrentActorMocked(msgs=msgs)) -+ result = upgradeinitramfsgenerator.write_md_uuid_cmdline_conf(context) -+ -+ assert result == expected_path -+ if expected_path: -+ assert '/etc/cmdline.d' in context.called_makedirs -+ assert context.written_files[expected_path] == expected_content -+ else: -+ assert not context.written_files -+ assert not context.called_makedirs -+ -+ -+def test_generate_initram_disk_includes_md_cmdline_conf(monkeypatch): -+ context = MockedContext() -+ raid_info = RAIDInfo(md_arrays=[MDArray(uuid='aaa:bbb')]) -+ input_msgs = gen_UDM_list(MODULES[0]) + [raid_info] -+ curr_actor = CurrentActorMocked(msgs=input_msgs, arch=architecture.ARCH_X86_64) -+ monkeypatch.setattr(upgradeinitramfsgenerator.api, 'current_actor', curr_actor) -+ monkeypatch.setattr(upgradeinitramfsgenerator, 'copy_dracut_modules', MockedCopyArgs()) -+ monkeypatch.setattr(upgradeinitramfsgenerator, '_get_target_kernel_version', lambda _: '1.0-1.x86_64') -+ monkeypatch.setattr(upgradeinitramfsgenerator, 'copy_kernel_modules', MockedCopyArgs()) -+ monkeypatch.setattr(upgradeinitramfsgenerator, 'copy_boot_files', lambda dummy: None) -+ monkeypatch.setattr(upgradeinitramfsgenerator, '_get_fspace', MockedGetFspace(2*2**30)) -+ -+ upgradeinitramfsgenerator.generate_initram_disk(context) -+ -+ assert context.written_files[upgradeinitramfsgenerator.LEAPP_CMDLINE_CONF_PATH] == 'rd.md.uuid=aaa:bbb\n' -+ assert len(context.called_call) == 1 -+ shell_cmd = context.called_call[0][0][0][2] -+ assert 'LEAPP_DRACUT_INSTALL_FILES="{path}"'.format( -+ path=upgradeinitramfsgenerator.LEAPP_CMDLINE_CONF_PATH -+ ) in shell_cmd -+ assert 'LEAPP_DRACUT_MDADMCONF="1"' in shell_cmd -+ -+ - def test_prepare_userspace_for_initram_no_script(monkeypatch): - monkeypatch.setattr(upgradeinitramfsgenerator.api, 'get_actor_file_path', lambda dummy: None) - with pytest.raises(StopActorExecutionError) as err: --- -2.54.0 - diff --git a/0095-Update-actions-checkout-action-to-v7.patch b/0095-Update-actions-checkout-action-to-v7.patch deleted file mode 100644 index 89ffe93..0000000 --- a/0095-Update-actions-checkout-action-to-v7.patch +++ /dev/null @@ -1,53 +0,0 @@ -From 19e6ac5f04af5d584c7ad5c2607aa35561a9eb67 Mon Sep 17 00:00:00 2001 -From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> -Date: Thu, 18 Jun 2026 19:01:33 +0000 -Subject: [PATCH 095/105] Update actions/checkout action to v7 - ---- - .github/workflows/codespell.yml | 2 +- - .github/workflows/differential-shellcheck.yml | 2 +- - .github/workflows/unit-tests.yml | 2 +- - 3 files changed, 3 insertions(+), 3 deletions(-) - -diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml -index 2ac322e5..03a62d51 100644 ---- a/.github/workflows/codespell.yml -+++ b/.github/workflows/codespell.yml -@@ -14,7 +14,7 @@ jobs: - runs-on: ubuntu-latest - - steps: -- - uses: actions/checkout@v6 -+ - uses: actions/checkout@v7 - - uses: codespell-project/actions-codespell@v2 - with: - ignore_words_list: ro,fo,couldn,repositor,zeor,bootup -diff --git a/.github/workflows/differential-shellcheck.yml b/.github/workflows/differential-shellcheck.yml -index 3b92d771..fc29bf30 100644 ---- a/.github/workflows/differential-shellcheck.yml -+++ b/.github/workflows/differential-shellcheck.yml -@@ -19,7 +19,7 @@ jobs: - - steps: - - name: Repository checkout -- uses: actions/checkout@v6 -+ uses: actions/checkout@v7 - with: - fetch-depth: 0 - -diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml -index f855baa2..bd829936 100644 ---- a/.github/workflows/unit-tests.yml -+++ b/.github/workflows/unit-tests.yml -@@ -52,7 +52,7 @@ jobs: - - steps: - - name: Checkout code -- uses: actions/checkout@v6 -+ uses: actions/checkout@v7 - with: - # NOTE(ivasilev) fetch-depth 0 is critical here as leapp deps discovery depends on specific substring in - # commit message and default 1 option will get us just merge commit which has an unrelevant message. --- -2.54.0 - diff --git a/0096-Update-distro-values-for-rhui-jobs-follow-up-on-9to1.patch b/0096-Update-distro-values-for-rhui-jobs-follow-up-on-9to1.patch deleted file mode 100644 index a17a6a2..0000000 --- a/0096-Update-distro-values-for-rhui-jobs-follow-up-on-9to1.patch +++ /dev/null @@ -1,115 +0,0 @@ -From 53baea1521ed27991c807f75f03675dbb849701e Mon Sep 17 00:00:00 2001 -From: Daniel Diblik -Date: Wed, 10 Jun 2026 12:23:17 +0200 -Subject: [PATCH 096/105] Update distro values for rhui jobs, follow up on - 9to10 - -Signed-off-by: Daniel Diblik ---- - .packit.yaml | 62 ++++++++++++++++++++++++++++++++-------------------- - 1 file changed, 38 insertions(+), 24 deletions(-) - -diff --git a/.packit.yaml b/.packit.yaml -index 49e251d8..63fe17d3 100644 ---- a/.packit.yaml -+++ b/.packit.yaml -@@ -158,7 +158,7 @@ jobs: - - aws - targets: - epel-8-x86_64: -- distros: [RHEL-8.10-rhui] -+ distros: [RHEL-8-rhui] - identifier: sanity-abstract-8to9-aws - - - &beaker-minimal-8to9-abstract-ondemand -@@ -235,28 +235,6 @@ jobs: - env: - <<: *env-810to96 - --- &sanity-810to96-aws -- <<: *sanity-abstract-8to9-aws -- trigger: pull_request -- targets: -- epel-8-x86_64: -- distros: [RHEL-8.10-rhui] -- identifier: sanity-8.10to9.6-aws -- tf_extra_params: -- test: -- tmt: -- plan_filter: 'tag:8to9 & tag:rhui-aws-tier0 & enabled:true & tag:-rhsm' -- environments: -- - tmt: -- context: *tmt-context-810to96 -- settings: -- provisioning: -- tags: -- BusinessUnit: sst_upgrades@leapp_upstream_test -- env: -- <<: *env-810to96 -- -- - # ###################################################################### # - # ############################# 8.10 > 9.8 ############################# # - # ###################################################################### # -@@ -311,6 +289,24 @@ jobs: - env: - <<: *env-810to98 - -+- &sanity-810to98-aws -+ <<: *sanity-abstract-8to9-aws -+ trigger: pull_request -+ identifier: sanity-8.10to9.8-aws -+ tf_extra_params: -+ test: -+ tmt: -+ plan_filter: 'tag:8to9 & tag:rhui-aws-tier0 & enabled:true & tag:-rhsm' -+ environments: -+ - tmt: -+ context: *tmt-context-810to98 -+ settings: -+ provisioning: -+ tags: -+ BusinessUnit: sst_upgrades@leapp_upstream_test -+ env: -+ <<: *env-810to98 -+ - # ###################################################################### # - # ############################# 8.10 > 9.9 ############################# # - # ###################################################################### # -@@ -452,7 +448,7 @@ jobs: - - aws - targets: - epel-9-x86_64: -- distros: [RHEL-9.6-rhui] -+ distros: [RHEL-9-rhui] - identifier: sanity-abstract-9to10-aws - - - &beaker-minimal-9to10-abstract-ondemand -@@ -598,6 +594,24 @@ jobs: - env: - <<: *env-98to102 - -+- &sanity-98to102-aws -+ <<: *sanity-abstract-9to10-aws -+ trigger: pull_request -+ identifier: sanity-9.8to10.2-aws -+ tf_extra_params: -+ test: -+ tmt: -+ plan_filter: 'tag:9to10 & tag:rhui-aws-tier0 & enabled:true & tag:-rhsm' -+ environments: -+ - tmt: -+ context: *tmt-context-98to102 -+ settings: -+ provisioning: -+ tags: -+ BusinessUnit: sst_upgrades@leapp_upstream_test -+ env: -+ <<: *env-98to102 -+ - # ###################################################################### # - # ############################# 9.9 > 10.3 ############################# # - # ###################################################################### # --- -2.54.0 - diff --git a/0097-1-9-Merge-and-refactor-of-repo-related-actors.patch b/0097-1-9-Merge-and-refactor-of-repo-related-actors.patch deleted file mode 100644 index 69b275d..0000000 --- a/0097-1-9-Merge-and-refactor-of-repo-related-actors.patch +++ /dev/null @@ -1,2493 +0,0 @@ -From 6661f06a594d5e9a136398c183eb7c6b496158d3 Mon Sep 17 00:00:00 2001 -From: karolinku -Date: Tue, 19 May 2026 21:16:59 +0200 -Subject: [PATCH 097/105] [1/9] Merge and refactor of repo-related actors - -Consolidate repositoriesmapping, peseventsscanner, -repositoriesblacklist, and setuptargetrepos into a single actor. -New actor executes four stages (previously handled by separeate -actors) sequentially: - - Load repository mapping (repomap.json) and produce RepositoriesMapping - - Compute blacklisted target repos (e.g. unsupported CRB) - - Process PES events to determine RPM transaction tasks - - Build final list of target repositories - -Data that previously flow through the messages between these -actors is now passed directly as function arguments, simplifying the -workflow and eliminating the circular dependency workaround between -repositoriesblacklist and the rest. - -This is initial commit starting the work. The whole change is split -into 9 commits to improve the readability of the changes. - -Additional initial changes: -* Deprecate RepositoriesBlacklisted model in favour - of RepositoriesBlocklisted. - -* Introduce the `to_block` field in the `RepositoriesSetupTasks` model - to be able to block specific repository IDs. - -* Rename repomap related libraries to distinguish better their - purpose. Original names `repomap` and `repositoriesmapping` next - to each other are confusing. Also, the `repomap` library name was - conflicting often with the code (e.g. variable name `repomap` used - in fuctions when the `repomap` library was imported as well. - It was not clear without checking of the scope what exactly - is executed. I am thinking also about their merge in future, but - let's keep them separated at least for now. - -* Rename the `repositoriesblacklist` library - to `repositoriesblocklist`. It's possible that later or earlier this - change would happen so let's rename it now, so all future changes - are applied under the new filename. - -* Also synchronize quoting in the actor to use single-quotes everywhere. -* All imports and calls in libraries and tests have been adjusted. - -Unit tests updated with assistance of AI. - -Co-Authored-by: Petr Stodulka -Assisted-by: Claude Code (Opus 4.6) -Jira: RHEL-115867 ---- - .../common/actors/peseventsscanner/actor.py | 41 -- - .../tests/files/sample02.json | 1 - - .../tests/files/sample03.json | 0 - .../actors/repositoriesblacklist/actor.py | 31 -- - .../tests/test_repositoriesblacklist.py | 221 ----------- - .../actors/repositoriesmapping/actor.py | 21 - - .../tests/test_scancustommodifications.py | 9 +- - .../common/actors/setuptargetrepos/actor.py | 45 --- - .../libraries/setuptargetrepos_repomap.py | 367 ------------------ - .../actors/targetcontentresolver/actor.py | 65 ++++ - .../libraries/pes_event_parsing.py | 0 - .../libraries/pes_events_scanner.py | 73 ++-- - .../libraries/repomap_calc.py} | 0 - .../libraries/repomap_loader.py} | 7 +- - .../libraries/repositoriesblocklist.py} | 95 +++-- - .../libraries/setuptargetrepos.py | 85 ++-- - .../libraries/targetcontentresolver.py | 39 ++ - .../tests/files/repomap_example.json | 0 - .../tests/files/sample01.json | 0 - .../tests/files/sample04.json | 0 - .../tests/test_event_parsing.py | 0 - .../tests/test_pes_event_scanner.py | 27 +- - .../tests/test_repomap_operations.py} | 6 +- - .../tests/test_repositoriesblocklist.py | 329 ++++++++++++++++ - .../tests/test_setuptargetrepos.py | 83 +++- - .../tests/test_targetcontentresolver.py | 63 +++ - .../tests/unit_test_repomap_loader.py} | 38 +- - .../common/models/repositoriesblacklisted.py | 11 - - .../common/models/repositoriesblocklisted.py | 34 ++ - .../common/models/repositoriessetuptasks.py | 15 +- - 30 files changed, 794 insertions(+), 912 deletions(-) - delete mode 100644 repos/system_upgrade/common/actors/peseventsscanner/actor.py - delete mode 100644 repos/system_upgrade/common/actors/peseventsscanner/tests/files/sample02.json - delete mode 100644 repos/system_upgrade/common/actors/peseventsscanner/tests/files/sample03.json - delete mode 100644 repos/system_upgrade/common/actors/repositoriesblacklist/actor.py - delete mode 100644 repos/system_upgrade/common/actors/repositoriesblacklist/tests/test_repositoriesblacklist.py - delete mode 100644 repos/system_upgrade/common/actors/repositoriesmapping/actor.py - delete mode 100644 repos/system_upgrade/common/actors/setuptargetrepos/actor.py - delete mode 100644 repos/system_upgrade/common/actors/setuptargetrepos/libraries/setuptargetrepos_repomap.py - create mode 100644 repos/system_upgrade/common/actors/targetcontentresolver/actor.py - rename repos/system_upgrade/common/actors/{peseventsscanner => targetcontentresolver}/libraries/pes_event_parsing.py (100%) - rename repos/system_upgrade/common/actors/{peseventsscanner => targetcontentresolver}/libraries/pes_events_scanner.py (93%) - rename repos/system_upgrade/common/actors/{peseventsscanner/libraries/peseventsscanner_repomap.py => targetcontentresolver/libraries/repomap_calc.py} (100%) - rename repos/system_upgrade/common/actors/{repositoriesmapping/libraries/repositoriesmapping.py => targetcontentresolver/libraries/repomap_loader.py} (98%) - rename repos/system_upgrade/common/actors/{repositoriesblacklist/libraries/repositoriesblacklist.py => targetcontentresolver/libraries/repositoriesblocklist.py} (63%) - rename repos/system_upgrade/common/actors/{setuptargetrepos => targetcontentresolver}/libraries/setuptargetrepos.py (71%) - create mode 100644 repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py - rename repos/system_upgrade/common/actors/{repositoriesmapping => targetcontentresolver}/tests/files/repomap_example.json (100%) - rename repos/system_upgrade/common/actors/{peseventsscanner => targetcontentresolver}/tests/files/sample01.json (100%) - rename repos/system_upgrade/common/actors/{peseventsscanner => targetcontentresolver}/tests/files/sample04.json (100%) - rename repos/system_upgrade/common/actors/{peseventsscanner => targetcontentresolver}/tests/test_event_parsing.py (100%) - rename repos/system_upgrade/common/actors/{peseventsscanner => targetcontentresolver}/tests/test_pes_event_scanner.py (96%) - rename repos/system_upgrade/common/actors/{setuptargetrepos/tests/test_repomapping.py => targetcontentresolver/tests/test_repomap_operations.py} (99%) - create mode 100644 repos/system_upgrade/common/actors/targetcontentresolver/tests/test_repositoriesblocklist.py - rename repos/system_upgrade/common/actors/{setuptargetrepos => targetcontentresolver}/tests/test_setuptargetrepos.py (74%) - create mode 100644 repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py - rename repos/system_upgrade/common/actors/{repositoriesmapping/tests/unit_test_repositoriesmapping.py => targetcontentresolver/tests/unit_test_repomap_loader.py} (88%) - delete mode 100644 repos/system_upgrade/common/models/repositoriesblacklisted.py - create mode 100644 repos/system_upgrade/common/models/repositoriesblocklisted.py - -diff --git a/repos/system_upgrade/common/actors/peseventsscanner/actor.py b/repos/system_upgrade/common/actors/peseventsscanner/actor.py -deleted file mode 100644 -index f801f1a1..00000000 ---- a/repos/system_upgrade/common/actors/peseventsscanner/actor.py -+++ /dev/null -@@ -1,41 +0,0 @@ --from leapp.actors import Actor --from leapp.libraries.actor.pes_events_scanner import process --from leapp.models import ( -- ConsumedDataAsset, -- DistributionSignedRPM, -- EnabledModules, -- PESRpmTransactionTasks, -- RepositoriesBlacklisted, -- RepositoriesFacts, -- RepositoriesMapping, -- RepositoriesSetupTasks, -- RHUIInfo, -- RpmTransactionTasks --) --from leapp.reporting import Report --from leapp.tags import FactsPhaseTag, IPUWorkflowTag -- -- --class PesEventsScanner(Actor): -- """ -- Provides data about package events from Package Evolution Service. -- -- After collecting data from a provided JSON file containing Package Evolution Service events, a -- message with relevant data will be produced to help DNF Upgrade transaction calculation. -- """ -- -- name = 'pes_events_scanner' -- consumes = ( -- EnabledModules, -- DistributionSignedRPM, -- RepositoriesBlacklisted, -- RepositoriesFacts, -- RepositoriesMapping, -- RHUIInfo, -- RpmTransactionTasks, -- ) -- produces = (ConsumedDataAsset, PESRpmTransactionTasks, RepositoriesSetupTasks, Report) -- tags = (IPUWorkflowTag, FactsPhaseTag) -- -- def process(self): -- process() -diff --git a/repos/system_upgrade/common/actors/peseventsscanner/tests/files/sample02.json b/repos/system_upgrade/common/actors/peseventsscanner/tests/files/sample02.json -deleted file mode 100644 -index 0967ef42..00000000 ---- a/repos/system_upgrade/common/actors/peseventsscanner/tests/files/sample02.json -+++ /dev/null -@@ -1 +0,0 @@ --{} -diff --git a/repos/system_upgrade/common/actors/peseventsscanner/tests/files/sample03.json b/repos/system_upgrade/common/actors/peseventsscanner/tests/files/sample03.json -deleted file mode 100644 -index e69de29b..00000000 -diff --git a/repos/system_upgrade/common/actors/repositoriesblacklist/actor.py b/repos/system_upgrade/common/actors/repositoriesblacklist/actor.py -deleted file mode 100644 -index b8f6135f..00000000 ---- a/repos/system_upgrade/common/actors/repositoriesblacklist/actor.py -+++ /dev/null -@@ -1,31 +0,0 @@ --from leapp.actors import Actor --from leapp.libraries.actor import repositoriesblacklist --from leapp.models import CustomTargetRepository, RepositoriesBlacklisted, RepositoriesFacts, RepositoriesMapping --from leapp.reporting import Report --from leapp.tags import FactsPhaseTag, IPUWorkflowTag -- -- --class RepositoriesBlacklist(Actor): -- """ -- Exclude target repositories provided by Red Hat without support. -- -- Conditions to exclude: -- - there are not such repositories already enabled on the source system -- (e.g. "Optional" repositories) -- - such repositories are not required for the upgrade explicitly by the user -- (e.g. via the --enablerepo option or via the /etc/leapp/files/leapp_upgrade_repositories.repo file) -- -- E.g. CRB repository is provided by Red Hat but it is without the support. -- """ -- -- name = "repositories_blacklist" -- consumes = ( -- CustomTargetRepository, -- RepositoriesFacts, -- RepositoriesMapping, -- ) -- produces = (RepositoriesBlacklisted, Report) -- tags = (IPUWorkflowTag, FactsPhaseTag) -- -- def process(self): -- repositoriesblacklist.process() -diff --git a/repos/system_upgrade/common/actors/repositoriesblacklist/tests/test_repositoriesblacklist.py b/repos/system_upgrade/common/actors/repositoriesblacklist/tests/test_repositoriesblacklist.py -deleted file mode 100644 -index 945007c6..00000000 ---- a/repos/system_upgrade/common/actors/repositoriesblacklist/tests/test_repositoriesblacklist.py -+++ /dev/null -@@ -1,221 +0,0 @@ --import pytest -- --from leapp import reporting --from leapp.libraries.actor import repositoriesblacklist --from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked, produce_mocked --from leapp.libraries.stdlib import api --from leapp.models import ( -- CustomTargetRepository, -- PESIDRepositoryEntry, -- RepoMapEntry, -- RepositoriesBlacklisted, -- RepositoriesFacts, -- RepositoriesMapping, -- RepositoryData, -- RepositoryFile --) -- -- --@pytest.fixture --def repofacts_opts_disabled(): -- repos_data = [ -- RepositoryData( -- repoid="codeready-builder-for-rhel-8-x86_64-rpms", -- name="RHEL 8 CRB", -- enabled=False, -- ) -- ] -- repos_files = [ -- RepositoryFile(file="/etc/yum.repos.d/redhat.repo", data=repos_data) -- ] -- return RepositoriesFacts(repositories=repos_files) -- -- --@pytest.fixture --def rhel8_crb_pesidrepo(): -- return PESIDRepositoryEntry( -- pesid='rhel8-CRB', -- major_version='8', -- repoid='codeready-builder-for-rhel-8-x86_64-rpms', -- rhui='', -- arch='x86_64', -- channel='ga', -- repo_type='rpm', -- distro='rhel', -- ) -- -- --@pytest.fixture --def rhel9_crb_pesidrepo(): -- return PESIDRepositoryEntry( -- pesid='rhel9-CRB', -- major_version='9', -- repoid='codeready-builder-for-rhel-9-x86_64-rpms', -- rhui='', -- arch='x86_64', -- channel='ga', -- repo_type='rpm', -- distro='rhel', -- ) -- -- --@pytest.fixture --def repomap_opts_only(rhel8_crb_pesidrepo, rhel9_crb_pesidrepo): -- return RepositoriesMapping( -- mapping=[RepoMapEntry(source='rhel8-CRB', target=['rhel9-CRB'])], -- repositories=[rhel8_crb_pesidrepo, rhel9_crb_pesidrepo] -- ) -- -- --def test_all_target_optionals_blacklisted_when_no_optional_on_source(monkeypatch, repomap_opts_only): -- """ -- Tests whether every target optional repository gets blacklisted -- if no optional repositories are used on the source system. -- """ -- -- repos_data = [ -- RepositoryData( -- repoid="rhel-8-server-rpms", -- name="RHEL 8 Server", -- enabled=True, -- ) -- ] -- repos_files = [ -- RepositoryFile(file="/etc/yum.repos.d/redhat.repo", data=repos_data) -- ] -- repo_facts = RepositoriesFacts(repositories=repos_files) -- -- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[repo_facts, repomap_opts_only])) -- monkeypatch.setattr(api, 'produce', produce_mocked()) -- monkeypatch.setattr(reporting, 'create_report', produce_mocked()) -- -- repositoriesblacklist.process() -- -- assert api.produce.called -- assert 'codeready-builder-for-rhel-9-x86_64-rpms' in api.produce.model_instances[0].repoids -- -- --def test_with_no_mapping_for_optional_repos(monkeypatch, repomap_opts_only, repofacts_opts_disabled): -- """ -- Tests whether nothing gets produced if no valid target is found for an optional repository in mapping data. -- """ -- -- repomap_opts_only.repositories[1].pesid = 'test_pesid' -- -- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[repofacts_opts_disabled, repomap_opts_only])) -- monkeypatch.setattr(api, 'produce', produce_mocked()) -- -- repositoriesblacklist.process() -- -- assert not api.produce.called -- -- --def test_blacklist_produced_when_optional_repo_disabled(monkeypatch, repofacts_opts_disabled, repomap_opts_only): -- """ -- Tests whether a correct blacklist is generated when there is disabled optional repo on the system. -- """ -- -- monkeypatch.setattr( -- api, -- "current_actor", -- CurrentActorMocked(msgs=[repofacts_opts_disabled, repomap_opts_only]), -- ) -- monkeypatch.setattr(api, "produce", produce_mocked()) -- monkeypatch.setattr(reporting, "create_report", produce_mocked()) -- -- repositoriesblacklist.process() -- -- assert api.produce.model_instances, 'A blacklist should get generated.' -- -- expected_blacklisted_repoid = 'codeready-builder-for-rhel-9-x86_64-rpms' -- err_msg = 'Blacklist does not contain expected repoid.' -- assert expected_blacklisted_repoid in api.produce.model_instances[0].repoids, err_msg -- -- --def test_no_blacklist_produced_when_optional_repo_enabled(monkeypatch, repofacts_opts_disabled, repomap_opts_only): -- """ -- Tests whether nothing is produced when an optional repository is enabled. -- -- Data are set up in such a fashion so that the determined blacklist would not be empty. -- """ -- -- repofacts_opts_disabled.repositories[0].data[0].enabled = True -- -- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[repofacts_opts_disabled, repomap_opts_only])) -- monkeypatch.setattr(api, "produce", produce_mocked()) -- monkeypatch.setattr(reporting, "create_report", produce_mocked()) -- -- repositoriesblacklist.process() -- -- assert not api.produce.called -- -- --def test_repositoriesblacklist_not_empty(monkeypatch, repofacts_opts_disabled, repomap_opts_only): -- """ -- Tests whether a message containing correct packages from the determined blacklist is produced. -- """ -- -- blacklisted_repoid = 'test' -- monkeypatch.setattr(repositoriesblacklist, "_get_repoids_to_exclude", lambda dummy_mapping: {blacklisted_repoid}) -- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[repofacts_opts_disabled, repomap_opts_only])) -- monkeypatch.setattr(api, "produce", produce_mocked()) -- monkeypatch.setattr(reporting, "create_report", produce_mocked()) -- -- repositoriesblacklist.process() -- assert api.produce.called == 1 -- assert isinstance(api.produce.model_instances[0], RepositoriesBlacklisted) -- assert api.produce.model_instances[0].repoids[0] == blacklisted_repoid -- assert reporting.create_report.called == 1 -- -- --def test_repositoriesblacklist_empty(monkeypatch, repofacts_opts_disabled, repomap_opts_only): -- """ -- Tests whether nothing is produced if there are some disabled optional -- repos, but an empty blacklist is determined from the repo mapping data. -- """ -- -- msgs_to_feed = [repofacts_opts_disabled, repomap_opts_only] -- -- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs_to_feed)) -- monkeypatch.setattr( -- repositoriesblacklist, -- "_get_repoids_to_exclude", -- lambda dummy_mapping: set() -- ) -- monkeypatch.setattr(api, "produce", produce_mocked()) -- -- repositoriesblacklist.process() -- assert api.produce.called == 0 -- -- --@pytest.mark.parametrize( -- ("enabled_repo", "exp_report_title", "message_produced"), -- [ -- ("codeready-builder-for-rhel-9-x86_64-rpms", "Using repository not supported by Red Hat", False), -- ("some_other_enabled_repo", "Excluded target system repositories", True), -- (None, "Excluded target system repositories", True), -- ], --) --def test_enablerepo_option(monkeypatch, -- repofacts_opts_disabled, -- repomap_opts_only, -- enabled_repo, -- exp_report_title, -- message_produced): -- """ -- Tests whether the actor respects CustomTargetRepository messages when constructing the blacklist. -- """ -- -- msgs_to_feed = [repomap_opts_only, repofacts_opts_disabled] -- -- if enabled_repo: -- msgs_to_feed.append(CustomTargetRepository(repoid=enabled_repo)) -- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs_to_feed)) -- monkeypatch.setattr(api, 'produce', produce_mocked()) -- monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) -- repositoriesblacklist.process() -- assert reporting.create_report.report_fields["title"] == exp_report_title -- if message_produced: -- assert isinstance(api.produce.model_instances[0], RepositoriesBlacklisted) -- else: -- assert not api.produce.model_instances -diff --git a/repos/system_upgrade/common/actors/repositoriesmapping/actor.py b/repos/system_upgrade/common/actors/repositoriesmapping/actor.py -deleted file mode 100644 -index 3ed4ff7b..00000000 ---- a/repos/system_upgrade/common/actors/repositoriesmapping/actor.py -+++ /dev/null -@@ -1,21 +0,0 @@ --from leapp.actors import Actor --from leapp.libraries.actor.repositoriesmapping import scan_repositories --from leapp.models import ConsumedDataAsset, RepositoriesMapping --from leapp.tags import FactsPhaseTag, IPUWorkflowTag -- -- --class RepositoriesMappingScanner(Actor): -- """ -- Produces message containing repository mapping based on provided file. -- -- The actor filters out data irrelevant to the current IPU (data with different -- source/target major versions) from the raw repository mapping data. -- """ -- -- name = 'repository_mapping' -- consumes = () -- produces = (ConsumedDataAsset, RepositoriesMapping,) -- tags = (IPUWorkflowTag, FactsPhaseTag) -- -- def process(self): -- scan_repositories() -diff --git a/repos/system_upgrade/common/actors/scancustommodifications/tests/test_scancustommodifications.py b/repos/system_upgrade/common/actors/scancustommodifications/tests/test_scancustommodifications.py -index 33d0660f..8dbf9b00 100644 ---- a/repos/system_upgrade/common/actors/scancustommodifications/tests/test_scancustommodifications.py -+++ b/repos/system_upgrade/common/actors/scancustommodifications/tests/test_scancustommodifications.py -@@ -44,13 +44,12 @@ S.5....T. c etc/leapp/files/pes-events.json - ('repos/system_upgrade/common/libraries/dnfplugin.py', ''), - ('repos/system_upgrade/common/libraries/testutils.py', ''), - # the rest of false positives discovered by dkubek -- ('repos/system_upgrade/common/actors/setuptargetrepos/libraries/setuptargetrepos_repomap.py', 'setuptargetrepos'), -+ ('repos/system_upgrade/common/actors/targetcontentresolver/libraries/repomap_calc.py', -+ 'target_content_resolver'), - ('repos/system_upgrade/el8toel9/actors/sssdfacts/libraries/sssdfacts8to9.py', 'sssd_facts_8to9'), - ('repos/system_upgrade/el8toel9/actors/nisscanner/libraries/nisscan.py', 'nis_scanner'), -- ('repos/system_upgrade/common/actors/setuptargetrepos/libraries/setuptargetrepos_repomap.py', 'setuptargetrepos'), -- ('repos/system_upgrade/common/actors/repositoriesmapping/libraries/repositoriesmapping.py', 'repository_mapping'), -- ('repos/system_upgrade/common/actors/peseventsscanner/libraries/peseventsscanner_repomap.py', -- 'pes_events_scanner') -+ ('repos/system_upgrade/common/actors/targetcontentresolver/libraries/repomap_loader.py', -+ 'target_content_resolver') - ]) - def test_deduce_actor_name_from_file(a_file, name): - assert scancustommodifications.deduce_actor_name(a_file) == name -diff --git a/repos/system_upgrade/common/actors/setuptargetrepos/actor.py b/repos/system_upgrade/common/actors/setuptargetrepos/actor.py -deleted file mode 100644 -index 91855818..00000000 ---- a/repos/system_upgrade/common/actors/setuptargetrepos/actor.py -+++ /dev/null -@@ -1,45 +0,0 @@ --from leapp.actors import Actor --from leapp.libraries.actor import setuptargetrepos --from leapp.models import ( -- CustomTargetRepository, -- InstalledRPM, -- RepositoriesBlacklisted, -- RepositoriesFacts, -- RepositoriesMapping, -- RepositoriesSetupTasks, -- RHUIInfo, -- SkippedRepositories, -- TargetRepositories, -- UsedRepositories --) --from leapp.tags import FactsPhaseTag, IPUWorkflowTag -- -- --class SetupTargetRepos(Actor): -- """ -- Produces list of repositories that should be available to be used during IPU process. -- -- The list of expected target repositories is produced based on: -- * required custom target repositories, -- * discovered enabled repositories, -- * repositories from which originates the installed content, -- * and the system distribution. -- -- The list of repositories can be additionally affected in case of RHEL by -- required channel (e.g. eus) or by detected use of RHUI. -- """ -- -- name = 'setuptargetrepos' -- consumes = (CustomTargetRepository, -- InstalledRPM, -- RepositoriesSetupTasks, -- RepositoriesMapping, -- RepositoriesFacts, -- RepositoriesBlacklisted, -- RHUIInfo, -- UsedRepositories) -- produces = (TargetRepositories, SkippedRepositories) -- tags = (IPUWorkflowTag, FactsPhaseTag) -- -- def process(self): -- setuptargetrepos.process() -diff --git a/repos/system_upgrade/common/actors/setuptargetrepos/libraries/setuptargetrepos_repomap.py b/repos/system_upgrade/common/actors/setuptargetrepos/libraries/setuptargetrepos_repomap.py -deleted file mode 100644 -index 763eddc6..00000000 ---- a/repos/system_upgrade/common/actors/setuptargetrepos/libraries/setuptargetrepos_repomap.py -+++ /dev/null -@@ -1,367 +0,0 @@ --from leapp.libraries.common.config import get_source_distro_id, get_target_distro_id, get_target_product_channel --from leapp.libraries.common.config.version import get_source_major_version, get_target_major_version --from leapp.libraries.stdlib import api -- --DEFAULT_PESID = { -- '8': 'rhel8-BaseOS', -- '9': 'rhel9-BaseOS', -- '10': 'rhel10-BaseOS' --} -- -- --def _get_channel_prio(pesid_repo): -- priorities = { -- 'beta': 0, -- 'ga': 1, -- } -- return priorities.get(pesid_repo.channel, 10) -- -- --class RepoMapDataHandler: -- """ -- Provide the basic functionality to work with the repository data easily. -- """ -- -- def __init__( -- self, -- repo_map, -- source_distro="", -- target_distro="", -- cloud_provider="", -- default_channels=None, -- ): -- """ -- Initialize the object based on the given RepositoriesMapping msg. -- -- Expects that msg contains just stuff related for the current IPU -- (at least mapping and repos for the used upgrade path and architecture). -- -- :param repo_map: A valid RepositoryMapping message. -- :type repo_map: RepositoryMapping -- :param source_distro: The distribution to map repos from, default to current -- :type source_distro: str -- :param target_distro: The distribution to map repos to, default to current target distro -- :type target_distro: str -- :param default_channels: A list of default channels to use when a target repository -- equivalent exactly matching a source repository was not found. -- :type default_channels: List[str] -- :param prio_channel: Prefer repositories with this channel when looking for target equivalents. -- :type prio_channel: str -- """ -- # NOTE: currently I am keeping this default data structure that is not -- # ideal for work, but there is not any significant impact.. -- self.repositories = repo_map.repositories -- self.mapping = repo_map.mapping -- -- self.source_distro = source_distro or get_source_distro_id() -- self.target_distro = target_distro or get_target_distro_id() -- # FIXME(pstodulk): what about default_channel -> fallback_channel -- # hardcoded always as ga? instead of list of channels.. -- # it'd be possibly confusing naming now... -- self.default_channels = default_channels or ['ga'] -- -- # Make self.prio_channel None if the user did not specify any target channels, so that self.default_channels -- # will be used instead -- self.prio_channel = get_target_product_channel(default=None) -- -- self.cloud_provider = cloud_provider -- -- # Cloud provider might have multiple variants, e.g, aws: (aws, aws-sap-es4) - normalize it -- cloud_providers = ('aws', 'azure', 'google', 'alibaba') -- for provider in cloud_providers: -- if cloud_provider.startswith(provider): -- self.cloud_provider = provider -- break -- -- def set_default_channels(self, default_channels): -- """ -- Set the default channels that are used as a fallback when searching -- for the right target repository. -- -- Usually it's not problem to find a target repository that matches -- the source repository, however in some cases the target repository -- doesn't have to be available in the required (premium) channel but -- could be present in the standard one. -- -- This is used usually for fallbacks and it's prerequisite for the time -- the required channel will not be present for the particular repository. -- E.g. can happen for layered products which has different lifecycles -- and doesn't have to provide a special premium channels at all. E.g. -- the Extras repository has GA and Beta channels, but no EUS. In case the -- EUS is required, returns the GA one instead when this is present. -- -- It's recommended to make the GA ('ga') always present in the default -- list. -- -- :param default_channels: Default channels to use when a target equivalent to a source repository that -- matches its target channel properties exactly could not be found. -- :type default_channels: List[str] -- """ -- self.default_channels = default_channels -- -- def get_pesid_repo_entry(self, repoid, major_version, distro): -- """ -- Retrieve the PESIDRepositoryEntry that matches the given repoid, distro and OS major version -- -- If multiple pesid repo entries with the same repoid were found, the entry with rhui matching the source -- system's rhui info will be returned. If no entry with matching rhui exists, the CDN one is returned if any. -- -- :param repoid: RepoID that the PESIDRepositoryEntry should match. -- :type repoid: str -- :param major_version: Major version that the PESIDRepositoryEntry should match. -- :type major_version: str -- :param distro: Distro that the PESIDRepositoryEntry should match. -- :type distro: str -- :return: The PESIDRepositoryEntry matching the given repoid and major_version or None if no such -- entry could be found. -- :rtype: Optional[PESIDRepositoryEntry] -- """ -- matching_pesid_repos = [] -- for pesid_repo in self.repositories: -- # FIXME(pstodulk): Why we do not check actually architecture here? -- # It seems obvious we should check it, but it's not clear why we -- # don't and investigation might be required. -- # For the investigation: -- # # check repoids matching various architectures -- # # check repoids without $arch in substring on how many architectures they are present -- # Investigate and: add a comment with an explanation or fix the -- # condition. -- if ( -- pesid_repo.repoid == repoid -- and pesid_repo.major_version == major_version -- and pesid_repo.distro == distro -- ): -- matching_pesid_repos.append(pesid_repo) -- -- # FIXME: when a PESID is present for multiple architectures, there -- # are multiple matching repos even though there should really be just -- # one, the condition below fails even though it shouldn't -- if len(matching_pesid_repos) == 1: -- # Perform no heuristics if only a single pesid repository with matching repoid found -- return matching_pesid_repos[0] -- -- # Multiple (different) repositories with the same repoid found (can happen in clouds) - prefer the cloud one -- cdn_pesid_repo = None -- for pesid_repo in matching_pesid_repos: -- if pesid_repo.rhui == self.cloud_provider: -- return pesid_repo -- if not pesid_repo.rhui: -- cdn_pesid_repo = pesid_repo -- -- # If we did not find a repoid for the current cloud provider, return the CDN repository -- return cdn_pesid_repo # might be None e.g. if we are on Azure with an AWS repository enabled (unlikely) -- -- def get_target_pesids(self, source_pesid): -- """ -- Return sorted list of target PES IDs for the given source PES ID. -- -- :param source_pesid: Source PES ID to find equivalents for. -- :type source_pesid: src -- :return: The list of target PES IDs the provided source_pesid is mapped to. -- :rtype: List[PESIDRepositoryEntry] -- """ -- pesids = set() -- for repomap in self.mapping: -- if repomap.source == source_pesid: -- pesids.update(repomap.target) -- return sorted(pesids) -- -- def get_pesid_repos(self, pesid, major_version, distro): -- """ -- Get the list of PESIDRepositoryEntry with the specified PES ID, -- OS major version, and OS release ID. -- -- :param pesid: PES ID of the repositories to be retrieved. -- :type pesid: str -- :param major_version: OS major version of repositories to be retrieved. -- :type major_version: str -- :param distro: OS release ID of repositories to be retrieved, -- :type distro: str -- :return: A list of PESIDRepositoryEntries that match the provided PES ID, OS major version, and OS release ID. -- :rtype: List[PESIDRepositoryEntry] -- """ -- pesid_repos = [] -- for pesid_repo in self.repositories: -- if ( -- pesid_repo.pesid == pesid -- and pesid_repo.major_version == major_version -- and pesid_repo.distro == distro -- ): -- pesid_repos.append(pesid_repo) -- return pesid_repos -- -- def get_source_pesid_repos(self, pesid): -- """ -- Return the list of PESIDRepositoryEntry objects for a specified PES ID -- matching the source OS. -- -- :param pesid: The PES ID for which to retrieve PESIDRepositoryEntries. -- :type pesid: str -- :return: A list of PESIDRepositoryEntries that match the provided PES ID and have -- the OS Major version same as the source OS. -- :rtype: List[PESIDRepositoryEntry] -- """ -- return self.get_pesid_repos(pesid, get_source_major_version(), self.source_distro) -- -- def get_target_pesid_repos(self, pesid): -- """ -- Return the list of PESIDRepositoryEntry objects for a specified PES ID -- matching the target OS. -- -- :param pesid: The PES ID for which to retrieve PESIDRepositoryEntries. -- :type pesid: str -- :return: A list of PESIDRepositoryEntries that match the provided PES ID and have -- the OS Major version same as the target OS. -- :rtype: List[PESIDRepositoryEntry] -- """ -- return self.get_pesid_repos(pesid, get_target_major_version(), self.target_distro) -- -- def _find_repository_target_equivalent(self, src_pesidrepo, target_pesid): -- """ -- Find the target repository that is the best-match to the source one with the given -- target PES ID. -- -- :param src_pesidrepo: The source repository to find equivalent to. -- :type src_pesidrepo: PESIDRepositoryEntry -- :param target_pesid: The target PES ID which the target repository must contain. -- :type target_pesid: str -- :return: A target equivalent of given repository. -- :rtype: Optional[PESIDRepositoryEntry] -- """ -- -- candidates = [] -- for candidate in self.get_target_pesid_repos(target_pesid): -- matches_rhui = candidate.rhui == src_pesidrepo.rhui -- matches_repo_type = candidate.repo_type == 'rpm' -- matches_arch = candidate.arch == api.current_actor().configuration.architecture -- matches_distro = candidate.distro == self.target_distro -- -- if matches_rhui and matches_arch and matches_distro and matches_repo_type: -- # user can specify in future the specific channel should be -- # prioritized always (e.g. want to go to EUS...). -- channel = self.prio_channel or src_pesidrepo.channel -- if candidate.channel == channel: -- return candidate -- candidates.append(candidate) -- -- # Fallback... -- # Could not find exact-match, so go through candidates if we find an -- # alternative in one of default channels (usually just 'ga') -- for channel in self.default_channels: -- for candidate in candidates: -- if channel == candidate.channel: -- return candidate -- -- # This is a case, that must be handled by the caller -- return None -- -- def get_mapped_target_pesid_repos(self, src_pesidrepo): -- """ -- Returns the dict of form {target_pesid: target PESIDRepositoryEntry} containing -- pesids of repositories that is the source pesidrepo mapped to as keys and with -- the best fitting repository for each of the target pesids as values. -- -- The function always returns the best candidate for every target pesid -- that fits to the given source repository. In case no repository is find -- for a pesid, the value in dict is None: {pesid: None} -- -- :param src_pesidrepo: The PESIDRepositoryEntry to find the target pesids and the corresponding -- best-fitting repositories to. -- :type src_pesidrepo: PESIDRepositoryEntry -- :return: A dictionary of the form described above. -- :rtype: Dict[str, PESIDRepositoryEntry] -- """ -- result = {} -- for target_pesid in self.get_target_pesids(src_pesidrepo.pesid): -- result[target_pesid] = self._find_repository_target_equivalent(src_pesidrepo, target_pesid) -- return result -- -- def get_mapped_target_repoids(self, src_pesidrepo): -- """ -- Return the list of target repoids for the given src_pesidrepo. -- -- Some actors do not has to check whether a target repository has been -- found for each target PES ID and details about the target repositories. -- For such actors it's ok to see just list of repoids they should be -- interested about and keep the proper checks for the right actor. -- -- :param src_pesidrepo: The PESIDRepositoryEntry to find the corresponding best-fitting repositories to. -- :type src_pesidrepo: PESIDRepositoryEntry -- :return: A dictionary of the form described above. -- :rtype: Dict[str, PESIDRepositoryEntry] -- """ -- return [repo.repoid for repo in self.get_mapped_target_pesid_repos(src_pesidrepo).values() if repo] -- -- def get_expected_target_pesid_repos(self, src_repoids): -- """ -- Return {target_pesid: PESIDRepositoryEntry} with expected target repositories. -- -- If some repositories are mapped to a target pesid for which no equivalent -- repository is discovered, such a key contains just None value. -- -- :param src_repoids: list of present source repoids that should be mapped to target repositories -- :type src_repoids: List[str] -- :rtype: {str: PESIDRepositoryEntry} -- """ -- # {pesid: target_repo} -- target_repos_best_candidates = {} -- for src_repoid in src_repoids: -- src_pesidrepo = self.get_pesid_repo_entry(src_repoid, get_source_major_version(), self.source_distro) -- if not src_pesidrepo: -- # unmapped or custom repo -> skip this one -- continue -- -- for target_pesid, target_candidate in self.get_mapped_target_pesid_repos(src_pesidrepo).items(): -- best_candidate = target_repos_best_candidates.get(target_pesid, None) -- if not best_candidate: -- # we need to initialize the pesid even when the target_candidate is empty -- # to know we possibly miss something -- target_repos_best_candidates[target_pesid] = target_candidate -- if not target_candidate: -- # It's not crucial in this moment - the pesid can be still filled -- # by other maps. However log the warning as it is still unexpected -- # with the valid repomap data -- api.current_logger().warning( -- 'Cannot find any mapped target repository from the' -- ' {pesid} family for the {repoid} repository.' -- .format(repoid=src_repoid, pesid=target_pesid) -- ) -- continue -- -- if best_candidate and _get_channel_prio(target_candidate) > _get_channel_prio(best_candidate): -- # NOTE(pstodulk): we want just one target repository from the "PESID family" -- # priority: beta < ga < all_else -- # Why all_else? -- # -> we do not expect multiple different premium channels present on the system -- target_repos_best_candidates[target_pesid] = target_candidate -- return target_repos_best_candidates -- -- --def get_default_repository_channels(repomap, src_repoids): -- """ -- Returns the default repository channels. The 'ga' channel is always included and is the last -- one in the list, so it is the lowest priority when checking for channels. -- -- :param repomap: A RepoMapDataHandler instance containing the RepositoriesMapping data. -- :type repomap: RepoMapDataHandler -- :param src_repoids: Repositories present on the source system. -- :type src_repoids: List[str] -- :rtype: List[str] -- """ -- default_pesid = DEFAULT_PESID[get_source_major_version()] -- top_prio_pesid_repo = None -- for repoid in src_repoids: -- pesid_repo = repomap.get_pesid_repo_entry( -- repoid, get_source_major_version(), get_source_distro_id() -- ) -- if not pesid_repo or pesid_repo.pesid != default_pesid: -- continue -- if not top_prio_pesid_repo or _get_channel_prio(pesid_repo) > _get_channel_prio(top_prio_pesid_repo): -- top_prio_pesid_repo = pesid_repo -- -- # always return at least 'ga' -- if not top_prio_pesid_repo or top_prio_pesid_repo.channel == 'ga': -- return ['ga'] -- -- # keep this order to prefer higher prio to check first -- return [top_prio_pesid_repo.channel, 'ga'] -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/actor.py b/repos/system_upgrade/common/actors/targetcontentresolver/actor.py -new file mode 100644 -index 00000000..6ea95732 ---- /dev/null -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/actor.py -@@ -0,0 +1,65 @@ -+from leapp.actors import Actor -+from leapp.libraries.actor.targetcontentresolver import process -+from leapp.models import ( -+ ConsumedDataAsset, -+ CustomTargetRepository, -+ DistributionSignedRPM, -+ EnabledModules, -+ InstalledRPM, -+ PESRpmTransactionTasks, -+ RepositoriesBlacklisted, -+ RepositoriesBlocklisted, -+ RepositoriesFacts, -+ RepositoriesMapping, -+ RepositoriesSetupTasks, -+ RHUIInfo, -+ RpmTransactionTasks, -+ SkippedRepositories, -+ TargetRepositories, -+ UsedRepositories -+) -+from leapp.reporting import Report -+from leapp.tags import FactsPhaseTag, IPUWorkflowTag -+from leapp.utils.deprecation import suppress_deprecation -+ -+ -+@suppress_deprecation(RepositoriesBlacklisted) -+class TargetContentResolver(Actor): -+ """ -+ Resolve the complete set of target repositories and RPM transaction tasks for the upgrade. -+ -+ This actor consolidates four steps that were previously separate actors: -+ 1. Load the repository mapping (repomap.json) and produce RepositoriesMapping. -+ 2. Determine which target repositories should be excluded (e.g. unsupported CRB repos). -+ 3. Process PES events (pes-events.json) to compute package changes and -+ produce PESRpmTransactionTasks. -+ 4. Determine the final list of target repositories to enable, producing -+ TargetRepositories and SkippedRepositories. -+ """ -+ -+ name = 'target_content_resolver' -+ consumes = ( -+ CustomTargetRepository, -+ DistributionSignedRPM, -+ EnabledModules, -+ InstalledRPM, -+ RepositoriesFacts, -+ RepositoriesSetupTasks, -+ RHUIInfo, -+ RpmTransactionTasks, -+ UsedRepositories, -+ ) -+ produces = ( -+ ConsumedDataAsset, -+ PESRpmTransactionTasks, -+ Report, -+ RepositoriesBlacklisted, -+ RepositoriesBlocklisted, -+ RepositoriesMapping, -+ SkippedRepositories, -+ TargetRepositories, -+ ) -+ tags = (IPUWorkflowTag, FactsPhaseTag) -+ -+ def process(self): -+ process() -diff --git a/repos/system_upgrade/common/actors/peseventsscanner/libraries/pes_event_parsing.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/pes_event_parsing.py -similarity index 100% -rename from repos/system_upgrade/common/actors/peseventsscanner/libraries/pes_event_parsing.py -rename to repos/system_upgrade/common/actors/targetcontentresolver/libraries/pes_event_parsing.py -diff --git a/repos/system_upgrade/common/actors/peseventsscanner/libraries/pes_events_scanner.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/pes_events_scanner.py -similarity index 93% -rename from repos/system_upgrade/common/actors/peseventsscanner/libraries/pes_events_scanner.py -rename to repos/system_upgrade/common/actors/targetcontentresolver/libraries/pes_events_scanner.py -index 6ba16ced..a03071ae 100644 ---- a/repos/system_upgrade/common/actors/peseventsscanner/libraries/pes_events_scanner.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/pes_events_scanner.py -@@ -3,7 +3,7 @@ from functools import partial - - from leapp import reporting - from leapp.exceptions import StopActorExecutionError --from leapp.libraries.actor import peseventsscanner_repomap -+from leapp.libraries.actor import repomap_calc - from leapp.libraries.actor.pes_event_parsing import Action, get_pes_events, Package - from leapp.libraries.common import rpms - from leapp.libraries.common.config import get_target_distro_id, version -@@ -16,10 +16,7 @@ from leapp.models import ( - Module, - PESIDRepositoryEntry, - PESRpmTransactionTasks, -- RepositoriesBlacklisted, - RepositoriesFacts, -- RepositoriesMapping, -- RepositoriesSetupTasks, - RHUIInfo, - RpmTransactionTasks - ) -@@ -323,11 +320,14 @@ def report_skipped_packages(title, message, skipped_pkgs, remediation=None): - api.current_logger().info(summary) - - --def remove_new_packages_from_blacklisted_repos(source_pkgs, target_pkgs): -+def remove_new_packages_from_blacklisted_repos(source_pkgs, target_pkgs, blacklisted_repoids=None): - """ - Remove newly installed packages from blacklisted repositories that were computed to be on the target system. -+ -+ :param blacklisted_repoids: Set of repoids to exclude. If None, an empty set is used. - """ -- blacklisted_repoids = get_blacklisted_repoids() -+ if blacklisted_repoids is None: -+ blacklisted_repoids = set() - new_pkgs = target_pkgs.difference(source_pkgs) - pkgs_from_blacklisted_repos = set(pkg for pkg in new_pkgs if pkg.repository in blacklisted_repoids) - -@@ -340,13 +340,6 @@ def remove_new_packages_from_blacklisted_repos(source_pkgs, target_pkgs): - return blacklisted_repoids, target_pkgs.difference(pkgs_from_blacklisted_repos) - - --def get_blacklisted_repoids(): -- repos_blacklisted = set() -- for blacklist in api.consume(RepositoriesBlacklisted): -- repos_blacklisted.update(blacklist.repoids) -- return repos_blacklisted -- -- - def get_enabled_repoids(): - """ - Collects repoids of all enabled repositories on the source system. -@@ -364,44 +357,34 @@ def get_enabled_repoids(): - return enabled_repoids - - --def get_pesid_to_repoid_map(target_pesids): -+def get_pesid_to_repoid_map(target_pesids, repositories_map_msg): - """ - Get a dictionary mapping all PESID repositories to their corresponding repoid. - - :param target_pesids: The set of target PES IDs needed to be mapped -+ :param repositories_map_msg: RepositoriesMapping message. - :return: Dictionary mapping the target_pesids to their corresponding repoid - """ -- -- repositories_map_msgs = api.consume(RepositoriesMapping) -- repositories_map_msg = next(repositories_map_msgs, None) -- if list(repositories_map_msgs): -- api.current_logger().warning('Unexpectedly received more than one RepositoriesMapping message.') -- if not repositories_map_msg: -- raise StopActorExecutionError( -- 'Cannot parse RepositoriesMapping data properly', -- details={'Problem': 'Did not receive a message with mapped repositories'} -- ) -- - rhui_info = next(api.consume(RHUIInfo), None) - cloud_provider = rhui_info.provider if rhui_info else '' - -- repomap = peseventsscanner_repomap.RepoMapDataHandler(repositories_map_msg, cloud_provider=cloud_provider) -+ repomap_handler = repomap_calc.RepoMapDataHandler(repositories_map_msg, cloud_provider=cloud_provider) - - # NOTE: We have to calculate expected target repositories like in the setuptargetrepos actor. - # It's planned to handle this in different a way in future... - - enabled_repoids = get_enabled_repoids() -- default_channels = peseventsscanner_repomap.get_default_repository_channels(repomap, enabled_repoids) -- repomap.set_default_channels(default_channels) -+ default_channels = repomap_calc.get_default_repository_channels(repomap_handler, enabled_repoids) -+ repomap_handler.set_default_channels(default_channels) - -- exp_pesid_repos = repomap.get_expected_target_pesid_repos(enabled_repoids) -+ exp_pesid_repos = repomap_handler.get_expected_target_pesid_repos(enabled_repoids) - # FIXME: this is hack now. In case some packages will need a repository - # with pesid that is not mapped by default regarding the enabled repos, - # let's use this one representative repository (baseos/appstream) to get - # data for a guess of the best repository from the requires target pesid.. - # FIXME: this could now fail in case all repos are disabled... - representative_repo = exp_pesid_repos.get( -- peseventsscanner_repomap.DEFAULT_PESID[version.get_target_major_version()], None -+ repomap_calc.DEFAULT_PESID[version.get_target_major_version()], None - ) - if not representative_repo: - api.current_logger().warning('Cannot determine the representative target base repository.') -@@ -409,7 +392,7 @@ def get_pesid_to_repoid_map(target_pesids): - 'Fallback: Create an artificial representative PESIDRepositoryEntry for the repository mapping' - ) - representative_repo = PESIDRepositoryEntry( -- pesid=peseventsscanner_repomap.DEFAULT_PESID[version.get_target_major_version()], -+ pesid=repomap_calc.DEFAULT_PESID[version.get_target_major_version()], - arch=api.current_actor().configuration.architecture, - major_version=version.get_target_major_version(), - repoid='artificial-repoid', -@@ -429,7 +412,7 @@ def get_pesid_to_repoid_map(target_pesids): - if not representative_repo: - api.current_logger().warning('Cannot find suitable repository for PES ID: {}'.format(pesid)) - continue -- pesid_repo = repomap._find_repository_target_equivalent(representative_repo, pesid) -+ pesid_repo = repomap_handler._find_repository_target_equivalent(representative_repo, pesid) - - if not pesid_repo: - api.current_logger().warning('Cannot find suitable repository for PES ID: {}'.format(pesid)) -@@ -450,7 +433,7 @@ def get_pesid_to_repoid_map(target_pesids): - return repositories_mapping - - --def replace_pesids_with_repoids_in_packages(packages, source_pkgs_repoids): -+def replace_pesids_with_repoids_in_packages(packages, source_pkgs_repoids, repositories_map_msg): - """Replace packages with PESID in their .repository field with ones that have repoid providing the package.""" - # We want to map only PESIDs - if some package had no events, it will its repository set to source system repoid - packages_with_pesid = {pkg for pkg in packages if pkg.repository not in source_pkgs_repoids} -@@ -458,7 +441,7 @@ def replace_pesids_with_repoids_in_packages(packages, source_pkgs_repoids): - - required_target_pesids = {pkg.repository for pkg in packages_with_pesid} - -- pesid_to_target_repoid_map = get_pesid_to_repoid_map(required_target_pesids) -+ pesid_to_target_repoid_map = get_pesid_to_repoid_map(required_target_pesids, repositories_map_msg) - - packages_with_unknown_target_repoid = { - pkg -@@ -572,11 +555,19 @@ def include_instructions_from_transaction_configuration(rpm_tasks, transaction_c - modules_to_reset=modules_to_reset) - - --def process(): -+def scan_pes_events(repositories_map_msg, blacklisted_repoids=None): -+ """ -+ Process PES events and compute RPM transaction tasks. -+ -+ :param repositories_map_msg: RepositoriesMapping message. -+ :param blacklisted_repoids: Set of repoids to exclude from target packages. If None, an empty set is used. -+ :returns: Set of target repoids that need to be enabled, or None if no PES events are found. -+ :rtype: Optional[set] -+ """ - # Retrieve data - installed_pkgs, transaction configuration, pes events - events = get_pes_events('/etc/leapp/files', 'pes-events.json') - if not events: -- return -+ return None - - releases = get_relevant_releases(events) - installed_pkgs = get_installed_pkgs() -@@ -595,16 +586,14 @@ def process(): - events, releases) - - # Packages coming out of the events have PESID as their repository, however, we need real repoid -- target_pkgs = replace_pesids_with_repoids_in_packages(target_pkgs, repoids_of_source_pkgs) -+ target_pkgs = replace_pesids_with_repoids_in_packages(target_pkgs, repoids_of_source_pkgs, repositories_map_msg) - - # Apply the desired repository blacklisting - blacklisted_repoids, target_pkgs = remove_new_packages_from_blacklisted_repos(pkgs_to_begin_computation_with, -- target_pkgs) -+ target_pkgs, blacklisted_repoids) - - # Look at the target packages and determine what repositories to enable -- target_repoids = sorted(set(p.repository for p in target_pkgs) - blacklisted_repoids - repoids_of_source_pkgs) -- repos_to_enable = RepositoriesSetupTasks(to_enable=target_repoids) -- api.produce(repos_to_enable) -+ pes_requested_repoids = set(p.repository for p in target_pkgs) - blacklisted_repoids - repoids_of_source_pkgs - - # Compare the packages on source system and the computed packages on target system and determine what to install - rpm_tasks = compute_rpm_tasks_from_pkg_set_diff(pkgs_to_begin_computation_with, target_pkgs, pkgs_to_demodularize) -@@ -612,3 +601,5 @@ def process(): - installed_pkgs) - if rpm_tasks: - api.produce(rpm_tasks) -+ -+ return pes_requested_repoids -diff --git a/repos/system_upgrade/common/actors/peseventsscanner/libraries/peseventsscanner_repomap.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repomap_calc.py -similarity index 100% -rename from repos/system_upgrade/common/actors/peseventsscanner/libraries/peseventsscanner_repomap.py -rename to repos/system_upgrade/common/actors/targetcontentresolver/libraries/repomap_calc.py -diff --git a/repos/system_upgrade/common/actors/repositoriesmapping/libraries/repositoriesmapping.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repomap_loader.py -similarity index 98% -rename from repos/system_upgrade/common/actors/repositoriesmapping/libraries/repositoriesmapping.py -rename to repos/system_upgrade/common/actors/targetcontentresolver/libraries/repomap_loader.py -index 503e66a3..52b8337d 100644 ---- a/repos/system_upgrade/common/actors/repositoriesmapping/libraries/repositoriesmapping.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repomap_loader.py -@@ -183,10 +183,12 @@ def scan_repositories(read_repofile_func=_read_repofile): - mapping = repomap_data.get_mappings(get_source_major_version(), get_target_major_version()) - - valid_major_versions = [get_source_major_version(), get_target_major_version()] -- api.produce(RepositoriesMapping( -+ repositories_mapping = RepositoriesMapping( - mapping=mapping, - repositories=repomap_data.get_repositories(valid_major_versions) -- )) -+ ) -+ api.produce(repositories_mapping) -+ return repositories_mapping - except ModelViolationError as err: - err_message = ( - 'The repository mapping file is invalid: ' -@@ -200,3 +202,4 @@ def scan_repositories(read_repofile_func=_read_repofile): - except ValueError as err: - # The error should contain enough information, so we do not need to clarify it further - _inhibit_upgrade('The repository mapping file is invalid: {}'.format(err)) -+ return None -diff --git a/repos/system_upgrade/common/actors/repositoriesblacklist/libraries/repositoriesblacklist.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repositoriesblocklist.py -similarity index 63% -rename from repos/system_upgrade/common/actors/repositoriesblacklist/libraries/repositoriesblacklist.py -rename to repos/system_upgrade/common/actors/targetcontentresolver/libraries/repositoriesblocklist.py -index 5059f619..48656187 100644 ---- a/repos/system_upgrade/common/actors/repositoriesblacklist/libraries/repositoriesblacklist.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repositoriesblocklist.py -@@ -2,7 +2,14 @@ from leapp import reporting - from leapp.exceptions import StopActorExecutionError - from leapp.libraries.common.config.version import get_source_major_version, get_target_major_version - from leapp.libraries.stdlib import api --from leapp.models import CustomTargetRepository, RepositoriesBlacklisted, RepositoriesFacts, RepositoriesMapping -+from leapp.models import ( -+ CustomTargetRepository, -+ RepositoriesBlacklisted, -+ RepositoriesBlocklisted, -+ RepositoriesFacts, -+ RepositoriesSetupTasks -+) -+from leapp.utils.deprecation import suppress_deprecation - - # {OS_MAJOR_VERSION: PESID} - UNSUPPORTED_PESIDS = { -@@ -14,11 +21,11 @@ UNSUPPORTED_PESIDS = { - - def _report_using_unsupported_repos(repos): - report = [ -- reporting.Title("Using repository not supported by Red Hat"), -+ reporting.Title('Using repository not supported by Red Hat'), - reporting.Summary( -- "The following repositories have been used for the " -- "upgrade, but they are not supported by the Red Hat.:\n" -- "- {}".format("\n - ".join(repos)) -+ 'The following repositories have been used for the ' -+ 'upgrade, but they are not supported by the Red Hat.:\n' -+ '- {}'.format('\n - '.join(repos)) - ), - reporting.Severity(reporting.Severity.HIGH), - reporting.Groups([reporting.Groups.REPOSITORY]), -@@ -28,25 +35,25 @@ def _report_using_unsupported_repos(repos): - - def _report_excluded_repos(repos): - api.current_logger().info( -- "The CRB repository is not enabled. Excluding {} from the upgrade".format(repos) -+ 'The CRB repository is not enabled. Excluding {} from the upgrade'.format(repos) - ) - - report = [ -- reporting.Title("Excluded target system repositories"), -+ reporting.Title('Excluded target system repositories'), - reporting.Summary( -- "The following repositories are not supported by " -- "Red Hat and are excluded from the list of repositories " -- "used during the upgrade.\n- {}".format("\n- ".join(repos)) -+ 'The following repositories are not supported by ' -+ 'Red Hat and are excluded from the list of repositories ' -+ 'used during the upgrade.\n- {}'.format('\n- '.join(repos)) - ), - reporting.Severity(reporting.Severity.INFO), - reporting.Groups([reporting.Groups.REPOSITORY]), - reporting.Groups([reporting.Groups.FAILURE]), - reporting.Remediation( - hint=( -- "If some of excluded repositories are still required to be used" -- " during the upgrade, execute leapp with the --enablerepo option" -- " with the repoid of the repository required to be enabled" -- " as an argument (the option can be used multiple times)." -+ 'If some of excluded repositories are still required to be used' -+ ' during the upgrade, execute leapp with the --enablerepo option' -+ ' with the repoid of the repository required to be enabled' -+ ' as an argument (the option can be used multiple times).' - ) - ), - ] -@@ -87,10 +94,10 @@ def _get_pesid_repos(repo_mapping, pesid, major_version): - - def _get_repoids_to_exclude(repo_mapping): - """ -- Returns a set of repoids that should be blacklisted on the target system. -+ Returns a set of repoids that should be blocklisted on the target system. - - :param RepositoriesMapping repo_mapping: Repository mapping data. -- :returns: A set of repoids to blacklist on the target system. -+ :returns: A set of repoids to blocklist on the target system. - :rtype: Set[str] - """ - pesid_repos_to_exclude = _get_pesid_repos(repo_mapping, -@@ -123,7 +130,7 @@ def _are_optional_repos_disabled(repo_mapping, repos_on_system): - return True - - --def process(): -+def get_blocklisted_repoids(repo_mapping): - """ - Exclude target repositories provided by Red Hat without support. - -@@ -134,27 +141,22 @@ def process(): - (e.g. via the --enablerepo option or via the /etc/leapp/files/leapp_upgrade_repositories.repo file) - - E.g. CRB repository is provided by Red Hat but it is without the support. -- """ - -- repo_mapping = next(api.consume(RepositoriesMapping), None) -+ :param repo_mapping: RepositoriesMapping message. -+ :returns: Set of repoids to blocklist on the target system. -+ :rtype: set -+ """ - repos_facts = next(api.consume(RepositoriesFacts), None) - -- # Handle required messages not received -- missing_messages = [] -- if not repo_mapping: -- missing_messages.append('RepositoriesMapping') - if not repos_facts: -- missing_messages.append('RepositoriesFacts') -- if missing_messages: - raise StopActorExecutionError('Actor didn\'t receive required messages: {0}'.format( -- ', '.join(missing_messages) -+ 'RepositoriesFacts' - )) - - if not _are_optional_repos_disabled(repo_mapping, repos_facts): - # nothing to do - an optional repository is enabled -- return -+ return set() - -- # Optional repos are either not present or they are present, but disabled -> blacklist them on target system - repos_to_exclude = _get_repoids_to_exclude(repo_mapping) - - # Do not exclude repos manually enabled from the CLI -@@ -165,4 +167,39 @@ def process(): - _report_using_unsupported_repos(manually_enabled_repos) - if filtered_repos_to_exclude: - _report_excluded_repos(filtered_repos_to_exclude) -- api.produce(RepositoriesBlacklisted(repoids=list(filtered_repos_to_exclude))) -+ -+ return filtered_repos_to_exclude -+ -+ -+@suppress_deprecation(RepositoriesBlacklisted) -+def compute_blocklist(repo_mapping): -+ """ -+ Compute the full blocklist and extract external repoid requests. -+ -+ Merges the internally determined blocklist (unsupported repos like CRB) -+ with external blocklist requests from ``RepositoriesSetupTasks.to_block``. -+ When the resulting blocklist is non-empty, produces both -+ ``RepositoriesBlocklisted`` and the deprecated ``RepositoriesBlacklisted`` -+ messages. -+ -+ Also extracts ``to_enable`` repoids from the same external tasks so that -+ the caller does not need to re-consume the messages. -+ -+ :param repo_mapping: RepositoriesMapping message. -+ :returns: Tuple of (full_blocklist_repoids, external_repoids_requests). -+ :rtype: tuple[set, set] -+ """ -+ internal_blocklist = get_blocklisted_repoids(repo_mapping) -+ -+ external_blocklist_repoids = set() -+ external_repoids_requests = set() -+ for task in api.consume(RepositoriesSetupTasks): -+ external_blocklist_repoids.update(task.to_block) -+ external_repoids_requests.update(task.to_enable) -+ -+ full_blocklist_repoids = internal_blocklist.union(external_blocklist_repoids) -+ if full_blocklist_repoids: -+ api.produce(RepositoriesBlocklisted(repoids=sorted(full_blocklist_repoids))) -+ api.produce(RepositoriesBlacklisted(repoids=sorted(full_blocklist_repoids))) -+ -+ return full_blocklist_repoids, external_repoids_requests -diff --git a/repos/system_upgrade/common/actors/setuptargetrepos/libraries/setuptargetrepos.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/setuptargetrepos.py -similarity index 71% -rename from repos/system_upgrade/common/actors/setuptargetrepos/libraries/setuptargetrepos.py -rename to repos/system_upgrade/common/actors/targetcontentresolver/libraries/setuptargetrepos.py -index df17a217..b9e5a1c4 100644 ---- a/repos/system_upgrade/common/actors/setuptargetrepos/libraries/setuptargetrepos.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/setuptargetrepos.py -@@ -1,4 +1,5 @@ --from leapp.libraries.actor import setuptargetrepos_repomap -+from leapp.libraries.actor import repomap_calc -+from leapp.libraries.actor.pes_events_scanner import get_enabled_repoids as _get_enabled_repoids - from leapp.libraries.common.config import get_source_distro_id, get_target_distro_id - from leapp.libraries.common.config.version import get_source_major_version, get_source_version - from leapp.libraries.stdlib import api -@@ -6,10 +7,6 @@ from leapp.models import ( - CustomTargetRepository, - DistroTargetRepository, - InstalledRPM, -- RepositoriesBlacklisted, -- RepositoriesFacts, -- RepositoriesMapping, -- RepositoriesSetupTasks, - RHELTargetRepository, - RHUIInfo, - SkippedRepositories, -@@ -24,22 +21,6 @@ RHUI_CLIENT_REPOIDS_RHEL88_TO_RHEL810 = { - } - - --def _get_enabled_repoids(): -- """ -- Collects repoids of all enabled repositories on the source system. -- -- :returns: Set of all enabled repository IDs present on the source system. -- :rtype: Set[str] -- """ -- enabled_repoids = set() -- for repos in api.consume(RepositoriesFacts): -- for repo_file in repos.repositories: -- for repo in repo_file.data: -- if repo.enabled: -- enabled_repoids.add(repo.repoid) -- return enabled_repoids -- -- - def _get_repoids_from_installed_packages(): - repoids_from_installed_packages = set() - for installed_packages in api.consume(InstalledRPM): -@@ -48,13 +29,6 @@ def _get_repoids_from_installed_packages(): - return repoids_from_installed_packages - - --def _get_blacklisted_repoids(): -- repos_blacklisted = set() -- for blacklist in api.consume(RepositoriesBlacklisted): -- repos_blacklisted.update(blacklist.repoids) -- return repos_blacklisted -- -- - def _get_custom_target_repos(): - custom_repos = [] - for repo in api.consume(CustomTargetRepository): -@@ -84,24 +58,35 @@ def _get_mapped_repoids(repomap, src_repoids): - - - @suppress_deprecation(RHELTargetRepository) --def process(): -+def setup_target_repos(repositories_map_msg, pes_requested_repoids=None, -+ blacklisted_repoids=None, external_repoids_requests=None): -+ """ -+ Determine the final list of target repositories. -+ -+ :param repositories_map_msg: RepositoriesMapping message. -+ :param pes_requested_repoids: Set of repoids derived from PES events that need to be enabled. -+ :param blacklisted_repoids: Set of repoids to exclude from target repos. If None, an empty set is used. -+ :param external_repoids_requests: Set of repoids requested by external actors (e.g. satellite_upgrade_facts). -+ """ - # Load relevant data from messages - used_repoids_dict = _get_used_repo_dict() - enabled_repoids = _get_enabled_repoids() -- excluded_repoids = _get_blacklisted_repoids() -+ excluded_repoids = blacklisted_repoids if blacklisted_repoids is not None else set() - custom_repos = _get_custom_target_repos() - repoids_from_installed_packages = _get_repoids_from_installed_packages() - - # Setup repomap handler -- repo_mappig_msg = next(api.consume(RepositoriesMapping), RepositoriesMapping()) -+ repo_mapping_msg = repositories_map_msg - - rhui_info = next(api.consume(RHUIInfo), None) - cloud_provider = rhui_info.provider if rhui_info else '' - -- repomap = setuptargetrepos_repomap.RepoMapDataHandler(repo_mappig_msg, cloud_provider=cloud_provider) -+ repomap_handler = repomap_calc.RepoMapDataHandler(repo_mapping_msg, cloud_provider=cloud_provider) - - # Filter set of repoids from installed packages so that it contains only repoids with mapping -- repoids_from_installed_packages_with_mapping = _get_mapped_repoids(repomap, repoids_from_installed_packages) -+ repoids_from_installed_packages_with_mapping = _get_mapped_repoids( -+ repomap_handler, repoids_from_installed_packages -+ ) - - # Set of repoid that are going to be mapped to target repoids containing enabled repoids and also repoids from - # installed packages that have mapping to prevent missing repositories that are disabled during the upgrade, but -@@ -118,13 +103,13 @@ def process(): - repoids_to_map.remove(rhel810_rhui_client_repoid) - repoids_to_map.add(rhel88_rhui_client_repoid) - -- # Set default repository channels for the repomap -+ # Set default repository channels for the repositories mapping - # TODO(pstodulk): what about skip this completely and keep the default 'ga'..? -- default_channels = setuptargetrepos_repomap.get_default_repository_channels(repomap, repoids_to_map) -- repomap.set_default_channels(default_channels) -+ default_channels = repomap_calc.get_default_repository_channels(repomap_handler, repoids_to_map) -+ repomap_handler.set_default_channels(default_channels) - -- # Get target distro repoids based on the repomap -- expected_repos = repomap.get_expected_target_pesid_repos(repoids_to_map) -+ # Get target distro repoids based on the repositories mapping -+ expected_repos = repomap_handler.get_expected_target_pesid_repos(repoids_to_map) - target_distro_repoids = set() - for target_pesid, target_pesidrepo in expected_repos.items(): - if not target_pesidrepo: -@@ -145,17 +130,17 @@ def process(): - continue - target_distro_repoids.add(target_pesidrepo.repoid) - -- # FIXME: this could possibly result into a try to enable multiple repositories -- # from the same family (pesid). But unless we have a bug in previous actors, -- # it should not happen :) it's not blocker error anyway, so survive it. -- # - We expect to deliver the fix as part of the refactoring when we merge -- # setuptargetrepos & peseventsscanner actors together (+ blacklistrepos?) -- for task in api.consume(RepositoriesSetupTasks): -- for repo in task.to_enable: -- if repo in excluded_repoids: -- api.current_logger().debug('Skipping the {} repo from setup task (excluded).'.format(repo)) -- continue -- target_distro_repoids.add(repo) -+ # FIXME: this could possibly result in enabling multiple repositories -+ # from the same family (pesid). Proper fix requires deduplication by -+ # PESID family, picking the best candidate per family. -+ all_requested_repoids = set(pes_requested_repoids or set()) -+ all_requested_repoids.update(external_repoids_requests or set()) -+ -+ for repo in all_requested_repoids: -+ if repo in excluded_repoids: -+ api.current_logger().debug('Skipping the {} repo from setup task (excluded).'.format(repo)) -+ continue -+ target_distro_repoids.add(repo) - - # create the final lists and sort them (for easier testing) - if get_target_distro_id() == 'rhel': -@@ -167,7 +152,7 @@ def process(): - custom_repos = sorted(custom_repos, key=lambda x: x.repoid) - - # produce message about skipped repositories -- enabled_repoids_with_mapping = _get_mapped_repoids(repomap, enabled_repoids) -+ enabled_repoids_with_mapping = _get_mapped_repoids(repomap_handler, enabled_repoids) - skipped_repoids = enabled_repoids & set(used_repoids_dict.keys()) - enabled_repoids_with_mapping - if skipped_repoids: - pkgs = set() -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -new file mode 100644 -index 00000000..4db9003d ---- /dev/null -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -@@ -0,0 +1,39 @@ -+from leapp.libraries.actor import pes_events_scanner, repositoriesblocklist, setuptargetrepos -+from leapp.libraries.actor.repomap_loader import scan_repositories -+ -+ -+def process(): -+ """ -+ Orchestrate the four stages of target content resolution. -+ -+ 1. Load and produce the repository mapping (repomap.json). The produced -+ RepositoriesMapping message is also used directly by the subsequent -+ stages, avoiding a redundant round-trip through the message bus. -+ 2. Determine which target repositories should be excluded (blocklisted), -+ e.g. CRB repos that are unsupported and not explicitly enabled. -+ Merge with any external blocklist requests from RepositoriesSetupTasks -+ and produce RepositoriesBlocklisted / RepositoriesBlacklisted messages. -+ 3. Process PES events to compute RPM transaction tasks (what to install, -+ remove, keep) and determine which new repositories are needed. RPM -+ tasks are produced for downstream actors; the set of requested target -+ repoids is returned for direct use in stage 4. -+ 4. Build the final list of target repositories by combining PES-requested -+ repoids from stage 3, external repoid requests (to_enable from -+ RepositoriesSetupTasks, e.g. from satellite_upgrade_facts), enabled -+ source repos, installed package repos, and custom/blacklisted repo -+ configuration. -+ """ -+ repositories_map_msg = scan_repositories() -+ -+ full_blocklist_repoids, external_repoids_requests = ( -+ repositoriesblocklist.compute_blocklist(repositories_map_msg) -+ ) -+ -+ pes_requested_repoids = pes_events_scanner.scan_pes_events(repositories_map_msg, full_blocklist_repoids) -+ -+ setuptargetrepos.setup_target_repos( -+ repositories_map_msg, -+ pes_requested_repoids=pes_requested_repoids, -+ blacklisted_repoids=full_blocklist_repoids, -+ external_repoids_requests=external_repoids_requests, -+ ) -diff --git a/repos/system_upgrade/common/actors/repositoriesmapping/tests/files/repomap_example.json b/repos/system_upgrade/common/actors/targetcontentresolver/tests/files/repomap_example.json -similarity index 100% -rename from repos/system_upgrade/common/actors/repositoriesmapping/tests/files/repomap_example.json -rename to repos/system_upgrade/common/actors/targetcontentresolver/tests/files/repomap_example.json -diff --git a/repos/system_upgrade/common/actors/peseventsscanner/tests/files/sample01.json b/repos/system_upgrade/common/actors/targetcontentresolver/tests/files/sample01.json -similarity index 100% -rename from repos/system_upgrade/common/actors/peseventsscanner/tests/files/sample01.json -rename to repos/system_upgrade/common/actors/targetcontentresolver/tests/files/sample01.json -diff --git a/repos/system_upgrade/common/actors/peseventsscanner/tests/files/sample04.json b/repos/system_upgrade/common/actors/targetcontentresolver/tests/files/sample04.json -similarity index 100% -rename from repos/system_upgrade/common/actors/peseventsscanner/tests/files/sample04.json -rename to repos/system_upgrade/common/actors/targetcontentresolver/tests/files/sample04.json -diff --git a/repos/system_upgrade/common/actors/peseventsscanner/tests/test_event_parsing.py b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_event_parsing.py -similarity index 100% -rename from repos/system_upgrade/common/actors/peseventsscanner/tests/test_event_parsing.py -rename to repos/system_upgrade/common/actors/targetcontentresolver/tests/test_event_parsing.py -diff --git a/repos/system_upgrade/common/actors/peseventsscanner/tests/test_pes_event_scanner.py b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_pes_event_scanner.py -similarity index 96% -rename from repos/system_upgrade/common/actors/peseventsscanner/tests/test_pes_event_scanner.py -rename to repos/system_upgrade/common/actors/targetcontentresolver/tests/test_pes_event_scanner.py -index c8c14528..f872a947 100644 ---- a/repos/system_upgrade/common/actors/peseventsscanner/tests/test_pes_event_scanner.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_pes_event_scanner.py -@@ -22,7 +22,6 @@ from leapp.models import ( - RepoMapEntry, - RepositoriesFacts, - RepositoriesMapping, -- RepositoriesSetupTasks, - RepositoryData, - RepositoryFile, - RPM, -@@ -271,7 +270,7 @@ def test_actor_performs(monkeypatch): - monkeypatch.setattr(api, 'produce', produced_messages) - monkeypatch.setattr(reporting, 'create_report', created_report) - -- pes_events_scanner.process() -+ pes_events_scanner.scan_pes_events(repositories_mapping) - - assert produced_messages.called - -@@ -307,15 +306,14 @@ def test_transaction_configuration_has_effect(monkeypatch): - def test_repository_blacklist_is_correctly_applied(monkeypatch): - _Pkg = partial(Package, modulestream=None) - -- monkeypatch.setattr(pes_events_scanner, 'get_blacklisted_repoids', lambda: {'repo-a', 'repo-b', 'repo-c'}) - monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) - monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) - - source_pkgs = {_Pkg('pkg-b', 'repo-b')} - target_pkgs = {_Pkg('pkg-a', 'repo-a'), _Pkg('pkg-b', 'repo-b'), _Pkg('pkg-c', 'repo-c'), _Pkg('pkg-d', 'repo-d')} - -- blacklisted_repoids, target_pkgs = pes_events_scanner.remove_new_packages_from_blacklisted_repos(source_pkgs, -- target_pkgs) -+ blacklisted_repoids, target_pkgs = pes_events_scanner.remove_new_packages_from_blacklisted_repos( -+ source_pkgs, target_pkgs, {'repo-a', 'repo-b', 'repo-c'}) - result = pkgs_into_tuples(target_pkgs) - - assert blacklisted_repoids == {'repo-a', 'repo-b', 'repo-c'} -@@ -342,15 +340,14 @@ def test_blacklisted_repoid_is_not_produced(monkeypatch): - monkeypatch.setattr(pes_events_scanner, 'get_installed_pkgs', lambda: installed_pkgs) - monkeypatch.setattr(pes_events_scanner, 'get_pes_events', lambda folder, filename: events) - monkeypatch.setattr(pes_events_scanner, 'apply_transaction_configuration', lambda pkgs, transaction_cfg: pkgs) -- monkeypatch.setattr(pes_events_scanner, 'get_blacklisted_repoids', lambda: {'blacklisted-rhel9'}) - monkeypatch.setattr(pes_events_scanner, 'replace_pesids_with_repoids_in_packages', -- lambda pkgs, src_pkgs_repoids: pkgs) -+ lambda pkgs, src_pkgs_repoids, repo_map_msg=None: pkgs) - - monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) - monkeypatch.setattr(api, 'produce', produce_mocked()) - monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) - -- pes_events_scanner.process() -+ setup_tasks = pes_events_scanner.scan_pes_events(None, blacklisted_repoids={'blacklisted-rhel9'}) - - assert not reporting.create_report.called - -@@ -361,9 +358,8 @@ def test_blacklisted_repoid_is_not_produced(monkeypatch): - 'no PESRpmTransactionTasks should be produced, however, they were.') - assert not rpm_tasks, fail_desc - -- repo_setup_tasks = [msg for msg in api.produce.model_instances if isinstance(msg, RepositoriesSetupTasks)] -- assert len(repo_setup_tasks) == 1 -- assert repo_setup_tasks[0].to_enable == ['repoid-rhel9'] -+ assert setup_tasks is not None -+ assert setup_tasks == {'repoid-rhel9'} - - - @pytest.mark.parametrize( -@@ -501,10 +497,10 @@ def test_transaction_configuration_is_applied(monkeypatch): - monkeypatch.setattr(pes_events_scanner, 'remove_undesired_events', lambda events, releases: events) - monkeypatch.setattr(pes_events_scanner, '_get_enabled_modules', lambda *args: []) - monkeypatch.setattr(pes_events_scanner, 'replace_pesids_with_repoids_in_packages', -- lambda target_pkgs, repoids_of_source_pkgs: target_pkgs) -+ lambda target_pkgs, repoids_of_source_pkgs, repo_map_msg=None: target_pkgs) - monkeypatch.setattr(pes_events_scanner, - 'remove_new_packages_from_blacklisted_repos', -- lambda source_pkgs, target_pkgs: (set(), target_pkgs)) -+ lambda source_pkgs, target_pkgs, bl=None: (set(), target_pkgs)) - - msgs = [ - RpmTransactionTasks(to_remove=['pkg-not-in-events']), -@@ -517,9 +513,10 @@ def test_transaction_configuration_is_applied(monkeypatch): - - monkeypatch.setattr(api, 'produce', produce_mocked()) - -- pes_events_scanner.process() -+ setup_tasks = pes_events_scanner.scan_pes_events(None) - -- assert api.produce.called == 2 -+ assert api.produce.called == 1 -+ assert setup_tasks is not None - - produced_rpm_transaction_tasks = [ - msg for msg in api.produce.model_instances if isinstance(msg, PESRpmTransactionTasks) -diff --git a/repos/system_upgrade/common/actors/setuptargetrepos/tests/test_repomapping.py b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_repomap_operations.py -similarity index 99% -rename from repos/system_upgrade/common/actors/setuptargetrepos/tests/test_repomapping.py -rename to repos/system_upgrade/common/actors/targetcontentresolver/tests/test_repomap_operations.py -index 32af8609..ab5a4587 100644 ---- a/repos/system_upgrade/common/actors/setuptargetrepos/tests/test_repomapping.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_repomap_operations.py -@@ -3,8 +3,8 @@ import logging - - import pytest - --from leapp.libraries.actor import setuptargetrepos_repomap --from leapp.libraries.actor.setuptargetrepos_repomap import get_default_repository_channels, RepoMapDataHandler -+from leapp.libraries.actor import repomap_calc -+from leapp.libraries.actor.repomap_calc import get_default_repository_channels, RepoMapDataHandler - from leapp.libraries.common.testutils import CurrentActorMocked - from leapp.libraries.stdlib import api - from leapp.models import PESIDRepositoryEntry, RepoMapEntry, RepositoriesMapping -@@ -287,7 +287,7 @@ def test_get_source_pesid_repos(monkeypatch, repomap_data_for_pesid_repo_retriev - fail_description = ( - 'The get_source_pesid_repos method does not take into account the source system version correctly.' - ) -- monkeypatch.setattr(setuptargetrepos_repomap, 'get_source_major_version', lambda: '10') -+ monkeypatch.setattr(repomap_calc, 'get_source_major_version', lambda: '10') - - # Repeat the same test as above to make sure it respects the source OS major version - assert [] == handler.get_source_pesid_repos('pesid1'), fail_description -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_repositoriesblocklist.py b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_repositoriesblocklist.py -new file mode 100644 -index 00000000..5ffa0990 ---- /dev/null -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_repositoriesblocklist.py -@@ -0,0 +1,329 @@ -+import pytest -+ -+from leapp import reporting -+from leapp.libraries.actor import repositoriesblocklist -+from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked, produce_mocked -+from leapp.libraries.stdlib import api -+from leapp.models import ( -+ CustomTargetRepository, -+ PESIDRepositoryEntry, -+ RepoMapEntry, -+ RepositoriesBlacklisted, -+ RepositoriesBlocklisted, -+ RepositoriesFacts, -+ RepositoriesMapping, -+ RepositoriesSetupTasks, -+ RepositoryData, -+ RepositoryFile -+) -+from leapp.utils.deprecation import suppress_deprecation -+ -+ -+@pytest.fixture -+def repofacts_opts_disabled(): -+ repos_data = [ -+ RepositoryData( -+ repoid="codeready-builder-for-rhel-8-x86_64-rpms", -+ name="RHEL 8 CRB", -+ enabled=False, -+ ) -+ ] -+ repos_files = [ -+ RepositoryFile(file="/etc/yum.repos.d/redhat.repo", data=repos_data) -+ ] -+ return RepositoriesFacts(repositories=repos_files) -+ -+ -+@pytest.fixture -+def rhel8_crb_pesidrepo(): -+ return PESIDRepositoryEntry( -+ pesid='rhel8-CRB', -+ major_version='8', -+ repoid='codeready-builder-for-rhel-8-x86_64-rpms', -+ rhui='', -+ arch='x86_64', -+ channel='ga', -+ repo_type='rpm', -+ distro='rhel', -+ ) -+ -+ -+@pytest.fixture -+def rhel9_crb_pesidrepo(): -+ return PESIDRepositoryEntry( -+ pesid='rhel9-CRB', -+ major_version='9', -+ repoid='codeready-builder-for-rhel-9-x86_64-rpms', -+ rhui='', -+ arch='x86_64', -+ channel='ga', -+ repo_type='rpm', -+ distro='rhel', -+ ) -+ -+ -+@pytest.fixture -+def repomap_opts_only(rhel8_crb_pesidrepo, rhel9_crb_pesidrepo): -+ return RepositoriesMapping( -+ mapping=[RepoMapEntry(source='rhel8-CRB', target=['rhel9-CRB'])], -+ repositories=[rhel8_crb_pesidrepo, rhel9_crb_pesidrepo] -+ ) -+ -+ -+def test_all_target_optionals_blacklisted_when_no_optional_on_source(monkeypatch, repomap_opts_only): -+ """ -+ Tests whether every target optional repository gets blacklisted -+ if no optional repositories are used on the source system. -+ """ -+ -+ repos_data = [ -+ RepositoryData( -+ repoid="rhel-8-server-rpms", -+ name="RHEL 8 Server", -+ enabled=True, -+ ) -+ ] -+ repos_files = [ -+ RepositoryFile(file="/etc/yum.repos.d/redhat.repo", data=repos_data) -+ ] -+ repo_facts = RepositoriesFacts(repositories=repos_files) -+ -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[repo_facts])) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ monkeypatch.setattr(reporting, 'create_report', produce_mocked()) -+ -+ result = repositoriesblocklist.get_blocklisted_repoids(repomap_opts_only) -+ -+ assert 'codeready-builder-for-rhel-9-x86_64-rpms' in result -+ -+ -+def test_with_no_mapping_for_optional_repos(monkeypatch, repomap_opts_only, repofacts_opts_disabled): -+ """ -+ Tests whether an empty set is returned if no valid target is found for an optional repository in mapping data. -+ """ -+ -+ repomap_opts_only.repositories[1].pesid = 'test_pesid' -+ -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[repofacts_opts_disabled])) -+ -+ result = repositoriesblocklist.get_blocklisted_repoids(repomap_opts_only) -+ -+ assert not result -+ -+ -+def test_blacklist_produced_when_optional_repo_disabled(monkeypatch, repofacts_opts_disabled, repomap_opts_only): -+ """ -+ Tests whether a correct blacklist is generated when there is disabled optional repo on the system. -+ """ -+ -+ monkeypatch.setattr( -+ api, -+ "current_actor", -+ CurrentActorMocked(msgs=[repofacts_opts_disabled]), -+ ) -+ monkeypatch.setattr(api, "produce", produce_mocked()) -+ monkeypatch.setattr(reporting, "create_report", produce_mocked()) -+ -+ result = repositoriesblocklist.get_blocklisted_repoids(repomap_opts_only) -+ -+ assert result, 'A blacklist should get generated.' -+ -+ expected_blacklisted_repoid = 'codeready-builder-for-rhel-9-x86_64-rpms' -+ err_msg = 'Blacklist does not contain expected repoid.' -+ assert expected_blacklisted_repoid in result, err_msg -+ -+ -+def test_no_blacklist_produced_when_optional_repo_enabled(monkeypatch, repofacts_opts_disabled, repomap_opts_only): -+ """ -+ Tests whether an empty set is returned when an optional repository is enabled. -+ -+ Data are set up in such a fashion so that the determined blacklist would not be empty. -+ """ -+ -+ repofacts_opts_disabled.repositories[0].data[0].enabled = True -+ -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[repofacts_opts_disabled])) -+ -+ result = repositoriesblocklist.get_blocklisted_repoids(repomap_opts_only) -+ -+ assert not result -+ -+ -+def test_repositoriesblacklist_not_empty(monkeypatch, repofacts_opts_disabled, repomap_opts_only): -+ """ -+ Tests whether the correct set of blacklisted repoids is returned. -+ """ -+ -+ blacklisted_repoid = 'test' -+ monkeypatch.setattr(repositoriesblocklist, "_get_repoids_to_exclude", lambda dummy_mapping: {blacklisted_repoid}) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[repofacts_opts_disabled])) -+ monkeypatch.setattr(api, "produce", produce_mocked()) -+ monkeypatch.setattr(reporting, "create_report", produce_mocked()) -+ -+ result = repositoriesblocklist.get_blocklisted_repoids(repomap_opts_only) -+ assert blacklisted_repoid in result -+ assert reporting.create_report.called == 1 -+ -+ -+def test_repositoriesblacklist_empty(monkeypatch, repofacts_opts_disabled, repomap_opts_only): -+ """ -+ Tests whether an empty set is returned if there are some disabled optional -+ repos, but an empty blacklist is determined from the repo mapping data. -+ """ -+ -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[repofacts_opts_disabled])) -+ monkeypatch.setattr( -+ repositoriesblocklist, -+ "_get_repoids_to_exclude", -+ lambda dummy_mapping: set() -+ ) -+ -+ result = repositoriesblocklist.get_blocklisted_repoids(repomap_opts_only) -+ assert not result -+ -+ -+@pytest.mark.parametrize( -+ ("enabled_repo", "exp_report_title", "message_produced"), -+ [ -+ ("codeready-builder-for-rhel-9-x86_64-rpms", "Using repository not supported by Red Hat", False), -+ ("some_other_enabled_repo", "Excluded target system repositories", True), -+ (None, "Excluded target system repositories", True), -+ ], -+) -+def test_enablerepo_option(monkeypatch, -+ repofacts_opts_disabled, -+ repomap_opts_only, -+ enabled_repo, -+ exp_report_title, -+ message_produced): -+ """ -+ Tests whether the actor respects CustomTargetRepository messages when constructing the blacklist. -+ """ -+ -+ msgs_to_feed = [repofacts_opts_disabled] -+ -+ if enabled_repo: -+ msgs_to_feed.append(CustomTargetRepository(repoid=enabled_repo)) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs_to_feed)) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) -+ result = repositoriesblocklist.get_blocklisted_repoids(repomap_opts_only) -+ assert reporting.create_report.report_fields["title"] == exp_report_title -+ if message_produced: -+ assert result -+ else: -+ assert not result -+ -+ -+def _get_produced_blocklisted(): -+ return [m for m in api.produce.model_instances -+ if isinstance(m, RepositoriesBlocklisted) and not isinstance(m, RepositoriesBlacklisted)] -+ -+ -+def _get_produced_blacklisted(): -+ return [m for m in api.produce.model_instances if isinstance(m, RepositoriesBlacklisted)] -+ -+ -+@suppress_deprecation(RepositoriesBlacklisted) -+def test_compute_blocklist_merges_internal_and_external(monkeypatch): -+ """ -+ compute_blocklist merges internal blocklist with external -+ RepositoriesSetupTasks.to_block and produces both messages. -+ """ -+ external_tasks = [ -+ RepositoriesSetupTasks(to_enable=['some-repo'], to_block=['ext-blocked-1', 'ext-blocked-2']), -+ ] -+ monkeypatch.setattr( -+ repositoriesblocklist, 'get_blocklisted_repoids', lambda _repo_map: {'crb-repo-1'} -+ ) -+ monkeypatch.setattr(api, 'consume', lambda _model: iter(external_tasks)) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ full_blocklist, external_repoids = repositoriesblocklist.compute_blocklist(None) -+ -+ assert full_blocklist == {'crb-repo-1', 'ext-blocked-1', 'ext-blocked-2'} -+ assert external_repoids == {'some-repo'} -+ -+ blocklisted_msgs = _get_produced_blocklisted() -+ assert len(blocklisted_msgs) == 1 -+ assert set(blocklisted_msgs[0].repoids) == full_blocklist -+ -+ blacklisted_msgs = _get_produced_blacklisted() -+ assert len(blacklisted_msgs) == 1 -+ assert set(blacklisted_msgs[0].repoids) == full_blocklist -+ -+ -+def test_compute_blocklist_no_messages_when_empty(monkeypatch): -+ """No blocklist messages are produced when blocklist is empty.""" -+ monkeypatch.setattr( -+ repositoriesblocklist, 'get_blocklisted_repoids', lambda _repo_map: set() -+ ) -+ monkeypatch.setattr(api, 'consume', lambda _model: iter([])) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ full_blocklist, external_repoids = repositoriesblocklist.compute_blocklist(None) -+ -+ assert full_blocklist == set() -+ assert external_repoids == set() -+ assert len(_get_produced_blocklisted()) == 0 -+ assert len(_get_produced_blacklisted()) == 0 -+ -+ -+@suppress_deprecation(RepositoriesBlacklisted) -+def test_compute_blocklist_only_internal(monkeypatch): -+ """Only internal blocklist when no external tasks are present.""" -+ monkeypatch.setattr( -+ repositoriesblocklist, 'get_blocklisted_repoids', lambda _repo_map: {'crb-repo-1'} -+ ) -+ monkeypatch.setattr(api, 'consume', lambda _model: iter([])) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ full_blocklist, external_repoids = repositoriesblocklist.compute_blocklist(None) -+ -+ assert full_blocklist == {'crb-repo-1'} -+ assert external_repoids == set() -+ assert len(_get_produced_blocklisted()) == 1 -+ -+ -+def test_compute_blocklist_multiple_external_tasks(monkeypatch): -+ """Blocklist and to_enable from multiple external tasks are aggregated.""" -+ external_tasks = [ -+ RepositoriesSetupTasks(to_block=['ext-1'], to_enable=['repo-a']), -+ RepositoriesSetupTasks(to_block=['ext-2', 'ext-3'], to_enable=['repo-b']), -+ ] -+ monkeypatch.setattr( -+ repositoriesblocklist, 'get_blocklisted_repoids', lambda _repo_map: set() -+ ) -+ monkeypatch.setattr(api, 'consume', lambda _model: iter(external_tasks)) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ full_blocklist, external_repoids = repositoriesblocklist.compute_blocklist(None) -+ -+ assert full_blocklist == {'ext-1', 'ext-2', 'ext-3'} -+ assert external_repoids == {'repo-a', 'repo-b'} -+ -+ -+@suppress_deprecation(RepositoriesBlacklisted) -+def test_compute_blocklist_overlapping_internal_and_external(monkeypatch): -+ """Overlapping repoids between internal and external blocklists are deduplicated.""" -+ external_tasks = [ -+ RepositoriesSetupTasks(to_block=['shared-repo', 'ext-only']), -+ ] -+ monkeypatch.setattr( -+ repositoriesblocklist, 'get_blocklisted_repoids', lambda _repo_map: {'shared-repo', 'internal-only'} -+ ) -+ monkeypatch.setattr(api, 'consume', lambda _model: iter(external_tasks)) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ full_blocklist, external_repoids = repositoriesblocklist.compute_blocklist(None) -+ -+ assert full_blocklist == {'shared-repo', 'internal-only', 'ext-only'} -+ assert external_repoids == set() -+ -+ blocklisted_msgs = _get_produced_blocklisted() -+ assert len(blocklisted_msgs) == 1 -+ assert set(blocklisted_msgs[0].repoids) == full_blocklist -+ -+ blacklisted_msgs = _get_produced_blacklisted() -+ assert len(blacklisted_msgs) == 1 -+ assert set(blacklisted_msgs[0].repoids) == full_blocklist -diff --git a/repos/system_upgrade/common/actors/setuptargetrepos/tests/test_setuptargetrepos.py b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_setuptargetrepos.py -similarity index 74% -rename from repos/system_upgrade/common/actors/setuptargetrepos/tests/test_setuptargetrepos.py -rename to repos/system_upgrade/common/actors/targetcontentresolver/tests/test_setuptargetrepos.py -index c3ff5f49..652bf30a 100644 ---- a/repos/system_upgrade/common/actors/setuptargetrepos/tests/test_setuptargetrepos.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_setuptargetrepos.py -@@ -8,10 +8,8 @@ from leapp.models import ( - InstalledRPM, - PESIDRepositoryEntry, - RepoMapEntry, -- RepositoriesBlacklisted, - RepositoriesFacts, - RepositoriesMapping, -- RepositoriesSetupTasks, - RepositoryData, - RepositoryFile, - RPM -@@ -36,7 +34,7 @@ def test_minimal_execution(monkeypatch): - monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) - monkeypatch.setattr(api, 'produce', produce_mocked()) - -- setuptargetrepos.process() -+ setuptargetrepos.setup_target_repos(msgs[0]) - - - def test_custom_repos(monkeypatch): -@@ -54,19 +52,17 @@ def test_custom_repos(monkeypatch): - baseurl='https://.../dist/rhel/blacklisted/8/os', - enabled=True) - -- repos_blacklisted = RepositoriesBlacklisted(repoids=['rhel-8-blacklisted-rpms']) -- - repositories_mapping = RepositoriesMapping( - mapping=[], - repositories=[] - ) - -- msgs = [custom, blacklisted, repos_blacklisted, repositories_mapping] -+ msgs = [custom, blacklisted, repositories_mapping] - - monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) - monkeypatch.setattr(api, 'produce', produce_mocked()) - -- setuptargetrepos.process() -+ setuptargetrepos.setup_target_repos(repositories_mapping, blacklisted_repoids={'rhel-8-blacklisted-rpms'}) - - assert api.produce.called - -@@ -77,19 +73,19 @@ def test_custom_repos(monkeypatch): - - def test_repositories_setup_tasks(monkeypatch): - """ -- Tests whether the actor propagates repositories received via a RepositoriesSetupTasks message -+ Tests whether the actor propagates requested repoids - to the resulting TargetRepositories (and blacklist filtering is applied to them). - """ -- repositories_setup_tasks = RepositoriesSetupTasks(to_enable=['rhel-8-server-rpms', -- 'rhel-8-blacklisted-rpms']) -- repos_blacklisted = RepositoriesBlacklisted(repoids=['rhel-8-blacklisted-rpms']) - repositories_mapping = RepositoriesMapping(mapping=[], repositories=[]) -- msgs = [repositories_setup_tasks, repos_blacklisted, repositories_mapping] -+ msgs = [repositories_mapping] - - monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) - monkeypatch.setattr(api, 'produce', produce_mocked()) - -- setuptargetrepos.process() -+ setuptargetrepos.setup_target_repos( -+ repositories_mapping, -+ blacklisted_repoids={'rhel-8-blacklisted-rpms'}, -+ external_repoids_requests={'rhel-8-server-rpms', 'rhel-8-blacklisted-rpms'}) - - assert api.produce.called - -@@ -187,9 +183,7 @@ def test_repos_mapping_for_distro(monkeypatch, src_distro, dst_distro): - ] - ) - -- repos_blacklisted = RepositoriesBlacklisted(repoids=['{}-9-blacklisted-rpms'.format(dst_distro)]) -- -- msgs = [facts, repomap, repos_blacklisted, installed_rpms] -+ msgs = [facts, repomap, installed_rpms] - - monkeypatch.setattr( - api, -@@ -198,7 +192,7 @@ def test_repos_mapping_for_distro(monkeypatch, src_distro, dst_distro): - ) - monkeypatch.setattr(api, 'produce', produce_mocked()) - -- setuptargetrepos.process() -+ setuptargetrepos.setup_target_repos(repomap, blacklisted_repoids={'{}-9-blacklisted-rpms'.format(dst_distro)}) - assert api.produce.called - - distro_repos = api.produce.model_instances[0].distro_repos -@@ -221,3 +215,58 @@ def test_repos_mapping_for_distro(monkeypatch, src_distro, dst_distro): - assert produced_rhel_repoids == expected_repoids - else: - assert not rhel_repos -+ -+ -+def test_pes_requested_repoids_added_to_target(monkeypatch): -+ """ -+ Tests whether PES-requested repoids are added to the target repositories -+ and that blacklisted PES-requested repoids are excluded. -+ """ -+ repositories_mapping = RepositoriesMapping(mapping=[], repositories=[]) -+ msgs = [repositories_mapping] -+ -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ setuptargetrepos.setup_target_repos( -+ repositories_mapping, -+ pes_requested_repoids={'pes-repo-1', 'pes-repo-2', 'pes-blocked'}, -+ blacklisted_repoids={'pes-blocked'}, -+ ) -+ -+ assert api.produce.called -+ -+ distro_repos = api.produce.model_instances[0].distro_repos -+ produced_repoids = {repo.repoid for repo in distro_repos} -+ -+ assert 'pes-repo-1' in produced_repoids -+ assert 'pes-repo-2' in produced_repoids -+ assert 'pes-blocked' not in produced_repoids -+ -+ -+def test_pes_and_external_repoids_combined(monkeypatch): -+ """ -+ Tests whether PES-requested and external repoids are combined -+ in the final target repositories, with blacklist filtering applied to both. -+ """ -+ repositories_mapping = RepositoriesMapping(mapping=[], repositories=[]) -+ msgs = [repositories_mapping] -+ -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ setuptargetrepos.setup_target_repos( -+ repositories_mapping, -+ pes_requested_repoids={'pes-repo'}, -+ blacklisted_repoids={'blocked-repo'}, -+ external_repoids_requests={'ext-repo', 'blocked-repo'}, -+ ) -+ -+ assert api.produce.called -+ -+ distro_repos = api.produce.model_instances[0].distro_repos -+ produced_repoids = {repo.repoid for repo in distro_repos} -+ -+ assert 'pes-repo' in produced_repoids -+ assert 'ext-repo' in produced_repoids -+ assert 'blocked-repo' not in produced_repoids -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py -new file mode 100644 -index 00000000..4e425da6 ---- /dev/null -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py -@@ -0,0 +1,63 @@ -+from leapp.libraries.actor import targetcontentresolver -+ -+ -+def test_process_orchestration(monkeypatch): -+ """ -+ Tests that process() wires data between the four stages correctly: -+ scan_repositories -> compute_blocklist -> scan_pes_events -> setup_target_repos. -+ """ -+ call_log = [] -+ -+ fake_repo_map = object() -+ fake_blocklist = frozenset({'blocked-repo'}) -+ fake_external_repoids = frozenset({'ext-repo'}) -+ fake_pes_repoids = frozenset({'pes-repo'}) -+ -+ def mock_scan_repositories(): -+ call_log.append('scan_repositories') -+ return fake_repo_map -+ -+ def mock_compute_blocklist(repo_mapping): -+ call_log.append('compute_blocklist') -+ assert repo_mapping is fake_repo_map -+ return fake_blocklist, fake_external_repoids -+ -+ def mock_scan_pes_events(repo_mapping, blacklisted_repoids): -+ call_log.append('scan_pes_events') -+ assert repo_mapping is fake_repo_map -+ assert blacklisted_repoids is fake_blocklist -+ return fake_pes_repoids -+ -+ def mock_setup_target_repos(repo_mapping, pes_requested_repoids=None, -+ blacklisted_repoids=None, external_repoids_requests=None): -+ call_log.append('setup_target_repos') -+ assert repo_mapping is fake_repo_map -+ assert pes_requested_repoids is fake_pes_repoids -+ assert blacklisted_repoids is fake_blocklist -+ assert external_repoids_requests is fake_external_repoids -+ -+ monkeypatch.setattr( -+ 'leapp.libraries.actor.targetcontentresolver.scan_repositories', -+ mock_scan_repositories, -+ ) -+ monkeypatch.setattr( -+ 'leapp.libraries.actor.targetcontentresolver.repositoriesblocklist.compute_blocklist', -+ mock_compute_blocklist, -+ ) -+ monkeypatch.setattr( -+ 'leapp.libraries.actor.targetcontentresolver.pes_events_scanner.scan_pes_events', -+ mock_scan_pes_events, -+ ) -+ monkeypatch.setattr( -+ 'leapp.libraries.actor.targetcontentresolver.setuptargetrepos.setup_target_repos', -+ mock_setup_target_repos, -+ ) -+ -+ targetcontentresolver.process() -+ -+ assert call_log == [ -+ 'scan_repositories', -+ 'compute_blocklist', -+ 'scan_pes_events', -+ 'setup_target_repos', -+ ] -diff --git a/repos/system_upgrade/common/actors/repositoriesmapping/tests/unit_test_repositoriesmapping.py b/repos/system_upgrade/common/actors/targetcontentresolver/tests/unit_test_repomap_loader.py -similarity index 88% -rename from repos/system_upgrade/common/actors/repositoriesmapping/tests/unit_test_repositoriesmapping.py -rename to repos/system_upgrade/common/actors/targetcontentresolver/tests/unit_test_repomap_loader.py -index 6d1a173a..50cf5962 100644 ---- a/repos/system_upgrade/common/actors/repositoriesmapping/tests/unit_test_repositoriesmapping.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/tests/unit_test_repomap_loader.py -@@ -5,7 +5,7 @@ import pytest - import requests - - from leapp.exceptions import StopActorExecutionError --from leapp.libraries.actor import repositoriesmapping -+from leapp.libraries.actor import repomap_loader - from leapp.libraries.common import fetch - from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked - from leapp.libraries.stdlib import api -@@ -32,9 +32,13 @@ def test_scan_existing_valid_data(monkeypatch, adjust_cwd): - monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(src_ver='7.9', dst_ver='8.4')) - monkeypatch.setattr(api, 'produce', produce_mocked()) - -- repositoriesmapping.scan_repositories(lambda dummy: data) -+ result = repomap_loader.scan_repositories(lambda dummy: data) - - assert api.produce.called, 'Actor did not produce any message when deserializing valid repomap data.' -+ assert result is not None, 'scan_repositories should return the RepositoriesMapping message.' -+ assert result is api.produce.model_instances[0], ( -+ 'The returned mapping should be the same object that was produced.' -+ ) - - fail_description = 'Actor produced multiple messages, but only one was expected.' - assert len(api.produce.model_instances) == 1, fail_description -@@ -140,7 +144,7 @@ def test_scan_repositories_with_missing_data(monkeypatch): - monkeypatch.setattr(fetch, 'read_or_fetch', read_or_fetch_mocked) - - with pytest.raises(StopActorExecutionError) as missing_data_error: -- repositoriesmapping.scan_repositories() -+ repomap_loader.scan_repositories() - assert 'does not contain a valid JSON object' in str(missing_data_error) - - -@@ -153,7 +157,7 @@ def test_scan_repositories_with_empty_data(monkeypatch): - monkeypatch.setattr(api, 'produce', produce_mocked()) - - with pytest.raises(StopActorExecutionError) as empty_data_error: -- repositoriesmapping.scan_repositories(lambda dummy: {}) -+ repomap_loader.scan_repositories(lambda dummy: {}) - assert 'the JSON is missing a required field' in str(empty_data_error) - - -@@ -175,7 +179,7 @@ def test_scan_repositories_with_bad_json_data_version(monkeypatch, version_forma - monkeypatch.setattr(api, 'produce', produce_mocked()) - - with pytest.raises(StopActorExecutionError) as bad_version_error: -- repositoriesmapping.scan_repositories(lambda dummy: json_data) -+ repomap_loader.scan_repositories(lambda dummy: json_data) - - assert 'mapping file is invalid' in str(bad_version_error) - -@@ -187,7 +191,7 @@ def test_scan_repositories_with_mapping_to_pesid_without_repos(monkeypatch): - """ - json_data = { - 'datetime': '202107141655Z', -- 'version_format': repositoriesmapping.RepoMapData.VERSION_FORMAT, -+ 'version_format': repomap_loader.RepoMapData.VERSION_FORMAT, - 'mapping': [ - { - 'source_major_version': '7', -@@ -221,7 +225,7 @@ def test_scan_repositories_with_mapping_to_pesid_without_repos(monkeypatch): - monkeypatch.setattr(api, 'produce', produce_mocked()) - - with pytest.raises(StopActorExecutionError) as error_info: -- repositoriesmapping.scan_repositories(lambda dummy: json_data) -+ repomap_loader.scan_repositories(lambda dummy: json_data) - - assert 'pesid is not related to any repository' in error_info.value.message - -@@ -234,7 +238,7 @@ def test_scan_repositories_with_repo_entry_missing_required_fields(monkeypatch): - - json_data = { - 'datetime': '202107141655Z', -- 'version_format': repositoriesmapping.RepoMapData.VERSION_FORMAT, -+ 'version_format': repomap_loader.RepoMapData.VERSION_FORMAT, - 'mapping': [ - { - 'source_major_version': '7', -@@ -275,7 +279,7 @@ def test_scan_repositories_with_repo_entry_missing_required_fields(monkeypatch): - monkeypatch.setattr(api, 'produce', produce_mocked()) - - with pytest.raises(StopActorExecutionError) as error_info: -- repositoriesmapping.scan_repositories(lambda dummy: json_data) -+ repomap_loader.scan_repositories(lambda dummy: json_data) - - assert 'the JSON is missing a required field' in error_info.value.message - -@@ -288,7 +292,7 @@ def test_scan_repositories_with_repo_entry_mapping_target_not_a_list(monkeypatch - - json_data = { - 'datetime': '202107141655Z', -- 'version_format': repositoriesmapping.RepoMapData.VERSION_FORMAT, -+ 'version_format': repomap_loader.RepoMapData.VERSION_FORMAT, - 'mapping': [ - { - 'source_major_version': '7', -@@ -329,6 +333,18 @@ def test_scan_repositories_with_repo_entry_mapping_target_not_a_list(monkeypatch - monkeypatch.setattr(api, 'produce', produce_mocked()) - - with pytest.raises(StopActorExecutionError) as error_info: -- repositoriesmapping.scan_repositories(lambda dummy: json_data) -+ repomap_loader.scan_repositories(lambda dummy: json_data) - - assert 'repository mapping file is invalid' in error_info.value.message -+ -+ -+def test_scan_repositories_returns_none_on_error(monkeypatch): -+ """ -+ Tests that scan_repositories returns None when the data is invalid -+ and a StopActorExecutionError is raised. -+ """ -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(src_ver='7.9', dst_ver='8.4')) -+ monkeypatch.setattr(api, 'produce', produce_mocked()) -+ -+ with pytest.raises(StopActorExecutionError): -+ repomap_loader.scan_repositories(lambda dummy: {}) -diff --git a/repos/system_upgrade/common/models/repositoriesblacklisted.py b/repos/system_upgrade/common/models/repositoriesblacklisted.py -deleted file mode 100644 -index 056eaff4..00000000 ---- a/repos/system_upgrade/common/models/repositoriesblacklisted.py -+++ /dev/null -@@ -1,11 +0,0 @@ --from leapp.models import fields, Model --from leapp.topics import SystemFactsTopic -- -- --class RepositoriesBlacklisted(Model): -- """ -- Repositories ID that should be ignored by Leapp during upgrade process -- """ -- topic = SystemFactsTopic -- -- repoids = fields.List(fields.String(), default=[]) -diff --git a/repos/system_upgrade/common/models/repositoriesblocklisted.py b/repos/system_upgrade/common/models/repositoriesblocklisted.py -new file mode 100644 -index 00000000..c1825ec3 ---- /dev/null -+++ b/repos/system_upgrade/common/models/repositoriesblocklisted.py -@@ -0,0 +1,34 @@ -+from leapp.models import fields, Model -+from leapp.topics import SystemFactsTopic -+from leapp.utils.deprecation import deprecated -+ -+ -+class RepositoriesBlocklisted(Model): -+ """ -+ Repository IDs that have been excluded from the target system during the upgrade. -+ -+ This message is expected to be produced just by one actor, -+ but it can be consumed by others to check what repositories have been ignored -+ during the upgrade process. -+ """ -+ topic = SystemFactsTopic -+ -+ repoids = fields.List(fields.String(), default=[]) -+ """ -+ List of excluded (blocked) repository IDs. -+ """ -+ -+ -+@deprecated( -+ since='2026-06-01', -+ message=( -+ 'This model has been deprecated and replaced. ' -+ 'To get the list of blocklisted repositories, use RepositoriesBlocklisted. ' -+ 'To request a repository to be blocklisted, use RepositoriesSetupTasks.blocklist.' -+ ), -+) -+class RepositoriesBlacklisted(RepositoriesBlocklisted): -+ """ -+ Repository IDs that should be ignored by Leapp during the upgrade process. -+ """ -+ topic = SystemFactsTopic -diff --git a/repos/system_upgrade/common/models/repositoriessetuptasks.py b/repos/system_upgrade/common/models/repositoriessetuptasks.py -index fb267ad0..35f627e4 100644 ---- a/repos/system_upgrade/common/models/repositoriessetuptasks.py -+++ b/repos/system_upgrade/common/models/repositoriessetuptasks.py -@@ -6,10 +6,23 @@ class RepositoriesSetupTasks(Model): - """ - Information about repositories that must be managed in order to complete upgrade process. - -- 'to_enable' field consists of a list of repositories that should be enabled in order to complete -+ * 'to_enable' field consists of a list of repositories that should be enabled in order to complete - upgrade process. This information should be processed by an actor dedicated to manage - repositories. -+ * 'to_block' field consists of a list of repositories that should be ignored during upgrade process. - """ - topic = SystemFactsTopic - - to_enable = fields.List(fields.String(), default=[]) -+ """ -+ List of repositories that should be enabled and used during the upgrade. -+ """ -+ -+ to_block = fields.List(fields.String(), default=[]) -+ """ -+ List of repositories that should be ignored (blocked) during the upgrade. -+ """ -+ # TODO(pstodulk): update the docstring: -+ # * what has precedence? to_enable or to_block? -+ # * note the effect of custom repositories! -+ # The code needs to be updated to correctly answer these Q at this moment. --- -2.54.0 - diff --git a/0098-2-9-targetcontentresolver-Init-message-consumption-c.patch b/0098-2-9-targetcontentresolver-Init-message-consumption-c.patch deleted file mode 100644 index 28c032a..0000000 --- a/0098-2-9-targetcontentresolver-Init-message-consumption-c.patch +++ /dev/null @@ -1,752 +0,0 @@ -From 4ff499df1e88ef3a7af57aca5ac58a648ddd4707 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Thu, 4 Jun 2026 07:20:14 +0200 -Subject: [PATCH 098/105] [2/9] targetcontentresolver: Init message consumption - centralization - -Centralize consumption of RepositoriesFacts, RepositoriesSetupTasks, -and CustomTargetRepository into a new InputData class in -targetcontentresolver. This deduplicates logic previously scattered -across repositoriesblocklist and pes_events_scanner, and validates -that required messages are present before processing begins. - -This is initial set of changes for future refactoring in individual -libraries of the actor. - -Some details about individual changes: -* setuptargetrepos: - * Drop the rename of get_enabled_repoids to a private functions - when importing. - -* targetcontentresolver: - * Introduce InputData class that consumes and validates required - messages, warns about unexpected duplicates, and raises - StopActorExecutionError when required messages are missing. - * Introduce ExternalRepoSetupTasks named tuple to collect all - external repository requests (to_enable, to_block, custom) - from RepositoriesSetupTasks and CustomTargetRepository messages. - -* pes_events_scanner: - * Pass enabled_repoids as parameter through scan_pes_events, - get_pesid_to_repoid_map, and replace_pesids_with_repoids_in_packages - instead of fetching it internally via get_enabled_repoids(). - * Drop default parameter for blacklisted_repoids in scan_pes_events - (must be always specified). - -* repositoriesblocklist.compute_blocklist: - * Accept external_tasks (ExternalRepoSetupTasks) instead of consuming - RepositoriesSetupTasks directly. - * Return only the set of blocklisted repoids instead of a tuple of - (blocklisted, enabled). - * Move RepositoriesFacts missing-message check to InputData. - * Update docstring to describe expected precedence order for - conflicting requests (implementation deferred to next commit). - * Remove misused FAILURE group from the CRB exclusion report. - -Unit tests adjusted with assistance of AI. - -Jira: RHEL-115867 -Assisted-by: Claude Code (Opus 4.6) -Co-authored-by: Matej Matuska -Signed-off-by: Petr Stodulka ---- - .../libraries/pes_event_parsing.py | 1 + - .../libraries/pes_events_scanner.py | 18 +-- - .../libraries/repositoriesblocklist.py | 83 +++++++------ - .../libraries/setuptargetrepos.py | 4 +- - .../libraries/targetcontentresolver.py | 93 ++++++++++++-- - .../tests/test_pes_event_scanner.py | 12 +- - .../tests/test_repositoriesblocklist.py | 49 +++----- - .../tests/test_targetcontentresolver.py | 114 +++++++++++++++++- - 8 files changed, 279 insertions(+), 95 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/pes_event_parsing.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/pes_event_parsing.py -index f24dda68..febf8578 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/pes_event_parsing.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/pes_event_parsing.py -@@ -109,6 +109,7 @@ def get_pes_events(pes_json_directory, pes_json_filename): - reporting.Groups([reporting.Groups.SANITY, reporting.Groups.INHIBITOR]), - reporting.RelatedResource('file', os.path.join(pes_json_directory, pes_json_filename)) - ]) -+ - raise StopActorExecution() - - -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/pes_events_scanner.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/pes_events_scanner.py -index a03071ae..cbc99adf 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/pes_events_scanner.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/pes_events_scanner.py -@@ -320,7 +320,7 @@ def report_skipped_packages(title, message, skipped_pkgs, remediation=None): - api.current_logger().info(summary) - - --def remove_new_packages_from_blacklisted_repos(source_pkgs, target_pkgs, blacklisted_repoids=None): -+def remove_new_packages_from_blacklisted_repos(source_pkgs, target_pkgs, blacklisted_repoids): - """ - Remove newly installed packages from blacklisted repositories that were computed to be on the target system. - -@@ -357,7 +357,7 @@ def get_enabled_repoids(): - return enabled_repoids - - --def get_pesid_to_repoid_map(target_pesids, repositories_map_msg): -+def get_pesid_to_repoid_map(target_pesids, repositories_map_msg, enabled_repoids): - """ - Get a dictionary mapping all PESID repositories to their corresponding repoid. - -@@ -373,7 +373,6 @@ def get_pesid_to_repoid_map(target_pesids, repositories_map_msg): - # NOTE: We have to calculate expected target repositories like in the setuptargetrepos actor. - # It's planned to handle this in different a way in future... - -- enabled_repoids = get_enabled_repoids() - default_channels = repomap_calc.get_default_repository_channels(repomap_handler, enabled_repoids) - repomap_handler.set_default_channels(default_channels) - -@@ -433,7 +432,7 @@ def get_pesid_to_repoid_map(target_pesids, repositories_map_msg): - return repositories_mapping - - --def replace_pesids_with_repoids_in_packages(packages, source_pkgs_repoids, repositories_map_msg): -+def replace_pesids_with_repoids_in_packages(packages, source_pkgs_repoids, repositories_map_msg, enabled_repoids): - """Replace packages with PESID in their .repository field with ones that have repoid providing the package.""" - # We want to map only PESIDs - if some package had no events, it will its repository set to source system repoid - packages_with_pesid = {pkg for pkg in packages if pkg.repository not in source_pkgs_repoids} -@@ -441,7 +440,7 @@ def replace_pesids_with_repoids_in_packages(packages, source_pkgs_repoids, repos - - required_target_pesids = {pkg.repository for pkg in packages_with_pesid} - -- pesid_to_target_repoid_map = get_pesid_to_repoid_map(required_target_pesids, repositories_map_msg) -+ pesid_to_target_repoid_map = get_pesid_to_repoid_map(required_target_pesids, repositories_map_msg, enabled_repoids) - - packages_with_unknown_target_repoid = { - pkg -@@ -555,7 +554,7 @@ def include_instructions_from_transaction_configuration(rpm_tasks, transaction_c - modules_to_reset=modules_to_reset) - - --def scan_pes_events(repositories_map_msg, blacklisted_repoids=None): -+def scan_pes_events(repositories_map_msg, blacklisted_repoids, enabled_repoids): - """ - Process PES events and compute RPM transaction tasks. - -@@ -586,7 +585,12 @@ def scan_pes_events(repositories_map_msg, blacklisted_repoids=None): - events, releases) - - # Packages coming out of the events have PESID as their repository, however, we need real repoid -- target_pkgs = replace_pesids_with_repoids_in_packages(target_pkgs, repoids_of_source_pkgs, repositories_map_msg) -+ target_pkgs = replace_pesids_with_repoids_in_packages( -+ target_pkgs, -+ repoids_of_source_pkgs, -+ repositories_map_msg, -+ enabled_repoids -+ ) - - # Apply the desired repository blacklisting - blacklisted_repoids, target_pkgs = remove_new_packages_from_blacklisted_repos(pkgs_to_begin_computation_with, -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repositoriesblocklist.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repositoriesblocklist.py -index 48656187..216bea52 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repositoriesblocklist.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repositoriesblocklist.py -@@ -1,14 +1,7 @@ - from leapp import reporting --from leapp.exceptions import StopActorExecutionError - from leapp.libraries.common.config.version import get_source_major_version, get_target_major_version - from leapp.libraries.stdlib import api --from leapp.models import ( -- CustomTargetRepository, -- RepositoriesBlacklisted, -- RepositoriesBlocklisted, -- RepositoriesFacts, -- RepositoriesSetupTasks --) -+from leapp.models import CustomTargetRepository, RepositoriesBlacklisted, RepositoriesBlocklisted, RepositoriesFacts - from leapp.utils.deprecation import suppress_deprecation - - # {OS_MAJOR_VERSION: PESID} -@@ -47,7 +40,6 @@ def _report_excluded_repos(repos): - ), - reporting.Severity(reporting.Severity.INFO), - reporting.Groups([reporting.Groups.REPOSITORY]), -- reporting.Groups([reporting.Groups.FAILURE]), - reporting.Remediation( - hint=( - 'If some of excluded repositories are still required to be used' -@@ -136,7 +128,7 @@ def get_blocklisted_repoids(repo_mapping): - - Conditions to exclude: - - there are not such repositories already enabled on the source system -- (e.g. "Optional" repositories) -+ (e.g. "CRB" repositories) - - such repositories are not required for the upgrade explicitly by the user - (e.g. via the --enablerepo option or via the /etc/leapp/files/leapp_upgrade_repositories.repo file) - -@@ -146,13 +138,9 @@ def get_blocklisted_repoids(repo_mapping): - :returns: Set of repoids to blocklist on the target system. - :rtype: set - """ -+ # TODO(pstodulk): replace it by enabled_repos - repos_facts = next(api.consume(RepositoriesFacts), None) - -- if not repos_facts: -- raise StopActorExecutionError('Actor didn\'t receive required messages: {0}'.format( -- 'RepositoriesFacts' -- )) -- - if not _are_optional_repos_disabled(repo_mapping, repos_facts): - # nothing to do - an optional repository is enabled - return set() -@@ -172,34 +160,51 @@ def get_blocklisted_repoids(repo_mapping): - - - @suppress_deprecation(RepositoriesBlacklisted) --def compute_blocklist(repo_mapping): -+def compute_blocklist(repo_mapping, external_tasks): - """ -- Compute the full blocklist and extract external repoid requests. -+ Create the blocklist of repositories that should be blocked during the upgrade. -+ -+ The content in the CRB repository is considered as unsupported. Hence -+ we do not want to enable such a repository by default on the target system -+ unless -+ * it is explicitely requested, or -+ * it is already enabled on the source system -+ This is important as some functionality originally supported on the source -+ source system can be moved to the CRB repository on the target system. -+ In such a case, we do not want to install automatically such a content -+ that lost a support. -+ -+ For this reason, compute the list of repositories that should be blocklisted -+ and take into account also external requests. -+ In case that multiple requests are raised for a specific repository, the -+ priority order is following: -+ internal block rq > external enable rq > external block rq > external custom rq -+ -+ The external custom request has the highest priority as this comes from -+ the system configuration or from the `--enablerepo` option used when executing -+ leapp - so it's understood as decision made by user explicitely for -+ the in-place upgrade purpose. Currently there is no explicit custom -+ configuration to disable a repo. -+ -+ Once the list is calculated, the RepositoriesBlocklisted and -+ RepositoriesBlacklisted messages are produced. - -- Merges the internally determined blocklist (unsupported repos like CRB) -- with external blocklist requests from ``RepositoriesSetupTasks.to_block``. -- When the resulting blocklist is non-empty, produces both -- ``RepositoriesBlocklisted`` and the deprecated ``RepositoriesBlacklisted`` -- messages. -+ :param repo_mapping: RepositoriesMapping message. -+ :type repo_mapping: RepositoriesMapping -+ :param external_tasks: External repositories tasks represented by object with following fields: - -- Also extracts ``to_enable`` repoids from the same external tasks so that -- the caller does not need to re-consume the messages. -+ * ``to_enable`` - repositories that should be enabled -+ * ``to_block`` - repositories that should be blocklisted -+ * ``custom`` - repositories explicitely requested by user to be used during the upgrade - -- :param repo_mapping: RepositoriesMapping message. -- :returns: Tuple of (full_blocklist_repoids, external_repoids_requests). -- :rtype: tuple[set, set] -+ :type external_tasks: targetcontentresolver.ExternalRepoSetupTasks -+ :returns: List of blocklisted repositories -+ :rtype: List[str] - """ - internal_blocklist = get_blocklisted_repoids(repo_mapping) -+ full_blocklist = internal_blocklist.union(external_tasks.to_block) -+ if full_blocklist: -+ api.produce(RepositoriesBlocklisted(repoids=sorted(full_blocklist))) -+ api.produce(RepositoriesBlacklisted(repoids=sorted(full_blocklist))) - -- external_blocklist_repoids = set() -- external_repoids_requests = set() -- for task in api.consume(RepositoriesSetupTasks): -- external_blocklist_repoids.update(task.to_block) -- external_repoids_requests.update(task.to_enable) -- -- full_blocklist_repoids = internal_blocklist.union(external_blocklist_repoids) -- if full_blocklist_repoids: -- api.produce(RepositoriesBlocklisted(repoids=sorted(full_blocklist_repoids))) -- api.produce(RepositoriesBlacklisted(repoids=sorted(full_blocklist_repoids))) -- -- return full_blocklist_repoids, external_repoids_requests -+ return full_blocklist -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/setuptargetrepos.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/setuptargetrepos.py -index b9e5a1c4..d6bfb91d 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/setuptargetrepos.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/setuptargetrepos.py -@@ -1,5 +1,5 @@ - from leapp.libraries.actor import repomap_calc --from leapp.libraries.actor.pes_events_scanner import get_enabled_repoids as _get_enabled_repoids -+from leapp.libraries.actor.pes_events_scanner import get_enabled_repoids - from leapp.libraries.common.config import get_source_distro_id, get_target_distro_id - from leapp.libraries.common.config.version import get_source_major_version, get_source_version - from leapp.libraries.stdlib import api -@@ -70,7 +70,7 @@ def setup_target_repos(repositories_map_msg, pes_requested_repoids=None, - """ - # Load relevant data from messages - used_repoids_dict = _get_used_repo_dict() -- enabled_repoids = _get_enabled_repoids() -+ enabled_repoids = get_enabled_repoids() - excluded_repoids = blacklisted_repoids if blacklisted_repoids is not None else set() - custom_repos = _get_custom_target_repos() - repoids_from_installed_packages = _get_repoids_from_installed_packages() -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -index 4db9003d..c1b46916 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -@@ -1,5 +1,83 @@ -+from collections import namedtuple -+ -+from leapp.exceptions import StopActorExecutionError - from leapp.libraries.actor import pes_events_scanner, repositoriesblocklist, setuptargetrepos - from leapp.libraries.actor.repomap_loader import scan_repositories -+from leapp.libraries.stdlib import api -+from leapp.models import CustomTargetRepository, RepositoriesFacts, RepositoriesSetupTasks -+ -+ExternalRepoSetupTasks = namedtuple('ExternalRepoSetupTasks', ('to_enable', 'to_block', 'custom')) -+ -+ -+class InputData(): -+ """ -+ Provide data from consumed messages -+ """ -+ -+ def __init__(self): -+ self._missing_messages = [] -+ -+ self._get_enabled_repoids() -+ self._get_external_reposetup_tasks() -+ -+ if self._missing_messages: -+ # NOTE(pstodulk): This would be an invalid object, -+ # so let's end the story here. -+ raise StopActorExecutionError( -+ message='Cannot calculate target content requirements', -+ details={ -+ 'details': 'Some of required leapp messages are missing.', -+ 'missing': [i.__name__ for i in self._missing_messages] -+ } -+ ) -+ -+ def _treat_consume_msg(self, model): -+ msgs = api.consume(model) -+ msg = next(msgs, None) -+ if list(msgs): -+ w_msg = f'Unexpectedly received more than one {model.__name__} message.' -+ api.current_logger().warning(w_msg) -+ if not msg: -+ self._missing_messages.append(model) -+ return None -+ return msg -+ -+ def _get_external_reposetup_tasks(self): -+ """ -+ Collect repository related tasks from other actors (or configs in future). -+ -+ This includes consumption of RepositoriesSetupTasks and CustomTargetRepository. -+ The second one should be understood as task as well based on the definition, -+ even when the name does not suggest it's actually task as well. -+ -+ Return named tuple with `to_enable`, `to_block`, and `custom` fields. -+ The last one is based on reposids in CustomTargetRepository messages. -+ """ -+ self.external_tasks = ExternalRepoSetupTasks(set(), set(), set()) -+ for task in api.consume(RepositoriesSetupTasks): -+ self.external_tasks.to_block.update(task.to_block) -+ self.external_tasks.to_enable.update(task.to_enable) -+ -+ self.external_tasks.custom.update({repo.repoid for repo in api.consume(CustomTargetRepository)}) -+ -+ def _get_enabled_repoids(self): -+ """ -+ Return set of repository IDs enabled on the source system. -+ -+ The information is consumed from the RepositoriesFacts message. -+ -+ :return: Set of all enabled repository IDs present on the source system. -+ :rtype: Set[str] -+ """ -+ self.enabled_repoids = set() -+ repos_facts = self._treat_consume_msg(RepositoriesFacts) -+ if repos_facts is None: -+ return -+ -+ for repo_file in repos_facts.repositories: -+ for repo in repo_file.data: -+ if repo.enabled: -+ self.enabled_repoids.add(repo.repoid) - - - def process(): -@@ -23,17 +101,18 @@ def process(): - source repos, installed package repos, and custom/blacklisted repo - configuration. - """ -+ indata = InputData() - repositories_map_msg = scan_repositories() -- -- full_blocklist_repoids, external_repoids_requests = ( -- repositoriesblocklist.compute_blocklist(repositories_map_msg) -+ blocklisted_repoids = repositoriesblocklist.compute_blocklist(repositories_map_msg, indata.external_tasks) -+ pes_requested_repoids = pes_events_scanner.scan_pes_events( -+ repositories_map_msg, -+ blocklisted_repoids, -+ indata.enabled_repoids - ) - -- pes_requested_repoids = pes_events_scanner.scan_pes_events(repositories_map_msg, full_blocklist_repoids) -- - setuptargetrepos.setup_target_repos( - repositories_map_msg, - pes_requested_repoids=pes_requested_repoids, -- blacklisted_repoids=full_blocklist_repoids, -- external_repoids_requests=external_repoids_requests, -+ blacklisted_repoids=blocklisted_repoids, -+ external_repoids_requests=indata.external_tasks.to_enable, - ) -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_pes_event_scanner.py b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_pes_event_scanner.py -index f872a947..a1f3a6e1 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_pes_event_scanner.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_pes_event_scanner.py -@@ -270,7 +270,7 @@ def test_actor_performs(monkeypatch): - monkeypatch.setattr(api, 'produce', produced_messages) - monkeypatch.setattr(reporting, 'create_report', created_report) - -- pes_events_scanner.scan_pes_events(repositories_mapping) -+ pes_events_scanner.scan_pes_events(repositories_mapping, set(), {'rhel8-repo'}) - - assert produced_messages.called - -@@ -341,13 +341,13 @@ def test_blacklisted_repoid_is_not_produced(monkeypatch): - monkeypatch.setattr(pes_events_scanner, 'get_pes_events', lambda folder, filename: events) - monkeypatch.setattr(pes_events_scanner, 'apply_transaction_configuration', lambda pkgs, transaction_cfg: pkgs) - monkeypatch.setattr(pes_events_scanner, 'replace_pesids_with_repoids_in_packages', -- lambda pkgs, src_pkgs_repoids, repo_map_msg=None: pkgs) -+ lambda pkgs, src_pkgs_repoids, repo_map_msg, enabled_repoids: pkgs) - - monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) - monkeypatch.setattr(api, 'produce', produce_mocked()) - monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) - -- setup_tasks = pes_events_scanner.scan_pes_events(None, blacklisted_repoids={'blacklisted-rhel9'}) -+ setup_tasks = pes_events_scanner.scan_pes_events(None, {'blacklisted-rhel9'}, set()) - - assert not reporting.create_report.called - -@@ -497,10 +497,10 @@ def test_transaction_configuration_is_applied(monkeypatch): - monkeypatch.setattr(pes_events_scanner, 'remove_undesired_events', lambda events, releases: events) - monkeypatch.setattr(pes_events_scanner, '_get_enabled_modules', lambda *args: []) - monkeypatch.setattr(pes_events_scanner, 'replace_pesids_with_repoids_in_packages', -- lambda target_pkgs, repoids_of_source_pkgs, repo_map_msg=None: target_pkgs) -+ lambda target_pkgs, repoids_of_source_pkgs, repo_map_msg, enabled_repoids: target_pkgs) - monkeypatch.setattr(pes_events_scanner, - 'remove_new_packages_from_blacklisted_repos', -- lambda source_pkgs, target_pkgs, bl=None: (set(), target_pkgs)) -+ lambda source_pkgs, target_pkgs, bl: (set(), target_pkgs)) - - msgs = [ - RpmTransactionTasks(to_remove=['pkg-not-in-events']), -@@ -513,7 +513,7 @@ def test_transaction_configuration_is_applied(monkeypatch): - - monkeypatch.setattr(api, 'produce', produce_mocked()) - -- setup_tasks = pes_events_scanner.scan_pes_events(None) -+ setup_tasks = pes_events_scanner.scan_pes_events(None, set(), set()) - - assert api.produce.called == 1 - assert setup_tasks is not None -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_repositoriesblocklist.py b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_repositoriesblocklist.py -index 5ffa0990..fc657824 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_repositoriesblocklist.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_repositoriesblocklist.py -@@ -2,6 +2,7 @@ import pytest - - from leapp import reporting - from leapp.libraries.actor import repositoriesblocklist -+from leapp.libraries.actor.targetcontentresolver import ExternalRepoSetupTasks - from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked, produce_mocked - from leapp.libraries.stdlib import api - from leapp.models import ( -@@ -12,7 +13,6 @@ from leapp.models import ( - RepositoriesBlocklisted, - RepositoriesFacts, - RepositoriesMapping, -- RepositoriesSetupTasks, - RepositoryData, - RepositoryFile - ) -@@ -228,21 +228,19 @@ def _get_produced_blacklisted(): - def test_compute_blocklist_merges_internal_and_external(monkeypatch): - """ - compute_blocklist merges internal blocklist with external -- RepositoriesSetupTasks.to_block and produces both messages. -+ ExternalRepoSetupTasks.to_block and produces both messages. - """ -- external_tasks = [ -- RepositoriesSetupTasks(to_enable=['some-repo'], to_block=['ext-blocked-1', 'ext-blocked-2']), -- ] -+ external_tasks = ExternalRepoSetupTasks( -+ to_enable={'some-repo'}, to_block={'ext-blocked-1', 'ext-blocked-2'}, custom=set() -+ ) - monkeypatch.setattr( - repositoriesblocklist, 'get_blocklisted_repoids', lambda _repo_map: {'crb-repo-1'} - ) -- monkeypatch.setattr(api, 'consume', lambda _model: iter(external_tasks)) - monkeypatch.setattr(api, 'produce', produce_mocked()) - -- full_blocklist, external_repoids = repositoriesblocklist.compute_blocklist(None) -+ full_blocklist = repositoriesblocklist.compute_blocklist(None, external_tasks) - - assert full_blocklist == {'crb-repo-1', 'ext-blocked-1', 'ext-blocked-2'} -- assert external_repoids == {'some-repo'} - - blocklisted_msgs = _get_produced_blocklisted() - assert len(blocklisted_msgs) == 1 -@@ -255,16 +253,15 @@ def test_compute_blocklist_merges_internal_and_external(monkeypatch): - - def test_compute_blocklist_no_messages_when_empty(monkeypatch): - """No blocklist messages are produced when blocklist is empty.""" -+ external_tasks = ExternalRepoSetupTasks(to_enable=set(), to_block=set(), custom=set()) - monkeypatch.setattr( - repositoriesblocklist, 'get_blocklisted_repoids', lambda _repo_map: set() - ) -- monkeypatch.setattr(api, 'consume', lambda _model: iter([])) - monkeypatch.setattr(api, 'produce', produce_mocked()) - -- full_blocklist, external_repoids = repositoriesblocklist.compute_blocklist(None) -+ full_blocklist = repositoriesblocklist.compute_blocklist(None, external_tasks) - - assert full_blocklist == set() -- assert external_repoids == set() - assert len(_get_produced_blocklisted()) == 0 - assert len(_get_produced_blacklisted()) == 0 - -@@ -272,53 +269,47 @@ def test_compute_blocklist_no_messages_when_empty(monkeypatch): - @suppress_deprecation(RepositoriesBlacklisted) - def test_compute_blocklist_only_internal(monkeypatch): - """Only internal blocklist when no external tasks are present.""" -+ external_tasks = ExternalRepoSetupTasks(to_enable=set(), to_block=set(), custom=set()) - monkeypatch.setattr( - repositoriesblocklist, 'get_blocklisted_repoids', lambda _repo_map: {'crb-repo-1'} - ) -- monkeypatch.setattr(api, 'consume', lambda _model: iter([])) - monkeypatch.setattr(api, 'produce', produce_mocked()) - -- full_blocklist, external_repoids = repositoriesblocklist.compute_blocklist(None) -+ full_blocklist = repositoriesblocklist.compute_blocklist(None, external_tasks) - - assert full_blocklist == {'crb-repo-1'} -- assert external_repoids == set() - assert len(_get_produced_blocklisted()) == 1 - - --def test_compute_blocklist_multiple_external_tasks(monkeypatch): -- """Blocklist and to_enable from multiple external tasks are aggregated.""" -- external_tasks = [ -- RepositoriesSetupTasks(to_block=['ext-1'], to_enable=['repo-a']), -- RepositoriesSetupTasks(to_block=['ext-2', 'ext-3'], to_enable=['repo-b']), -- ] -+def test_compute_blocklist_aggregated_external_tasks(monkeypatch): -+ """Blocklist from pre-aggregated external tasks is merged with internal.""" -+ external_tasks = ExternalRepoSetupTasks( -+ to_enable={'repo-a', 'repo-b'}, to_block={'ext-1', 'ext-2', 'ext-3'}, custom=set() -+ ) - monkeypatch.setattr( - repositoriesblocklist, 'get_blocklisted_repoids', lambda _repo_map: set() - ) -- monkeypatch.setattr(api, 'consume', lambda _model: iter(external_tasks)) - monkeypatch.setattr(api, 'produce', produce_mocked()) - -- full_blocklist, external_repoids = repositoriesblocklist.compute_blocklist(None) -+ full_blocklist = repositoriesblocklist.compute_blocklist(None, external_tasks) - - assert full_blocklist == {'ext-1', 'ext-2', 'ext-3'} -- assert external_repoids == {'repo-a', 'repo-b'} - - - @suppress_deprecation(RepositoriesBlacklisted) - def test_compute_blocklist_overlapping_internal_and_external(monkeypatch): - """Overlapping repoids between internal and external blocklists are deduplicated.""" -- external_tasks = [ -- RepositoriesSetupTasks(to_block=['shared-repo', 'ext-only']), -- ] -+ external_tasks = ExternalRepoSetupTasks( -+ to_enable=set(), to_block={'shared-repo', 'ext-only'}, custom=set() -+ ) - monkeypatch.setattr( - repositoriesblocklist, 'get_blocklisted_repoids', lambda _repo_map: {'shared-repo', 'internal-only'} - ) -- monkeypatch.setattr(api, 'consume', lambda _model: iter(external_tasks)) - monkeypatch.setattr(api, 'produce', produce_mocked()) - -- full_blocklist, external_repoids = repositoriesblocklist.compute_blocklist(None) -+ full_blocklist = repositoriesblocklist.compute_blocklist(None, external_tasks) - - assert full_blocklist == {'shared-repo', 'internal-only', 'ext-only'} -- assert external_repoids == set() - - blocklisted_msgs = _get_produced_blocklisted() - assert len(blocklisted_msgs) == 1 -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py -index 4e425da6..98f19ea8 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py -@@ -1,4 +1,17 @@ -+import pytest -+ -+from leapp.exceptions import StopActorExecutionError - from leapp.libraries.actor import targetcontentresolver -+from leapp.libraries.actor.targetcontentresolver import ExternalRepoSetupTasks, InputData -+from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked -+from leapp.libraries.stdlib import api -+from leapp.models import ( -+ CustomTargetRepository, -+ RepositoriesFacts, -+ RepositoriesSetupTasks, -+ RepositoryData, -+ RepositoryFile -+) - - - def test_process_orchestration(monkeypatch): -@@ -10,22 +23,33 @@ def test_process_orchestration(monkeypatch): - - fake_repo_map = object() - fake_blocklist = frozenset({'blocked-repo'}) -- fake_external_repoids = frozenset({'ext-repo'}) -+ fake_external_tasks = ExternalRepoSetupTasks( -+ to_enable=frozenset({'ext-repo'}), to_block=frozenset(), custom=frozenset() -+ ) -+ fake_enabled_repoids = frozenset({'enabled-repo'}) - fake_pes_repoids = frozenset({'pes-repo'}) - -+ class FakeInputData: -+ def __init__(self): -+ call_log.append('InputData') -+ self.external_tasks = fake_external_tasks -+ self.enabled_repoids = fake_enabled_repoids -+ - def mock_scan_repositories(): - call_log.append('scan_repositories') - return fake_repo_map - -- def mock_compute_blocklist(repo_mapping): -+ def mock_compute_blocklist(repo_mapping, external_tasks): - call_log.append('compute_blocklist') - assert repo_mapping is fake_repo_map -- return fake_blocklist, fake_external_repoids -+ assert external_tasks is fake_external_tasks -+ return fake_blocklist - -- def mock_scan_pes_events(repo_mapping, blacklisted_repoids): -+ def mock_scan_pes_events(repo_mapping, blacklisted_repoids, enabled_repoids): - call_log.append('scan_pes_events') - assert repo_mapping is fake_repo_map - assert blacklisted_repoids is fake_blocklist -+ assert enabled_repoids is fake_enabled_repoids - return fake_pes_repoids - - def mock_setup_target_repos(repo_mapping, pes_requested_repoids=None, -@@ -34,8 +58,12 @@ def test_process_orchestration(monkeypatch): - assert repo_mapping is fake_repo_map - assert pes_requested_repoids is fake_pes_repoids - assert blacklisted_repoids is fake_blocklist -- assert external_repoids_requests is fake_external_repoids -+ assert external_repoids_requests is fake_external_tasks.to_enable - -+ monkeypatch.setattr( -+ 'leapp.libraries.actor.targetcontentresolver.InputData', -+ FakeInputData, -+ ) - monkeypatch.setattr( - 'leapp.libraries.actor.targetcontentresolver.scan_repositories', - mock_scan_repositories, -@@ -56,8 +84,84 @@ def test_process_orchestration(monkeypatch): - targetcontentresolver.process() - - assert call_log == [ -+ 'InputData', - 'scan_repositories', - 'compute_blocklist', - 'scan_pes_events', - 'setup_target_repos', - ] -+ -+ -+def _make_repo_facts(repoids_enabled=None, repoids_disabled=None): -+ repos_data = [] -+ for repoid in (repoids_enabled or []): -+ repos_data.append(RepositoryData(repoid=repoid, name=repoid, enabled=True)) -+ for repoid in (repoids_disabled or []): -+ repos_data.append(RepositoryData(repoid=repoid, name=repoid, enabled=False)) -+ return RepositoriesFacts( -+ repositories=[RepositoryFile(file='/etc/yum.repos.d/test.repo', data=repos_data)] -+ ) -+ -+ -+def test_inputdata_collects_enabled_repoids(monkeypatch): -+ repo_facts = _make_repo_facts( -+ repoids_enabled=['repo-a', 'repo-b'], -+ repoids_disabled=['repo-c'], -+ ) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[repo_facts])) -+ -+ indata = InputData() -+ -+ assert indata.enabled_repoids == {'repo-a', 'repo-b'} -+ -+ -+def test_inputdata_raises_when_repofacts_missing(monkeypatch): -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[])) -+ -+ with pytest.raises(StopActorExecutionError): -+ InputData() -+ -+ -+def test_inputdata_aggregates_external_tasks(monkeypatch): -+ repo_facts = _make_repo_facts(repoids_enabled=['repo-a']) -+ setup_tasks_1 = RepositoriesSetupTasks(to_enable=['en-1'], to_block=['bl-1']) -+ setup_tasks_2 = RepositoriesSetupTasks(to_enable=['en-2'], to_block=['bl-2', 'bl-3']) -+ custom_1 = CustomTargetRepository(repoid='custom-1') -+ custom_2 = CustomTargetRepository(repoid='custom-2') -+ -+ monkeypatch.setattr( -+ api, 'current_actor', -+ CurrentActorMocked(msgs=[repo_facts, setup_tasks_1, setup_tasks_2, custom_1, custom_2]) -+ ) -+ -+ indata = InputData() -+ -+ assert indata.external_tasks.to_enable == {'en-1', 'en-2'} -+ assert indata.external_tasks.to_block == {'bl-1', 'bl-2', 'bl-3'} -+ assert indata.external_tasks.custom == {'custom-1', 'custom-2'} -+ -+ -+def test_inputdata_empty_external_tasks(monkeypatch): -+ repo_facts = _make_repo_facts(repoids_enabled=['repo-a']) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[repo_facts])) -+ -+ indata = InputData() -+ -+ assert indata.external_tasks.to_enable == set() -+ assert indata.external_tasks.to_block == set() -+ assert indata.external_tasks.custom == set() -+ -+ -+def test_inputdata_warns_on_duplicate_repofacts(monkeypatch): -+ repo_facts_1 = _make_repo_facts(repoids_enabled=['repo-a']) -+ repo_facts_2 = _make_repo_facts(repoids_enabled=['repo-b']) -+ monkeypatch.setattr( -+ api, 'current_actor', -+ CurrentActorMocked(msgs=[repo_facts_1, repo_facts_2]) -+ ) -+ monkeypatch.setattr(api, 'current_logger', logger_mocked()) -+ -+ indata = InputData() -+ -+ assert indata.enabled_repoids == {'repo-a'} -+ assert any('more than one' in msg for msg in api.current_logger.warnmsg) --- -2.54.0 - diff --git a/0099-3-9-targetcontentresolver-RepositoriesBlacklisted-us.patch b/0099-3-9-targetcontentresolver-RepositoriesBlacklisted-us.patch deleted file mode 100644 index 903fb76..0000000 --- a/0099-3-9-targetcontentresolver-RepositoriesBlacklisted-us.patch +++ /dev/null @@ -1,309 +0,0 @@ -From 51af2cfd4b028efa58c75d34c55b7b65eac7df27 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Sat, 6 Jun 2026 04:25:39 +0200 -Subject: [PATCH 099/105] [3/9] targetcontentresolver: RepositoriesBlacklisted: - use it as task - -Previous "refactoring" commits changes the purpose of the message -from - 1. task specifying what should be blocklisted - 2. fact what repositories are blocklisted -leaving it to be just the fact message. As discussed in team, -we believe that if the model has been used by any other third-party -actors, it was used in the first way to request blocklist of specific -repositories. So updating the behaviour in the way the message is -consumed and processed now instead of producing it. - -Note that both purposes cannot be kept in this original message -due to planned changes - which is one of reasons why the model -becomes deprecated in previous commits. - -Unit tests adjusted with assistance of AI. - -Jira: RHEL-115867 -Assisted-by: Claude Code (Opus 4.6) -Signed-off-by: Petr Stodulka ---- - .../libraries-and-api/deprecations-list.md | 2 +- - .../actors/targetcontentresolver/actor.py | 4 +-- - .../libraries/repositoriesblocklist.py | 8 ++--- - .../libraries/targetcontentresolver.py | 8 ++++- - .../tests/test_repositoriesblocklist.py | 23 ++---------- - .../tests/test_targetcontentresolver.py | 36 +++++++++++++++++++ - .../common/models/repositoriesblocklisted.py | 8 +++-- - 7 files changed, 55 insertions(+), 34 deletions(-) - -diff --git a/docs/source/libraries-and-api/deprecations-list.md b/docs/source/libraries-and-api/deprecations-list.md -index 301de558..1968c8af 100644 ---- a/docs/source/libraries-and-api/deprecations-list.md -+++ b/docs/source/libraries-and-api/deprecations-list.md -@@ -18,12 +18,12 @@ Only the versions in which a deprecation has been made are listed. - - **`LEAPP_NO_NETWORK_RENAMING`** - It becomes obsoleted by the solution based on `net.naming-scheme` which replaces the legacy solution based on created udev link files correcting NIC names during the upgrade. - - Models: - - **`RenamedInterfaces`** - Information provided in this message is not always complete and it's not used since the `net.naming-scheme` kernel command line argument is set during the upgrade. -+ - **`RepositoriesBlacklisted`** - To get the list of blocklisted repositories, consume `RepositoriesBlocklisted` message. To request a repository to be blocklisted, use `RepositoriesSetupTasks.to_block` instead. - - Shared libraries - - **`leapp.libraries.common.dnfconfig`** - Moved to `leapp.libraries.common.dnflibs.dnfconfig`. Original library is deprecated. - - **`leapp.libraries.common.dnfplugin`** - Moved to `leapp.libraries.common.dnflibs.dnfplugin`. Original library is deprecated. - - **`leapp.libraries.common.module`** - Replaced by `leapp.libraries.common.dnflibs.dnfmodule` (renamed for clarity). Original library is deprecated. - -- - ## v0.24.0 (till September 2026) - - Shared libraries - - **`leapp.libraries.common.config.get_distro_id()`** - The function has been replaced by variants for source and target distros - `leapp.libraries.common.config.get_source_distro_id()` and `leapp.libraries.common.config.get_target_distro_id()`. -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/actor.py b/repos/system_upgrade/common/actors/targetcontentresolver/actor.py -index 6ea95732..e132cc3a 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/actor.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/actor.py -@@ -43,9 +43,10 @@ class TargetContentResolver(Actor): - DistributionSignedRPM, - EnabledModules, - InstalledRPM, -+ RHUIInfo, -+ RepositoriesBlacklisted, - RepositoriesFacts, - RepositoriesSetupTasks, -- RHUIInfo, - RpmTransactionTasks, - UsedRepositories, - ) -@@ -53,7 +54,6 @@ class TargetContentResolver(Actor): - ConsumedDataAsset, - PESRpmTransactionTasks, - Report, -- RepositoriesBlacklisted, - RepositoriesBlocklisted, - RepositoriesMapping, - SkippedRepositories, -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repositoriesblocklist.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repositoriesblocklist.py -index 216bea52..f92f64fb 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repositoriesblocklist.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repositoriesblocklist.py -@@ -1,8 +1,7 @@ - from leapp import reporting - from leapp.libraries.common.config.version import get_source_major_version, get_target_major_version - from leapp.libraries.stdlib import api --from leapp.models import CustomTargetRepository, RepositoriesBlacklisted, RepositoriesBlocklisted, RepositoriesFacts --from leapp.utils.deprecation import suppress_deprecation -+from leapp.models import CustomTargetRepository, RepositoriesBlocklisted, RepositoriesFacts - - # {OS_MAJOR_VERSION: PESID} - UNSUPPORTED_PESIDS = { -@@ -159,7 +158,6 @@ def get_blocklisted_repoids(repo_mapping): - return filtered_repos_to_exclude - - --@suppress_deprecation(RepositoriesBlacklisted) - def compute_blocklist(repo_mapping, external_tasks): - """ - Create the blocklist of repositories that should be blocked during the upgrade. -@@ -186,8 +184,7 @@ def compute_blocklist(repo_mapping, external_tasks): - the in-place upgrade purpose. Currently there is no explicit custom - configuration to disable a repo. - -- Once the list is calculated, the RepositoriesBlocklisted and -- RepositoriesBlacklisted messages are produced. -+ Once the list is calculated the RepositoriesBlocklisted message is produced. - - :param repo_mapping: RepositoriesMapping message. - :type repo_mapping: RepositoriesMapping -@@ -205,6 +202,5 @@ def compute_blocklist(repo_mapping, external_tasks): - full_blocklist = internal_blocklist.union(external_tasks.to_block) - if full_blocklist: - api.produce(RepositoriesBlocklisted(repoids=sorted(full_blocklist))) -- api.produce(RepositoriesBlacklisted(repoids=sorted(full_blocklist))) - - return full_blocklist -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -index c1b46916..e6885e44 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -@@ -4,7 +4,8 @@ from leapp.exceptions import StopActorExecutionError - from leapp.libraries.actor import pes_events_scanner, repositoriesblocklist, setuptargetrepos - from leapp.libraries.actor.repomap_loader import scan_repositories - from leapp.libraries.stdlib import api --from leapp.models import CustomTargetRepository, RepositoriesFacts, RepositoriesSetupTasks -+from leapp.models import CustomTargetRepository, RepositoriesBlacklisted, RepositoriesFacts, RepositoriesSetupTasks -+from leapp.utils.deprecation import suppress_deprecation - - ExternalRepoSetupTasks = namedtuple('ExternalRepoSetupTasks', ('to_enable', 'to_block', 'custom')) - -@@ -42,6 +43,7 @@ class InputData(): - return None - return msg - -+ @suppress_deprecation(RepositoriesBlacklisted) - def _get_external_reposetup_tasks(self): - """ - Collect repository related tasks from other actors (or configs in future). -@@ -58,6 +60,10 @@ class InputData(): - self.external_tasks.to_block.update(task.to_block) - self.external_tasks.to_enable.update(task.to_enable) - -+ # DEPRECATED: Drop after 2026-07 -+ for task in api.consume(RepositoriesBlacklisted): -+ self.external_tasks.to_block.update(task.repoids) -+ - self.external_tasks.custom.update({repo.repoid for repo in api.consume(CustomTargetRepository)}) - - def _get_enabled_repoids(self): -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_repositoriesblocklist.py b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_repositoriesblocklist.py -index fc657824..ce72dc9e 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_repositoriesblocklist.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_repositoriesblocklist.py -@@ -9,14 +9,12 @@ from leapp.models import ( - CustomTargetRepository, - PESIDRepositoryEntry, - RepoMapEntry, -- RepositoriesBlacklisted, - RepositoriesBlocklisted, - RepositoriesFacts, - RepositoriesMapping, - RepositoryData, - RepositoryFile - ) --from leapp.utils.deprecation import suppress_deprecation - - - @pytest.fixture -@@ -216,19 +214,13 @@ def test_enablerepo_option(monkeypatch, - - - def _get_produced_blocklisted(): -- return [m for m in api.produce.model_instances -- if isinstance(m, RepositoriesBlocklisted) and not isinstance(m, RepositoriesBlacklisted)] -+ return [m for m in api.produce.model_instances if isinstance(m, RepositoriesBlocklisted)] - - --def _get_produced_blacklisted(): -- return [m for m in api.produce.model_instances if isinstance(m, RepositoriesBlacklisted)] -- -- --@suppress_deprecation(RepositoriesBlacklisted) - def test_compute_blocklist_merges_internal_and_external(monkeypatch): - """ - compute_blocklist merges internal blocklist with external -- ExternalRepoSetupTasks.to_block and produces both messages. -+ ExternalRepoSetupTasks.to_block and produces RepositoriesBlocklisted. - """ - external_tasks = ExternalRepoSetupTasks( - to_enable={'some-repo'}, to_block={'ext-blocked-1', 'ext-blocked-2'}, custom=set() -@@ -246,10 +238,6 @@ def test_compute_blocklist_merges_internal_and_external(monkeypatch): - assert len(blocklisted_msgs) == 1 - assert set(blocklisted_msgs[0].repoids) == full_blocklist - -- blacklisted_msgs = _get_produced_blacklisted() -- assert len(blacklisted_msgs) == 1 -- assert set(blacklisted_msgs[0].repoids) == full_blocklist -- - - def test_compute_blocklist_no_messages_when_empty(monkeypatch): - """No blocklist messages are produced when blocklist is empty.""" -@@ -263,10 +251,8 @@ def test_compute_blocklist_no_messages_when_empty(monkeypatch): - - assert full_blocklist == set() - assert len(_get_produced_blocklisted()) == 0 -- assert len(_get_produced_blacklisted()) == 0 - - --@suppress_deprecation(RepositoriesBlacklisted) - def test_compute_blocklist_only_internal(monkeypatch): - """Only internal blocklist when no external tasks are present.""" - external_tasks = ExternalRepoSetupTasks(to_enable=set(), to_block=set(), custom=set()) -@@ -296,7 +282,6 @@ def test_compute_blocklist_aggregated_external_tasks(monkeypatch): - assert full_blocklist == {'ext-1', 'ext-2', 'ext-3'} - - --@suppress_deprecation(RepositoriesBlacklisted) - def test_compute_blocklist_overlapping_internal_and_external(monkeypatch): - """Overlapping repoids between internal and external blocklists are deduplicated.""" - external_tasks = ExternalRepoSetupTasks( -@@ -314,7 +299,3 @@ def test_compute_blocklist_overlapping_internal_and_external(monkeypatch): - blocklisted_msgs = _get_produced_blocklisted() - assert len(blocklisted_msgs) == 1 - assert set(blocklisted_msgs[0].repoids) == full_blocklist -- -- blacklisted_msgs = _get_produced_blacklisted() -- assert len(blacklisted_msgs) == 1 -- assert set(blacklisted_msgs[0].repoids) == full_blocklist -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py -index 98f19ea8..97aaad33 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py -@@ -7,11 +7,13 @@ from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked - from leapp.libraries.stdlib import api - from leapp.models import ( - CustomTargetRepository, -+ RepositoriesBlacklisted, - RepositoriesFacts, - RepositoriesSetupTasks, - RepositoryData, - RepositoryFile - ) -+from leapp.utils.deprecation import suppress_deprecation - - - def test_process_orchestration(monkeypatch): -@@ -165,3 +167,37 @@ def test_inputdata_warns_on_duplicate_repofacts(monkeypatch): - - assert indata.enabled_repoids == {'repo-a'} - assert any('more than one' in msg for msg in api.current_logger.warnmsg) -+ -+ -+@suppress_deprecation(RepositoriesBlacklisted) -+def test_inputdata_consumes_deprecated_blacklisted(monkeypatch): -+ """RepositoriesBlacklisted repoids are added to external_tasks.to_block.""" -+ repo_facts = _make_repo_facts(repoids_enabled=['repo-a']) -+ blacklisted = RepositoriesBlacklisted(repoids=['bl-1', 'bl-2']) -+ -+ monkeypatch.setattr( -+ api, 'current_actor', -+ CurrentActorMocked(msgs=[repo_facts, blacklisted]) -+ ) -+ -+ indata = InputData() -+ -+ assert indata.external_tasks.to_block == {'bl-1', 'bl-2'} -+ -+ -+@suppress_deprecation(RepositoriesBlacklisted) -+def test_inputdata_merges_blacklisted_with_setup_tasks(monkeypatch): -+ """RepositoriesBlacklisted repoids are merged with RepositoriesSetupTasks.to_block.""" -+ repo_facts = _make_repo_facts(repoids_enabled=['repo-a']) -+ setup_tasks = RepositoriesSetupTasks(to_enable=['en-1'], to_block=['bl-from-setup']) -+ blacklisted = RepositoriesBlacklisted(repoids=['bl-from-deprecated']) -+ -+ monkeypatch.setattr( -+ api, 'current_actor', -+ CurrentActorMocked(msgs=[repo_facts, setup_tasks, blacklisted]) -+ ) -+ -+ indata = InputData() -+ -+ assert indata.external_tasks.to_block == {'bl-from-setup', 'bl-from-deprecated'} -+ assert indata.external_tasks.to_enable == {'en-1'} -diff --git a/repos/system_upgrade/common/models/repositoriesblocklisted.py b/repos/system_upgrade/common/models/repositoriesblocklisted.py -index c1825ec3..95fbd2dc 100644 ---- a/repos/system_upgrade/common/models/repositoriesblocklisted.py -+++ b/repos/system_upgrade/common/models/repositoriesblocklisted.py -@@ -23,12 +23,14 @@ class RepositoriesBlocklisted(Model): - since='2026-06-01', - message=( - 'This model has been deprecated and replaced. ' -- 'To get the list of blocklisted repositories, use RepositoriesBlocklisted. ' -- 'To request a repository to be blocklisted, use RepositoriesSetupTasks.blocklist.' -+ 'To get the list of blocklisted repositories, consume RepositoriesBlocklisted. ' -+ 'To request a repository to be blocklisted, produce RepositoriesSetupTasks.to_block.' - ), - ) - class RepositoriesBlacklisted(RepositoriesBlocklisted): - """ -- Repository IDs that should be ignored by Leapp during the upgrade process. -+ Specify list of repository IDs that should be blocked during the upgrade. -+ -+ Note this is deprecated and you should use RepositoriesSetupTasks.to_block - """ - topic = SystemFactsTopic --- -2.54.0 - diff --git a/0100-4-9-targetcontentresolver-repositoriesblacklist-rede.patch b/0100-4-9-targetcontentresolver-repositoriesblacklist-rede.patch deleted file mode 100644 index 901e49c..0000000 --- a/0100-4-9-targetcontentresolver-repositoriesblacklist-rede.patch +++ /dev/null @@ -1,786 +0,0 @@ -From 6d588f44f726eb2e16b15498caa1fc2887faccec Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Sat, 6 Jun 2026 06:52:09 +0200 -Subject: [PATCH 100/105] [4/9] targetcontentresolver: repositoriesblacklist: - redesign - -* The library does not consume any messages anymore: - * RepositoriesFacts were consumed to obtain information about - enabled repositories on the source system. This information is now - propagated - given to the entry `compute_blocklist()` function - as `enabled_repoids` parameter - - * ConsumeTargetRepository is not needed to consume anymore as the - information is included inside external_tasks. - -* Simplify the logig around "internal" (or implicit) blocklisting. - The original code has been written in more generic way as a relict - from IPU 7 -> 8 time, where "Optional" repositories existed - and CRB repositories have been newly introduced. Nowadays, we have - just CRB repositories and it's clear that only CRB repositories - will need still this special handling. So the solution has been - adjusted to CRB repositories only and all related code and comments - have been updated. - -* Specify the priority order when processing implicit/internal (CRB), - external, and explicit customer requests. The order is following: - internal (CRB) < external enable rq < external block rq < explicit user request - -* Work with sets instead of lists everywhere. This reduced amount - of code by another 25% when keeping it simple. - -* Filter out irrelevant CRB repositories from the report. - Originally the report about blocklisted CRB repositories included - all target CRB repoids - for all architectures and distributions. - Include only repoids related to the current architurecture. - Another filtering is planned to be applied later in the repomapping - itself, so only relevant data will be provided. - -* Update generated reports. - * Remove the "Using repository not supported by Red Hat" report. - It was inconsistent (printed only when a CRB repo has not been - enabled on the source system and user enabled it manually for the - upgrade process only. Also the report does not make sense for any - other (non-RHEL) distro. Discussing it with PO, we agreed on the - removal of the report. - * Add the stable Key to the exclusion report - -* Adjust the logic around implicit CRB repositories blocking. - * Do not blocklist any CRB repositories implicitly when any of them - is enabled by user manually. The behaviour is now consistent with - the one when a CRB repository is enabled on the source system. - Having a different behaviour to these both cases did not make a sense. - * Additionally blocks CRB repositories only on RHEL systems, - as other distros are not supported by us directly so the report - does not make a sense. - -Unit tests adjusted with assistance of AI. - -Jira: RHEL-115867 -Assisted-by: Claude Code (Opus 4.6) -Signed-off-by: Petr Stodulka ---- - .../libraries/repositoriesblocklist.py | 177 +++++------ - .../libraries/targetcontentresolver.py | 6 +- - .../tests/test_repositoriesblocklist.py | 280 ++++++++++-------- - .../tests/test_targetcontentresolver.py | 3 +- - .../common/models/repositoriessetuptasks.py | 7 + - 5 files changed, 245 insertions(+), 228 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repositoriesblocklist.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repositoriesblocklist.py -index f92f64fb..e526bcbd 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repositoriesblocklist.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repositoriesblocklist.py -@@ -1,28 +1,8 @@ - from leapp import reporting -+from leapp.libraries.common.config import get_target_distro_id - from leapp.libraries.common.config.version import get_source_major_version, get_target_major_version - from leapp.libraries.stdlib import api --from leapp.models import CustomTargetRepository, RepositoriesBlocklisted, RepositoriesFacts -- --# {OS_MAJOR_VERSION: PESID} --UNSUPPORTED_PESIDS = { -- "8": "rhel8-CRB", -- "9": "rhel9-CRB", -- "10": "rhel10-CRB" --} -- -- --def _report_using_unsupported_repos(repos): -- report = [ -- reporting.Title('Using repository not supported by Red Hat'), -- reporting.Summary( -- 'The following repositories have been used for the ' -- 'upgrade, but they are not supported by the Red Hat.:\n' -- '- {}'.format('\n - '.join(repos)) -- ), -- reporting.Severity(reporting.Severity.HIGH), -- reporting.Groups([reporting.Groups.REPOSITORY]), -- ] -- reporting.create_report(report) -+from leapp.models import RepositoriesBlocklisted - - - def _report_excluded_repos(repos): -@@ -47,125 +27,110 @@ def _report_excluded_repos(repos): - ' as an argument (the option can be used multiple times).' - ) - ), -+ reporting.Key('1b9132cb2362ae7830e48eee7811be9527747de8') - ] - reporting.create_report(report) - - --def _get_manually_enabled_repos(): -+def _get_crb_repos(repo_mapping, major_version): - """ -- Get a set of repositories (repoids) that are manually enabled. -- -- manually enabled means ( -- specified by --enablerepo option of the leapp command or -- inside the /etc/leapp/files/leapp_upgrade_repositories.repo), -- ) -- :rtype: set [repoid] -- """ -- try: -- return {repo.repoid for repo in api.consume(CustomTargetRepository)} -- except StopIteration: -- return set() -+ Return set of relevant CRB repoids for the specified major version - -+ Only CRB repositories for the current architecture and distribution are -+ relevant. - --def _get_pesid_repos(repo_mapping, pesid, major_version): -- """ -- Returns a list of pesid repos with the specified pesid and major version. -- -- :param str pesid: The PES ID representing the family of repositories. -- :param str major_version: The major version of the RHEL OS. -+ :param repo_mapping: RepositoriesMapping message. -+ :type repo_mapping: RepositoriesMapping -+ :param major_version: The OS major version. -+ :type major_version: str - :returns: A set of repoids with the specified pesid and major version. -- :rtype: List[PESIDRepositoryEntry] -+ :rtype: Set[str] - """ -- pesid_repos = [] -+ pesid = f'rhel{major_version}-CRB' -+ curr_arch = api.current_actor().configuration.architecture -+ crb_repoids = set() - for pesid_repo in repo_mapping.repositories: -+ if pesid_repo.major_version != major_version or pesid_repo.arch != curr_arch: -+ # irrelevant repository -+ continue - if pesid_repo.pesid == pesid and pesid_repo.major_version == major_version: -- pesid_repos.append(pesid_repo) -- return pesid_repos -- -- --def _get_repoids_to_exclude(repo_mapping): -- """ -- Returns a set of repoids that should be blocklisted on the target system. -- -- :param RepositoriesMapping repo_mapping: Repository mapping data. -- :returns: A set of repoids to blocklist on the target system. -- :rtype: Set[str] -- """ -- pesid_repos_to_exclude = _get_pesid_repos(repo_mapping, -- UNSUPPORTED_PESIDS[get_target_major_version()], -- get_target_major_version()) -- return {pesid_repo.repoid for pesid_repo in pesid_repos_to_exclude} -+ crb_repoids.add(pesid_repo.repoid) -+ return crb_repoids - - --def _are_optional_repos_disabled(repo_mapping, repos_on_system): -+def _are_crb_repos_disabled(repo_mapping, enabled_repoids): - """ -- Checks whether all optional repositories are disabled. -+ Checks whether all CRB repositories are disabled. - -- :param RepositoriesMapping repo_mapping: Repository mapping data. -- :param RepositoriesFacts repos_on_system: Installed repositories on the source system. -- :returns: True if there are any optional repositories enabled on the source system. -+ :param repo_mapping: RepositoriesMapping message. -+ :type repo_mapping: RepositoriesMapping -+ :param enabled_repoids: Set of repoids enabled on the source system. -+ :type enabled_repoids: Set[str] -+ :returns: False if any CRB repositories are enabled on the source system, True otherwise. -+ :rtype: bool - """ -+ return enabled_repoids.isdisjoint(_get_crb_repos(repo_mapping, get_source_major_version())) - -- # Get a set of all repo_ids that are optional -- optional_pesid_repos = _get_pesid_repos(repo_mapping, -- UNSUPPORTED_PESIDS[get_source_major_version()], -- get_source_major_version()) -- -- optional_repoids = [optional_pesid_repo.repoid for optional_pesid_repo in optional_pesid_repos] - -- # Gather all optional repositories on the source system that are not enabled -- for repofile in repos_on_system.repositories: -- for repository in repofile.data: -- if repository.repoid in optional_repoids and repository.enabled: -- return False -- return True -- -- --def get_blocklisted_repoids(repo_mapping): -+def _calc_internal_blocklist(repo_mapping, external_tasks, enabled_repoids): - """ -- Exclude target repositories provided by Red Hat without support. -+ Calculate the internal blocklist of target CRB repositories. - - Conditions to exclude: -- - there are not such repositories already enabled on the source system -- (e.g. "CRB" repositories) -- - such repositories are not required for the upgrade explicitly by the user -+ - no CRB repositories are enabled on the source system -+ - repository is not explicitly configured by user to be used during the upgrade - (e.g. via the --enablerepo option or via the /etc/leapp/files/leapp_upgrade_repositories.repo file) - -- E.g. CRB repository is provided by Red Hat but it is without the support. -+ Also report explicitly enabled and blocklisted CRB repositories, unless -+ CRB is already already enabled on the source system. - - :param repo_mapping: RepositoriesMapping message. -- :returns: Set of repoids to blocklist on the target system. -- :rtype: set -- """ -- # TODO(pstodulk): replace it by enabled_repos -- repos_facts = next(api.consume(RepositoriesFacts), None) -+ :type repo_mapping: RepositoriesMapping -+ :param external_tasks: External repositories tasks represented by object with following fields: - -- if not _are_optional_repos_disabled(repo_mapping, repos_facts): -- # nothing to do - an optional repository is enabled -+ * ``to_enable`` - repositories that should be enabled -+ * ``to_block`` - repositories that should be blocklisted -+ * ``custom`` - repositories explicitly requested by user to be used during the upgrade -+ -+ :type external_tasks: targetcontentresolver.ExternalRepoSetupTasks -+ :param enabled_repoids: Set of repoids enabled on the source system. -+ :type enabled_repoids: Set[str] -+ :returns: Set of CRB repoids to blocklist on the target system. -+ :rtype: Set[str] -+ """ -+ if not _are_crb_repos_disabled(repo_mapping, enabled_repoids): -+ # nothing to do - a CRB repo is enabled - return set() - -- repos_to_exclude = _get_repoids_to_exclude(repo_mapping) -+ repos_to_exclude = _get_crb_repos(repo_mapping, get_target_major_version()) - -- # Do not exclude repos manually enabled from the CLI -- manually_enabled_repos = _get_manually_enabled_repos() & repos_to_exclude -- filtered_repos_to_exclude = repos_to_exclude - manually_enabled_repos -+ # Do not exclude repositories explicitly required by user for the upgrade -+ manually_enabled_repos = external_tasks.custom & repos_to_exclude - - if manually_enabled_repos: -- _report_using_unsupported_repos(manually_enabled_repos) -- if filtered_repos_to_exclude: -- _report_excluded_repos(filtered_repos_to_exclude) -+ # NOTE(pstodulk): Same like in case that a CRB repo is enabled on src OS -+ return set() -+ -+ if get_target_distro_id() == 'rhel': -+ # TODO(pstodulk): unify reports about blocklisted repos and effect on -+ # rpms tasks in pes_events_scanner -+ _report_excluded_repos(repos_to_exclude) - -- return filtered_repos_to_exclude -+ return repos_to_exclude - - --def compute_blocklist(repo_mapping, external_tasks): -+def compute_blocklist(repo_mapping, external_tasks, enabled_repoids): - """ - Create the blocklist of repositories that should be blocked during the upgrade. - -+ Additional effects: -+ * Once the list is calculated the RepositoriesBlocklisted message is produced. -+ * Generate reports related CRB repositories -+ - The content in the CRB repository is considered as unsupported. Hence - we do not want to enable such a repository by default on the target system - unless -- * it is explicitely requested, or -+ * it is explicitly requested, or - * it is already enabled on the source system - This is important as some functionality originally supported on the source - source system can be moved to the CRB repository on the target system. -@@ -180,25 +145,25 @@ def compute_blocklist(repo_mapping, external_tasks): - - The external custom request has the highest priority as this comes from - the system configuration or from the `--enablerepo` option used when executing -- leapp - so it's understood as decision made by user explicitely for -+ leapp - so it's understood as decision made by user explicitly for - the in-place upgrade purpose. Currently there is no explicit custom - configuration to disable a repo. - -- Once the list is calculated the RepositoriesBlocklisted message is produced. -- - :param repo_mapping: RepositoriesMapping message. - :type repo_mapping: RepositoriesMapping - :param external_tasks: External repositories tasks represented by object with following fields: - - * ``to_enable`` - repositories that should be enabled - * ``to_block`` - repositories that should be blocklisted -- * ``custom`` - repositories explicitely requested by user to be used during the upgrade -+ * ``custom`` - repositories explicitly requested by user to be used during the upgrade - - :type external_tasks: targetcontentresolver.ExternalRepoSetupTasks -+ :param enabled_repoids: Set of repoids enabled on the source system. -+ :type enabled_repoids: Set[str] - :returns: List of blocklisted repositories - :rtype: List[str] - """ -- internal_blocklist = get_blocklisted_repoids(repo_mapping) -+ internal_blocklist = _calc_internal_blocklist(repo_mapping, external_tasks, enabled_repoids) - full_blocklist = internal_blocklist.union(external_tasks.to_block) - if full_blocklist: - api.produce(RepositoriesBlocklisted(repoids=sorted(full_blocklist))) -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -index e6885e44..e70346b0 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -@@ -109,7 +109,11 @@ def process(): - """ - indata = InputData() - repositories_map_msg = scan_repositories() -- blocklisted_repoids = repositoriesblocklist.compute_blocklist(repositories_map_msg, indata.external_tasks) -+ blocklisted_repoids = repositoriesblocklist.compute_blocklist( -+ repositories_map_msg, -+ indata.external_tasks, -+ indata.enabled_repoids -+ ) - pes_requested_repoids = pes_events_scanner.scan_pes_events( - repositories_map_msg, - blocklisted_repoids, -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_repositoriesblocklist.py b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_repositoriesblocklist.py -index ce72dc9e..843c8f74 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_repositoriesblocklist.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_repositoriesblocklist.py -@@ -5,31 +5,9 @@ from leapp.libraries.actor import repositoriesblocklist - from leapp.libraries.actor.targetcontentresolver import ExternalRepoSetupTasks - from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked, produce_mocked - from leapp.libraries.stdlib import api --from leapp.models import ( -- CustomTargetRepository, -- PESIDRepositoryEntry, -- RepoMapEntry, -- RepositoriesBlocklisted, -- RepositoriesFacts, -- RepositoriesMapping, -- RepositoryData, -- RepositoryFile --) -+from leapp.models import PESIDRepositoryEntry, RepoMapEntry, RepositoriesBlocklisted, RepositoriesMapping - -- --@pytest.fixture --def repofacts_opts_disabled(): -- repos_data = [ -- RepositoryData( -- repoid="codeready-builder-for-rhel-8-x86_64-rpms", -- name="RHEL 8 CRB", -- enabled=False, -- ) -- ] -- repos_files = [ -- RepositoryFile(file="/etc/yum.repos.d/redhat.repo", data=repos_data) -- ] -- return RepositoriesFacts(repositories=repos_files) -+_NO_TASKS = ExternalRepoSetupTasks(to_enable=set(), to_block=set(), custom=set()) - - - @pytest.fixture -@@ -68,149 +46,211 @@ def repomap_opts_only(rhel8_crb_pesidrepo, rhel9_crb_pesidrepo): - ) - - --def test_all_target_optionals_blacklisted_when_no_optional_on_source(monkeypatch, repomap_opts_only): -+def test_crb_repos_blocked_when_no_crb_on_source(monkeypatch, repomap_opts_only): - """ -- Tests whether every target optional repository gets blacklisted -- if no optional repositories are used on the source system. -+ Target CRB repos are blocklisted when no CRB repository -+ is enabled on the source system. - """ -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked( -+ arch='x86_64', src_ver='8.10', dst_ver='9.6', dst_distro='rhel' -+ )) -+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) - -- repos_data = [ -- RepositoryData( -- repoid="rhel-8-server-rpms", -- name="RHEL 8 Server", -- enabled=True, -- ) -- ] -- repos_files = [ -- RepositoryFile(file="/etc/yum.repos.d/redhat.repo", data=repos_data) -- ] -- repo_facts = RepositoriesFacts(repositories=repos_files) -- -- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[repo_facts])) -- monkeypatch.setattr(api, 'produce', produce_mocked()) -- monkeypatch.setattr(reporting, 'create_report', produce_mocked()) -- -- result = repositoriesblocklist.get_blocklisted_repoids(repomap_opts_only) -+ result = repositoriesblocklist._calc_internal_blocklist( -+ repomap_opts_only, _NO_TASKS, enabled_repoids={'rhel-8-server-rpms'} -+ ) - - assert 'codeready-builder-for-rhel-9-x86_64-rpms' in result - - --def test_with_no_mapping_for_optional_repos(monkeypatch, repomap_opts_only, repofacts_opts_disabled): -+def test_empty_result_when_no_crb_pesid_in_mapping(monkeypatch, repomap_opts_only): - """ -- Tests whether an empty set is returned if no valid target is found for an optional repository in mapping data. -+ Empty set is returned if no valid CRB target is found in mapping data. - """ -- - repomap_opts_only.repositories[1].pesid = 'test_pesid' - -- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[repofacts_opts_disabled])) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked( -+ arch='x86_64', src_ver='8.10', dst_ver='9.6', dst_distro='rhel' -+ )) -+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) - -- result = repositoriesblocklist.get_blocklisted_repoids(repomap_opts_only) -+ result = repositoriesblocklist._calc_internal_blocklist( -+ repomap_opts_only, _NO_TASKS, enabled_repoids=set() -+ ) - - assert not result - - --def test_blacklist_produced_when_optional_repo_disabled(monkeypatch, repofacts_opts_disabled, repomap_opts_only): -+def test_blocklist_generated_when_crb_disabled(monkeypatch, repomap_opts_only): - """ -- Tests whether a correct blacklist is generated when there is disabled optional repo on the system. -+ Blocklist is generated when CRB repos are disabled on the source system. - """ -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked( -+ arch='x86_64', src_ver='8.10', dst_ver='9.6', dst_distro='rhel' -+ )) -+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) - -- monkeypatch.setattr( -- api, -- "current_actor", -- CurrentActorMocked(msgs=[repofacts_opts_disabled]), -+ result = repositoriesblocklist._calc_internal_blocklist( -+ repomap_opts_only, _NO_TASKS, enabled_repoids=set() - ) -- monkeypatch.setattr(api, "produce", produce_mocked()) -- monkeypatch.setattr(reporting, "create_report", produce_mocked()) -- -- result = repositoriesblocklist.get_blocklisted_repoids(repomap_opts_only) - -- assert result, 'A blacklist should get generated.' -+ assert result, 'A blocklist should get generated.' - -- expected_blacklisted_repoid = 'codeready-builder-for-rhel-9-x86_64-rpms' -- err_msg = 'Blacklist does not contain expected repoid.' -- assert expected_blacklisted_repoid in result, err_msg -+ expected_blocklisted_repoid = 'codeready-builder-for-rhel-9-x86_64-rpms' -+ err_msg = 'Blocklist does not contain expected repoid.' -+ assert expected_blocklisted_repoid in result, err_msg - - --def test_no_blacklist_produced_when_optional_repo_enabled(monkeypatch, repofacts_opts_disabled, repomap_opts_only): -+def test_no_blocklist_when_crb_enabled_on_source(monkeypatch, repomap_opts_only): - """ -- Tests whether an empty set is returned when an optional repository is enabled. -- -- Data are set up in such a fashion so that the determined blacklist would not be empty. -+ Empty set is returned when a CRB repository is enabled on the source system. - """ -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked( -+ arch='x86_64', src_ver='8.10', dst_ver='9.6', dst_distro='rhel' -+ )) - -- repofacts_opts_disabled.repositories[0].data[0].enabled = True -- -- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[repofacts_opts_disabled])) -- -- result = repositoriesblocklist.get_blocklisted_repoids(repomap_opts_only) -+ result = repositoriesblocklist._calc_internal_blocklist( -+ repomap_opts_only, _NO_TASKS, -+ enabled_repoids={'codeready-builder-for-rhel-8-x86_64-rpms'} -+ ) - - assert not result - - --def test_repositoriesblacklist_not_empty(monkeypatch, repofacts_opts_disabled, repomap_opts_only): -+def test_blocklist_not_empty_with_mocked_crb_repos(monkeypatch, repomap_opts_only): - """ -- Tests whether the correct set of blacklisted repoids is returned. -+ Non-empty blocklist is returned when _get_crb_repos returns repos to exclude. - """ -- - blacklisted_repoid = 'test' -- monkeypatch.setattr(repositoriesblocklist, "_get_repoids_to_exclude", lambda dummy_mapping: {blacklisted_repoid}) -- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[repofacts_opts_disabled])) -- monkeypatch.setattr(api, "produce", produce_mocked()) -- monkeypatch.setattr(reporting, "create_report", produce_mocked()) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked( -+ arch='x86_64', src_ver='8.10', dst_ver='9.6', dst_distro='rhel' -+ )) -+ monkeypatch.setattr( -+ repositoriesblocklist, '_are_crb_repos_disabled', lambda _m, _e: True -+ ) -+ monkeypatch.setattr( -+ repositoriesblocklist, '_get_crb_repos', lambda _m, _v: {blacklisted_repoid} -+ ) -+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) - -- result = repositoriesblocklist.get_blocklisted_repoids(repomap_opts_only) -+ result = repositoriesblocklist._calc_internal_blocklist( -+ repomap_opts_only, _NO_TASKS, enabled_repoids=set() -+ ) - assert blacklisted_repoid in result - assert reporting.create_report.called == 1 - - --def test_repositoriesblacklist_empty(monkeypatch, repofacts_opts_disabled, repomap_opts_only): -+def test_blocklist_empty_with_mocked_empty_crb_repos(monkeypatch, repomap_opts_only): - """ -- Tests whether an empty set is returned if there are some disabled optional -- repos, but an empty blacklist is determined from the repo mapping data. -+ Empty set returned when _get_crb_repos returns no repos, even though -+ CRB is disabled on the source. - """ -- -- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[repofacts_opts_disabled])) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked( -+ arch='x86_64', src_ver='8.10', dst_ver='9.6', dst_distro='rhel' -+ )) -+ monkeypatch.setattr( -+ repositoriesblocklist, '_are_crb_repos_disabled', lambda _m, _e: True -+ ) - monkeypatch.setattr( -- repositoriesblocklist, -- "_get_repoids_to_exclude", -- lambda dummy_mapping: set() -+ repositoriesblocklist, '_get_crb_repos', lambda _m, _v: set() - ) -+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) - -- result = repositoriesblocklist.get_blocklisted_repoids(repomap_opts_only) -+ result = repositoriesblocklist._calc_internal_blocklist( -+ repomap_opts_only, _NO_TASKS, enabled_repoids=set() -+ ) - assert not result - - - @pytest.mark.parametrize( -- ("enabled_repo", "exp_report_title", "message_produced"), -+ ('custom_repoids', 'exp_report_title', 'exp_blocklist_empty'), - [ -- ("codeready-builder-for-rhel-9-x86_64-rpms", "Using repository not supported by Red Hat", False), -- ("some_other_enabled_repo", "Excluded target system repositories", True), -- (None, "Excluded target system repositories", True), -+ ( -+ {'codeready-builder-for-rhel-9-x86_64-rpms'}, -+ None, -+ True, -+ ), -+ ( -+ {'some_other_enabled_repo'}, -+ 'Excluded target system repositories', -+ False, -+ ), -+ ( -+ set(), -+ 'Excluded target system repositories', -+ False, -+ ), - ], - ) --def test_enablerepo_option(monkeypatch, -- repofacts_opts_disabled, -- repomap_opts_only, -- enabled_repo, -- exp_report_title, -- message_produced): -+def test_custom_enablerepo_effect(monkeypatch, repomap_opts_only, -+ custom_repoids, exp_report_title, -+ exp_blocklist_empty): - """ -- Tests whether the actor respects CustomTargetRepository messages when constructing the blacklist. -+ When a CRB repo is in external_tasks.custom the blocklist is empty. -+ When custom contains only non-CRB repos or is empty, CRB repos are excluded and reported. - """ -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked( -+ arch='x86_64', src_ver='8.10', dst_ver='9.6', dst_distro='rhel' -+ )) -+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) - -- msgs_to_feed = [repofacts_opts_disabled] -+ external_tasks = ExternalRepoSetupTasks( -+ to_enable=set(), to_block=set(), custom=custom_repoids -+ ) - -- if enabled_repo: -- msgs_to_feed.append(CustomTargetRepository(repoid=enabled_repo)) -- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs_to_feed)) -- monkeypatch.setattr(api, 'produce', produce_mocked()) -- monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) -- result = repositoriesblocklist.get_blocklisted_repoids(repomap_opts_only) -- assert reporting.create_report.report_fields["title"] == exp_report_title -- if message_produced: -- assert result -+ result = repositoriesblocklist._calc_internal_blocklist( -+ repomap_opts_only, external_tasks, enabled_repoids=set() -+ ) -+ -+ if exp_report_title: -+ assert reporting.create_report.report_fields['title'] == exp_report_title - else: -+ assert reporting.create_report.called == 0 -+ -+ if exp_blocklist_empty: - assert not result -+ else: -+ assert result -+ -+ -+def test_get_crb_repos_filters_by_architecture(monkeypatch): -+ """Only CRB repos matching the current architecture are returned.""" -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked( -+ arch='x86_64', src_ver='8.10', dst_ver='9.6', dst_distro='rhel' -+ )) -+ -+ repos = [ -+ PESIDRepositoryEntry( -+ pesid='rhel9-CRB', major_version='9', -+ repoid='crb-x86_64', rhui='', arch='x86_64', -+ channel='ga', repo_type='rpm', distro='rhel', -+ ), -+ PESIDRepositoryEntry( -+ pesid='rhel9-CRB', major_version='9', -+ repoid='crb-aarch64', rhui='', arch='aarch64', -+ channel='ga', repo_type='rpm', distro='rhel', -+ ), -+ ] -+ repo_mapping = RepositoriesMapping(mapping=[], repositories=repos) -+ -+ result = repositoriesblocklist._get_crb_repos(repo_mapping, '9') -+ -+ assert result == {'crb-x86_64'} -+ -+ -+def test_no_report_for_non_rhel_distro(monkeypatch, repomap_opts_only): -+ """No exclusion report generated for non-RHEL distros, but repos are still excluded.""" -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked( -+ arch='x86_64', src_ver='8.10', dst_ver='9.6', dst_distro='centos' -+ )) -+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) -+ -+ result = repositoriesblocklist._calc_internal_blocklist( -+ repomap_opts_only, _NO_TASKS, enabled_repoids=set() -+ ) -+ -+ assert result -+ assert reporting.create_report.called == 0 - - - def _get_produced_blocklisted(): -@@ -226,11 +266,11 @@ def test_compute_blocklist_merges_internal_and_external(monkeypatch): - to_enable={'some-repo'}, to_block={'ext-blocked-1', 'ext-blocked-2'}, custom=set() - ) - monkeypatch.setattr( -- repositoriesblocklist, 'get_blocklisted_repoids', lambda _repo_map: {'crb-repo-1'} -+ repositoriesblocklist, '_calc_internal_blocklist', lambda _rm, _et, _er: {'crb-repo-1'} - ) - monkeypatch.setattr(api, 'produce', produce_mocked()) - -- full_blocklist = repositoriesblocklist.compute_blocklist(None, external_tasks) -+ full_blocklist = repositoriesblocklist.compute_blocklist(None, external_tasks, set()) - - assert full_blocklist == {'crb-repo-1', 'ext-blocked-1', 'ext-blocked-2'} - -@@ -243,11 +283,11 @@ def test_compute_blocklist_no_messages_when_empty(monkeypatch): - """No blocklist messages are produced when blocklist is empty.""" - external_tasks = ExternalRepoSetupTasks(to_enable=set(), to_block=set(), custom=set()) - monkeypatch.setattr( -- repositoriesblocklist, 'get_blocklisted_repoids', lambda _repo_map: set() -+ repositoriesblocklist, '_calc_internal_blocklist', lambda _rm, _et, _er: set() - ) - monkeypatch.setattr(api, 'produce', produce_mocked()) - -- full_blocklist = repositoriesblocklist.compute_blocklist(None, external_tasks) -+ full_blocklist = repositoriesblocklist.compute_blocklist(None, external_tasks, set()) - - assert full_blocklist == set() - assert len(_get_produced_blocklisted()) == 0 -@@ -257,11 +297,11 @@ def test_compute_blocklist_only_internal(monkeypatch): - """Only internal blocklist when no external tasks are present.""" - external_tasks = ExternalRepoSetupTasks(to_enable=set(), to_block=set(), custom=set()) - monkeypatch.setattr( -- repositoriesblocklist, 'get_blocklisted_repoids', lambda _repo_map: {'crb-repo-1'} -+ repositoriesblocklist, '_calc_internal_blocklist', lambda _rm, _et, _er: {'crb-repo-1'} - ) - monkeypatch.setattr(api, 'produce', produce_mocked()) - -- full_blocklist = repositoriesblocklist.compute_blocklist(None, external_tasks) -+ full_blocklist = repositoriesblocklist.compute_blocklist(None, external_tasks, set()) - - assert full_blocklist == {'crb-repo-1'} - assert len(_get_produced_blocklisted()) == 1 -@@ -273,11 +313,11 @@ def test_compute_blocklist_aggregated_external_tasks(monkeypatch): - to_enable={'repo-a', 'repo-b'}, to_block={'ext-1', 'ext-2', 'ext-3'}, custom=set() - ) - monkeypatch.setattr( -- repositoriesblocklist, 'get_blocklisted_repoids', lambda _repo_map: set() -+ repositoriesblocklist, '_calc_internal_blocklist', lambda _rm, _et, _er: set() - ) - monkeypatch.setattr(api, 'produce', produce_mocked()) - -- full_blocklist = repositoriesblocklist.compute_blocklist(None, external_tasks) -+ full_blocklist = repositoriesblocklist.compute_blocklist(None, external_tasks, set()) - - assert full_blocklist == {'ext-1', 'ext-2', 'ext-3'} - -@@ -288,11 +328,11 @@ def test_compute_blocklist_overlapping_internal_and_external(monkeypatch): - to_enable=set(), to_block={'shared-repo', 'ext-only'}, custom=set() - ) - monkeypatch.setattr( -- repositoriesblocklist, 'get_blocklisted_repoids', lambda _repo_map: {'shared-repo', 'internal-only'} -+ repositoriesblocklist, '_calc_internal_blocklist', lambda _rm, _et, _er: {'shared-repo', 'internal-only'} - ) - monkeypatch.setattr(api, 'produce', produce_mocked()) - -- full_blocklist = repositoriesblocklist.compute_blocklist(None, external_tasks) -+ full_blocklist = repositoriesblocklist.compute_blocklist(None, external_tasks, set()) - - assert full_blocklist == {'shared-repo', 'internal-only', 'ext-only'} - -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py -index 97aaad33..0f28d656 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py -@@ -41,10 +41,11 @@ def test_process_orchestration(monkeypatch): - call_log.append('scan_repositories') - return fake_repo_map - -- def mock_compute_blocklist(repo_mapping, external_tasks): -+ def mock_compute_blocklist(repo_mapping, external_tasks, enabled_repoids): - call_log.append('compute_blocklist') - assert repo_mapping is fake_repo_map - assert external_tasks is fake_external_tasks -+ assert enabled_repoids is fake_enabled_repoids - return fake_blocklist - - def mock_scan_pes_events(repo_mapping, blacklisted_repoids, enabled_repoids): -diff --git a/repos/system_upgrade/common/models/repositoriessetuptasks.py b/repos/system_upgrade/common/models/repositoriessetuptasks.py -index 35f627e4..88056d97 100644 ---- a/repos/system_upgrade/common/models/repositoriessetuptasks.py -+++ b/repos/system_upgrade/common/models/repositoriessetuptasks.py -@@ -10,6 +10,13 @@ class RepositoriesSetupTasks(Model): - upgrade process. This information should be processed by an actor dedicated to manage - repositories. - * 'to_block' field consists of a list of repositories that should be ignored during upgrade process. -+ -+ The priority order of the requests is following: -+ to_enable < to_block < "external custom enablement request" -+ The external custom request can be made by user: -+ -+ * execute leapp with --enablerepo option -+ * using configuration files - """ - topic = SystemFactsTopic - --- -2.54.0 - diff --git a/0101-5-9-repositoriesmapping-scan_repositories-load_repos.patch b/0101-5-9-repositoriesmapping-scan_repositories-load_repos.patch deleted file mode 100644 index 901db07..0000000 --- a/0101-5-9-repositoriesmapping-scan_repositories-load_repos.patch +++ /dev/null @@ -1,288 +0,0 @@ -From 614a7f3d793ce1d47b7360a4133f2e605cdbe8a3 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Sat, 6 Jun 2026 12:30:55 +0200 -Subject: [PATCH 101/105] [5/9] repositoriesmapping: scan_repositories -> - load_repositories_mapping - -The original `scan_repositories()` name was confusing and it did not -reflect the purpose of the function. The new name -`load_repositories_mapping()` seems to be more intuitive. - -Also dropped obsolete check for the "repomap.csv" file. The file existed -only on RHEL 7, more than 3 years ago. So it's a good time to remove -this relict from the past. - -Unit tests adjusted with assistance of AI. - -Jira: RHEL-115867 -Assisted-by: Claude Code (Opus 4.6) -Signed-off-by: Petr Stodulka ---- - .../libraries/repomap_loader.py | 31 ++++++-------- - .../libraries/targetcontentresolver.py | 4 +- - .../tests/test_targetcontentresolver.py | 12 +++--- - .../tests/unit_test_repomap_loader.py | 40 +++++++++---------- - 4 files changed, 41 insertions(+), 46 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repomap_loader.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repomap_loader.py -index 52b8337d..81716f01 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repomap_loader.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repomap_loader.py -@@ -9,9 +9,6 @@ from leapp.libraries.stdlib import api - from leapp.models import PESIDRepositoryEntry, RepoMapEntry, RepositoriesMapping - from leapp.models.fields import ModelViolationError - --OLD_REPOMAP_FILE = 'repomap.csv' --"""The name of the old, deprecated repository mapping file (no longer used).""" -- - REPOMAP_FILE = 'repomap.json' - """The name of the new repository mapping file.""" - -@@ -159,23 +156,21 @@ def _read_repofile(repofile): - return repofile_data - - --def scan_repositories(read_repofile_func=_read_repofile): -+def load_repositories_mapping(read_repofile_func=_read_repofile): - """ -- Scan the repository mapping file and produce RepositoriesMap msg. -+ Load the mapping of DNF repositories related to the current upgrade path. -+ -+ Read the mapping from the repomap.json file and filter out data irrelevant -+ for the current and target system major version. -+ -+ Produce and return the RepositoriesMapping message. - -- See the description of the actor for more details. -+ :param read_repofile_func: The function that accept filename string to read the repomap file and return dict -+ :type read_repofile_func: Callable[[str], Dict] -+ :rtype: RepositoriesMapping -+ :raises StopActorExecutionError: When cannot produce valid RepositoriesMapping - """ - # TODO: add filter based on the current arch -- # TODO: deprecate the product type and introduce the "channels" ?.. more or less -- # NOTE: product type is changed, now it's channel: eus,e4s,aus,tus,ga,beta -- -- if os.path.exists(os.path.join('/etc/leapp/files', OLD_REPOMAP_FILE)): -- # NOTE: what about creating the report (instead of warning) -- api.current_logger().warning( -- 'The old repomap file /etc/leapp/files/repomap.csv is present.' -- ' The file has been replaced by the repomap.json file and it is' -- ' not used anymore.' -- ) - - json_data = read_repofile_func(REPOMAP_FILE) - try: -@@ -188,7 +183,6 @@ def scan_repositories(read_repofile_func=_read_repofile): - repositories=repomap_data.get_repositories(valid_major_versions) - ) - api.produce(repositories_mapping) -- return repositories_mapping - except ModelViolationError as err: - err_message = ( - 'The repository mapping file is invalid: ' -@@ -202,4 +196,5 @@ def scan_repositories(read_repofile_func=_read_repofile): - except ValueError as err: - # The error should contain enough information, so we do not need to clarify it further - _inhibit_upgrade('The repository mapping file is invalid: {}'.format(err)) -- return None -+ -+ return repositories_mapping -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -index e70346b0..b66168e6 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -@@ -2,7 +2,7 @@ from collections import namedtuple - - from leapp.exceptions import StopActorExecutionError - from leapp.libraries.actor import pes_events_scanner, repositoriesblocklist, setuptargetrepos --from leapp.libraries.actor.repomap_loader import scan_repositories -+from leapp.libraries.actor.repomap_loader import load_repositories_mapping - from leapp.libraries.stdlib import api - from leapp.models import CustomTargetRepository, RepositoriesBlacklisted, RepositoriesFacts, RepositoriesSetupTasks - from leapp.utils.deprecation import suppress_deprecation -@@ -108,7 +108,7 @@ def process(): - configuration. - """ - indata = InputData() -- repositories_map_msg = scan_repositories() -+ repositories_map_msg = load_repositories_mapping() - blocklisted_repoids = repositoriesblocklist.compute_blocklist( - repositories_map_msg, - indata.external_tasks, -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py -index 0f28d656..970fd797 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py -@@ -19,7 +19,7 @@ from leapp.utils.deprecation import suppress_deprecation - def test_process_orchestration(monkeypatch): - """ - Tests that process() wires data between the four stages correctly: -- scan_repositories -> compute_blocklist -> scan_pes_events -> setup_target_repos. -+ load_repositories_mapping -> compute_blocklist -> scan_pes_events -> setup_target_repos. - """ - call_log = [] - -@@ -37,8 +37,8 @@ def test_process_orchestration(monkeypatch): - self.external_tasks = fake_external_tasks - self.enabled_repoids = fake_enabled_repoids - -- def mock_scan_repositories(): -- call_log.append('scan_repositories') -+ def mock_load_repositories_mapping(): -+ call_log.append('load_repositories_mapping') - return fake_repo_map - - def mock_compute_blocklist(repo_mapping, external_tasks, enabled_repoids): -@@ -68,8 +68,8 @@ def test_process_orchestration(monkeypatch): - FakeInputData, - ) - monkeypatch.setattr( -- 'leapp.libraries.actor.targetcontentresolver.scan_repositories', -- mock_scan_repositories, -+ 'leapp.libraries.actor.targetcontentresolver.load_repositories_mapping', -+ mock_load_repositories_mapping, - ) - monkeypatch.setattr( - 'leapp.libraries.actor.targetcontentresolver.repositoriesblocklist.compute_blocklist', -@@ -88,7 +88,7 @@ def test_process_orchestration(monkeypatch): - - assert call_log == [ - 'InputData', -- 'scan_repositories', -+ 'load_repositories_mapping', - 'compute_blocklist', - 'scan_pes_events', - 'setup_target_repos', -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/tests/unit_test_repomap_loader.py b/repos/system_upgrade/common/actors/targetcontentresolver/tests/unit_test_repomap_loader.py -index 50cf5962..be9b3bb2 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/tests/unit_test_repomap_loader.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/tests/unit_test_repomap_loader.py -@@ -32,10 +32,10 @@ def test_scan_existing_valid_data(monkeypatch, adjust_cwd): - monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(src_ver='7.9', dst_ver='8.4')) - monkeypatch.setattr(api, 'produce', produce_mocked()) - -- result = repomap_loader.scan_repositories(lambda dummy: data) -+ result = repomap_loader.load_repositories_mapping(lambda dummy: data) - - assert api.produce.called, 'Actor did not produce any message when deserializing valid repomap data.' -- assert result is not None, 'scan_repositories should return the RepositoriesMapping message.' -+ assert result is not None, 'load_repositories_mapping should return the RepositoriesMapping message.' - assert result is api.produce.model_instances[0], ( - 'The returned mapping should be the same object that was produced.' - ) -@@ -126,9 +126,9 @@ def test_scan_existing_valid_data(monkeypatch, adjust_cwd): - assert expected_pesid_repo in pesid_repos, fail_description - - --def test_scan_repositories_with_missing_data(monkeypatch): -+def test_load_repositories_mapping_with_missing_data(monkeypatch): - """ -- Tests whether the scanning process fails gracefully when no data are read. -+ Tests whether the loading process fails gracefully when no data are read. - """ - mocked_actor = CurrentActorMocked(src_ver='7.9', dst_ver='8.4', msgs=[]) - -@@ -144,25 +144,25 @@ def test_scan_repositories_with_missing_data(monkeypatch): - monkeypatch.setattr(fetch, 'read_or_fetch', read_or_fetch_mocked) - - with pytest.raises(StopActorExecutionError) as missing_data_error: -- repomap_loader.scan_repositories() -+ repomap_loader.load_repositories_mapping() - assert 'does not contain a valid JSON object' in str(missing_data_error) - - --def test_scan_repositories_with_empty_data(monkeypatch): -+def test_load_repositories_mapping_with_empty_data(monkeypatch): - """ -- Tests whether the scanning process fails gracefully when empty json data received. -+ Tests whether the loading process fails gracefully when empty json data received. - """ - - monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(src_ver='7.9', dst_ver='8.4')) - monkeypatch.setattr(api, 'produce', produce_mocked()) - - with pytest.raises(StopActorExecutionError) as empty_data_error: -- repomap_loader.scan_repositories(lambda dummy: {}) -+ repomap_loader.load_repositories_mapping(lambda dummy: {}) - assert 'the JSON is missing a required field' in str(empty_data_error) - - - @pytest.mark.parametrize('version_format', ('0.0.0', '1.0.1', '1.1.0', '2.0.0')) --def test_scan_repositories_with_bad_json_data_version(monkeypatch, version_format): -+def test_load_repositories_mapping_with_bad_json_data_version(monkeypatch, version_format): - """ - Tests whether the json data is checked for the version field and error is raised if the version - does not match the latest one. -@@ -179,12 +179,12 @@ def test_scan_repositories_with_bad_json_data_version(monkeypatch, version_forma - monkeypatch.setattr(api, 'produce', produce_mocked()) - - with pytest.raises(StopActorExecutionError) as bad_version_error: -- repomap_loader.scan_repositories(lambda dummy: json_data) -+ repomap_loader.load_repositories_mapping(lambda dummy: json_data) - - assert 'mapping file is invalid' in str(bad_version_error) - - --def test_scan_repositories_with_mapping_to_pesid_without_repos(monkeypatch): -+def test_load_repositories_mapping_with_mapping_to_pesid_without_repos(monkeypatch): - """ - Tests that the loading of repositories mapping recognizes when there is a mapping with target pesid that does - not have any repositories and inhibits the upgrade. -@@ -225,12 +225,12 @@ def test_scan_repositories_with_mapping_to_pesid_without_repos(monkeypatch): - monkeypatch.setattr(api, 'produce', produce_mocked()) - - with pytest.raises(StopActorExecutionError) as error_info: -- repomap_loader.scan_repositories(lambda dummy: json_data) -+ repomap_loader.load_repositories_mapping(lambda dummy: json_data) - - assert 'pesid is not related to any repository' in error_info.value.message - - --def test_scan_repositories_with_repo_entry_missing_required_fields(monkeypatch): -+def test_load_repositories_mapping_with_repo_entry_missing_required_fields(monkeypatch): - """ - Tests whether deserialization of pesid repo entries missing some of the required fields - is handled internally and StopActorExecutionError is propagated to the user. -@@ -279,12 +279,12 @@ def test_scan_repositories_with_repo_entry_missing_required_fields(monkeypatch): - monkeypatch.setattr(api, 'produce', produce_mocked()) - - with pytest.raises(StopActorExecutionError) as error_info: -- repomap_loader.scan_repositories(lambda dummy: json_data) -+ repomap_loader.load_repositories_mapping(lambda dummy: json_data) - - assert 'the JSON is missing a required field' in error_info.value.message - - --def test_scan_repositories_with_repo_entry_mapping_target_not_a_list(monkeypatch): -+def test_load_repositories_mapping_with_repo_entry_mapping_target_not_a_list(monkeypatch): - """ - Tests whether deserialization of a mapping entry that has its target field set to a string - is handled internally and StopActorExecutionError is propagated to the user. -@@ -333,18 +333,18 @@ def test_scan_repositories_with_repo_entry_mapping_target_not_a_list(monkeypatch - monkeypatch.setattr(api, 'produce', produce_mocked()) - - with pytest.raises(StopActorExecutionError) as error_info: -- repomap_loader.scan_repositories(lambda dummy: json_data) -+ repomap_loader.load_repositories_mapping(lambda dummy: json_data) - - assert 'repository mapping file is invalid' in error_info.value.message - - --def test_scan_repositories_returns_none_on_error(monkeypatch): -+def test_load_repositories_mapping_raises_on_invalid_data(monkeypatch): - """ -- Tests that scan_repositories returns None when the data is invalid -- and a StopActorExecutionError is raised. -+ Tests that load_repositories_mapping raises StopActorExecutionError -+ when the data is invalid. - """ - monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(src_ver='7.9', dst_ver='8.4')) - monkeypatch.setattr(api, 'produce', produce_mocked()) - - with pytest.raises(StopActorExecutionError): -- repomap_loader.scan_repositories(lambda dummy: {}) -+ repomap_loader.load_repositories_mapping(lambda dummy: {}) --- -2.54.0 - diff --git a/0102-6-9-targetcontentresolver-operate-with-RepoMapDataHa.patch b/0102-6-9-targetcontentresolver-operate-with-RepoMapDataHa.patch deleted file mode 100644 index 647e36f..0000000 --- a/0102-6-9-targetcontentresolver-operate-with-RepoMapDataHa.patch +++ /dev/null @@ -1,615 +0,0 @@ -From d28e4c283de9ac3e6f172c900f58f91e7cbb66d1 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Sun, 7 Jun 2026 01:31:39 +0200 -Subject: [PATCH 102/105] [6/9] targetcontentresolver: operate with - RepoMapDataHandler - -Originally various libraries required RepositoriesMapping message -in the input parameters. Then some of them just initialized -RepoMapDataHandler (which requires to consume RHUIInfo message) -and in case of the `repositoriesblocklist` library it even duplicated -functionality implemented in the handler. - -The handler is now initialized inside the orchestrator and all other -libraries accept just the handler as the input parameter. This leads -to another deduplication and simplification of the codebase. - -Unit tests adjusted with assistance of AI. - -Jira: RHEL-115867 -Assisted-by: Claude Code (Opus 4.6) -Signed-off-by: Petr Stodulka ---- - .../libraries/pes_events_scanner.py | 27 +++++------ - .../libraries/repositoriesblocklist.py | 47 +++++++++---------- - .../libraries/setuptargetrepos.py | 14 ++---- - .../libraries/targetcontentresolver.py | 24 ++++++++-- - .../tests/test_pes_event_scanner.py | 5 +- - .../tests/test_repositoriesblocklist.py | 41 ++++++++++++---- - .../tests/test_setuptargetrepos.py | 23 +++++---- - .../tests/test_targetcontentresolver.py | 20 ++++---- - 8 files changed, 116 insertions(+), 85 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/pes_events_scanner.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/pes_events_scanner.py -index cbc99adf..96f26a59 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/pes_events_scanner.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/pes_events_scanner.py -@@ -17,7 +17,6 @@ from leapp.models import ( - PESIDRepositoryEntry, - PESRpmTransactionTasks, - RepositoriesFacts, -- RHUIInfo, - RpmTransactionTasks - ) - -@@ -112,6 +111,7 @@ def get_relevant_releases(events): - - - def _get_enabled_modules(): -+ # TODO(pstodulk): take a look - enabled_modules_msgs = api.consume(EnabledModules) - enabled_modules_msg = next(enabled_modules_msgs, None) - if list(enabled_modules_msgs): -@@ -357,22 +357,20 @@ def get_enabled_repoids(): - return enabled_repoids - - --def get_pesid_to_repoid_map(target_pesids, repositories_map_msg, enabled_repoids): -+def get_pesid_to_repoid_map(target_pesids, repomap_handler, enabled_repoids): - """ - Get a dictionary mapping all PESID repositories to their corresponding repoid. - - :param target_pesids: The set of target PES IDs needed to be mapped -- :param repositories_map_msg: RepositoriesMapping message. -+ :param repomap_handler: Operator to work with the repositories mapping data -+ :type repomap_handler: repomap_calc.RepoMapDataHandler - :return: Dictionary mapping the target_pesids to their corresponding repoid - """ -- rhui_info = next(api.consume(RHUIInfo), None) -- cloud_provider = rhui_info.provider if rhui_info else '' -- -- repomap_handler = repomap_calc.RepoMapDataHandler(repositories_map_msg, cloud_provider=cloud_provider) -- - # NOTE: We have to calculate expected target repositories like in the setuptargetrepos actor. - # It's planned to handle this in different a way in future... -- -+ # TODO(pstodulk): ?? hmm..what about moving it to the orchestrator? -+ # TODO(pstodulk): what about returning requests just for PESIDS? Setuptargetrepo -+ # lib will pick the right one for us later. - default_channels = repomap_calc.get_default_repository_channels(repomap_handler, enabled_repoids) - repomap_handler.set_default_channels(default_channels) - -@@ -432,7 +430,7 @@ def get_pesid_to_repoid_map(target_pesids, repositories_map_msg, enabled_repoids - return repositories_mapping - - --def replace_pesids_with_repoids_in_packages(packages, source_pkgs_repoids, repositories_map_msg, enabled_repoids): -+def replace_pesids_with_repoids_in_packages(packages, source_pkgs_repoids, repomap_handler, enabled_repoids): - """Replace packages with PESID in their .repository field with ones that have repoid providing the package.""" - # We want to map only PESIDs - if some package had no events, it will its repository set to source system repoid - packages_with_pesid = {pkg for pkg in packages if pkg.repository not in source_pkgs_repoids} -@@ -440,7 +438,7 @@ def replace_pesids_with_repoids_in_packages(packages, source_pkgs_repoids, repos - - required_target_pesids = {pkg.repository for pkg in packages_with_pesid} - -- pesid_to_target_repoid_map = get_pesid_to_repoid_map(required_target_pesids, repositories_map_msg, enabled_repoids) -+ pesid_to_target_repoid_map = get_pesid_to_repoid_map(required_target_pesids, repomap_handler, enabled_repoids) - - packages_with_unknown_target_repoid = { - pkg -@@ -554,11 +552,12 @@ def include_instructions_from_transaction_configuration(rpm_tasks, transaction_c - modules_to_reset=modules_to_reset) - - --def scan_pes_events(repositories_map_msg, blacklisted_repoids, enabled_repoids): -+def scan_pes_events(repomap_handler, blacklisted_repoids, enabled_repoids): - """ - Process PES events and compute RPM transaction tasks. - -- :param repositories_map_msg: RepositoriesMapping message. -+ :param repomap_handler: Operator to work with the repositories mapping data -+ :type repomap_handler: repomap_calc.RepoMapDataHandler - :param blacklisted_repoids: Set of repoids to exclude from target packages. If None, an empty set is used. - :returns: Set of target repoids that need to be enabled, or None if no PES events are found. - :rtype: Optional[set] -@@ -588,7 +587,7 @@ def scan_pes_events(repositories_map_msg, blacklisted_repoids, enabled_repoids): - target_pkgs = replace_pesids_with_repoids_in_packages( - target_pkgs, - repoids_of_source_pkgs, -- repositories_map_msg, -+ repomap_handler, - enabled_repoids - ) - -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repositoriesblocklist.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repositoriesblocklist.py -index e526bcbd..c77a1f13 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repositoriesblocklist.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/repositoriesblocklist.py -@@ -32,44 +32,39 @@ def _report_excluded_repos(repos): - reporting.create_report(report) - - --def _get_crb_repos(repo_mapping, major_version): -+def _get_crb_repos(repo_mapping, flag_source_os): - """ -- Return set of relevant CRB repoids for the specified major version -+ Return set of relevant CRB repoids for the source or target OS. - -- Only CRB repositories for the current architecture and distribution are -- relevant. -- -- :param repo_mapping: RepositoriesMapping message. -- :type repo_mapping: RepositoriesMapping -- :param major_version: The OS major version. -- :type major_version: str -+ :param repo_mapping: Operator to work with the repositories mapping data -+ :type repo_mapping: repomap_calc.RepoMapDataHandler -+ :param flag_source_os: Set True for the source OS, False for the target OS. -+ :type flag_source_os: bool - :returns: A set of repoids with the specified pesid and major version. - :rtype: Set[str] - """ -- pesid = f'rhel{major_version}-CRB' - curr_arch = api.current_actor().configuration.architecture -- crb_repoids = set() -- for pesid_repo in repo_mapping.repositories: -- if pesid_repo.major_version != major_version or pesid_repo.arch != curr_arch: -- # irrelevant repository -- continue -- if pesid_repo.pesid == pesid and pesid_repo.major_version == major_version: -- crb_repoids.add(pesid_repo.repoid) -- return crb_repoids -+ if flag_source_os: -+ pesid = 'rhel{}-CRB'.format(get_source_major_version()) -+ crb_repos = repo_mapping.get_source_pesid_repos(pesid) -+ else: -+ pesid = 'rhel{}-CRB'.format(get_target_major_version()) -+ crb_repos = repo_mapping.get_target_pesid_repos(pesid) -+ return {repo.repoid for repo in crb_repos if repo.arch == curr_arch} - - - def _are_crb_repos_disabled(repo_mapping, enabled_repoids): - """ - Checks whether all CRB repositories are disabled. - -- :param repo_mapping: RepositoriesMapping message. -- :type repo_mapping: RepositoriesMapping -+ :param repo_mapping: Operator to work with the repositories mapping data -+ :type repo_mapping: repomap_calc.RepoMapDataHandler - :param enabled_repoids: Set of repoids enabled on the source system. - :type enabled_repoids: Set[str] - :returns: False if any CRB repositories are enabled on the source system, True otherwise. - :rtype: bool - """ -- return enabled_repoids.isdisjoint(_get_crb_repos(repo_mapping, get_source_major_version())) -+ return enabled_repoids.isdisjoint(_get_crb_repos(repo_mapping, True)) - - - def _calc_internal_blocklist(repo_mapping, external_tasks, enabled_repoids): -@@ -84,8 +79,8 @@ def _calc_internal_blocklist(repo_mapping, external_tasks, enabled_repoids): - Also report explicitly enabled and blocklisted CRB repositories, unless - CRB is already already enabled on the source system. - -- :param repo_mapping: RepositoriesMapping message. -- :type repo_mapping: RepositoriesMapping -+ :param repo_mapping: Operator to work with the repositories mapping data -+ :type repo_mapping: repomap_calc.RepoMapDataHandler - :param external_tasks: External repositories tasks represented by object with following fields: - - * ``to_enable`` - repositories that should be enabled -@@ -102,7 +97,7 @@ def _calc_internal_blocklist(repo_mapping, external_tasks, enabled_repoids): - # nothing to do - a CRB repo is enabled - return set() - -- repos_to_exclude = _get_crb_repos(repo_mapping, get_target_major_version()) -+ repos_to_exclude = _get_crb_repos(repo_mapping, False) - - # Do not exclude repositories explicitly required by user for the upgrade - manually_enabled_repos = external_tasks.custom & repos_to_exclude -@@ -149,8 +144,8 @@ def compute_blocklist(repo_mapping, external_tasks, enabled_repoids): - the in-place upgrade purpose. Currently there is no explicit custom - configuration to disable a repo. - -- :param repo_mapping: RepositoriesMapping message. -- :type repo_mapping: RepositoriesMapping -+ :param repo_mapping: Operator to work with the repositories mapping data -+ :type repo_mapping: repomap_calc.RepoMapDataHandler - :param external_tasks: External repositories tasks represented by object with following fields: - - * ``to_enable`` - repositories that should be enabled -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/setuptargetrepos.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/setuptargetrepos.py -index d6bfb91d..4f7ffd9f 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/setuptargetrepos.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/setuptargetrepos.py -@@ -8,7 +8,6 @@ from leapp.models import ( - DistroTargetRepository, - InstalledRPM, - RHELTargetRepository, -- RHUIInfo, - SkippedRepositories, - TargetRepositories, - UsedRepositories -@@ -58,12 +57,13 @@ def _get_mapped_repoids(repomap, src_repoids): - - - @suppress_deprecation(RHELTargetRepository) --def setup_target_repos(repositories_map_msg, pes_requested_repoids=None, -+def setup_target_repos(repomap_handler, pes_requested_repoids=None, - blacklisted_repoids=None, external_repoids_requests=None): - """ - Determine the final list of target repositories. - -- :param repositories_map_msg: RepositoriesMapping message. -+ :param repomap_handler: Operator to work with the repositories mapping data -+ :type repomap_handler: repomap_calc.RepoMapDataHandler - :param pes_requested_repoids: Set of repoids derived from PES events that need to be enabled. - :param blacklisted_repoids: Set of repoids to exclude from target repos. If None, an empty set is used. - :param external_repoids_requests: Set of repoids requested by external actors (e.g. satellite_upgrade_facts). -@@ -75,14 +75,6 @@ def setup_target_repos(repositories_map_msg, pes_requested_repoids=None, - custom_repos = _get_custom_target_repos() - repoids_from_installed_packages = _get_repoids_from_installed_packages() - -- # Setup repomap handler -- repo_mapping_msg = repositories_map_msg -- -- rhui_info = next(api.consume(RHUIInfo), None) -- cloud_provider = rhui_info.provider if rhui_info else '' -- -- repomap_handler = repomap_calc.RepoMapDataHandler(repo_mapping_msg, cloud_provider=cloud_provider) -- - # Filter set of repoids from installed packages so that it contains only repoids with mapping - repoids_from_installed_packages_with_mapping = _get_mapped_repoids( - repomap_handler, repoids_from_installed_packages -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -index b66168e6..942a2680 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -@@ -2,9 +2,16 @@ from collections import namedtuple - - from leapp.exceptions import StopActorExecutionError - from leapp.libraries.actor import pes_events_scanner, repositoriesblocklist, setuptargetrepos -+from leapp.libraries.actor.repomap_calc import RepoMapDataHandler - from leapp.libraries.actor.repomap_loader import load_repositories_mapping - from leapp.libraries.stdlib import api --from leapp.models import CustomTargetRepository, RepositoriesBlacklisted, RepositoriesFacts, RepositoriesSetupTasks -+from leapp.models import ( -+ CustomTargetRepository, -+ RepositoriesBlacklisted, -+ RepositoriesFacts, -+ RepositoriesSetupTasks, -+ RHUIInfo -+) - from leapp.utils.deprecation import suppress_deprecation - - ExternalRepoSetupTasks = namedtuple('ExternalRepoSetupTasks', ('to_enable', 'to_block', 'custom')) -@@ -86,6 +93,13 @@ class InputData(): - self.enabled_repoids.add(repo.repoid) - - -+def _init_repomap_handler(): -+ repomap_msg = load_repositories_mapping() -+ rhui_info = next(api.consume(RHUIInfo), None) -+ cloud_provider = rhui_info.provider if rhui_info else '' -+ return RepoMapDataHandler(repomap_msg, cloud_provider=cloud_provider) -+ -+ - def process(): - """ - Orchestrate the four stages of target content resolution. -@@ -108,20 +122,20 @@ def process(): - configuration. - """ - indata = InputData() -- repositories_map_msg = load_repositories_mapping() -+ repomap_handler = _init_repomap_handler() - blocklisted_repoids = repositoriesblocklist.compute_blocklist( -- repositories_map_msg, -+ repomap_handler, - indata.external_tasks, - indata.enabled_repoids - ) - pes_requested_repoids = pes_events_scanner.scan_pes_events( -- repositories_map_msg, -+ repomap_handler, - blocklisted_repoids, - indata.enabled_repoids - ) - - setuptargetrepos.setup_target_repos( -- repositories_map_msg, -+ repomap_handler, - pes_requested_repoids=pes_requested_repoids, - blacklisted_repoids=blocklisted_repoids, - external_repoids_requests=indata.external_tasks.to_enable, -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_pes_event_scanner.py b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_pes_event_scanner.py -index a1f3a6e1..c6f16823 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_pes_event_scanner.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_pes_event_scanner.py -@@ -13,6 +13,7 @@ from leapp.libraries.actor.pes_events_scanner import ( - reporting, - TransactionConfiguration - ) -+from leapp.libraries.actor.repomap_calc import RepoMapDataHandler - from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked, produce_mocked - from leapp.models import ( - DistributionSignedRPM, -@@ -265,12 +266,14 @@ def test_actor_performs(monkeypatch): - ), - ) - -+ repomap_handler = RepoMapDataHandler(repositories_mapping) -+ - produced_messages = produce_mocked() - created_report = create_report_mocked() - monkeypatch.setattr(api, 'produce', produced_messages) - monkeypatch.setattr(reporting, 'create_report', created_report) - -- pes_events_scanner.scan_pes_events(repositories_mapping, set(), {'rhel8-repo'}) -+ pes_events_scanner.scan_pes_events(repomap_handler, set(), {'rhel8-repo'}) - - assert produced_messages.called - -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_repositoriesblocklist.py b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_repositoriesblocklist.py -index 843c8f74..5a2d1705 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_repositoriesblocklist.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_repositoriesblocklist.py -@@ -2,6 +2,7 @@ import pytest - - from leapp import reporting - from leapp.libraries.actor import repositoriesblocklist -+from leapp.libraries.actor.repomap_calc import RepoMapDataHandler - from leapp.libraries.actor.targetcontentresolver import ExternalRepoSetupTasks - from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked, produce_mocked - from leapp.libraries.stdlib import api -@@ -56,8 +57,9 @@ def test_crb_repos_blocked_when_no_crb_on_source(monkeypatch, repomap_opts_only) - )) - monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) - -+ handler = RepoMapDataHandler(repomap_opts_only) - result = repositoriesblocklist._calc_internal_blocklist( -- repomap_opts_only, _NO_TASKS, enabled_repoids={'rhel-8-server-rpms'} -+ handler, _NO_TASKS, enabled_repoids={'rhel-8-server-rpms'} - ) - - assert 'codeready-builder-for-rhel-9-x86_64-rpms' in result -@@ -74,8 +76,9 @@ def test_empty_result_when_no_crb_pesid_in_mapping(monkeypatch, repomap_opts_onl - )) - monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) - -+ handler = RepoMapDataHandler(repomap_opts_only) - result = repositoriesblocklist._calc_internal_blocklist( -- repomap_opts_only, _NO_TASKS, enabled_repoids=set() -+ handler, _NO_TASKS, enabled_repoids=set() - ) - - assert not result -@@ -90,8 +93,9 @@ def test_blocklist_generated_when_crb_disabled(monkeypatch, repomap_opts_only): - )) - monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) - -+ handler = RepoMapDataHandler(repomap_opts_only) - result = repositoriesblocklist._calc_internal_blocklist( -- repomap_opts_only, _NO_TASKS, enabled_repoids=set() -+ handler, _NO_TASKS, enabled_repoids=set() - ) - - assert result, 'A blocklist should get generated.' -@@ -109,8 +113,9 @@ def test_no_blocklist_when_crb_enabled_on_source(monkeypatch, repomap_opts_only) - arch='x86_64', src_ver='8.10', dst_ver='9.6', dst_distro='rhel' - )) - -+ handler = RepoMapDataHandler(repomap_opts_only) - result = repositoriesblocklist._calc_internal_blocklist( -- repomap_opts_only, _NO_TASKS, -+ handler, _NO_TASKS, - enabled_repoids={'codeready-builder-for-rhel-8-x86_64-rpms'} - ) - -@@ -198,8 +203,9 @@ def test_custom_enablerepo_effect(monkeypatch, repomap_opts_only, - to_enable=set(), to_block=set(), custom=custom_repoids - ) - -+ handler = RepoMapDataHandler(repomap_opts_only) - result = repositoriesblocklist._calc_internal_blocklist( -- repomap_opts_only, external_tasks, enabled_repoids=set() -+ handler, external_tasks, enabled_repoids=set() - ) - - if exp_report_title: -@@ -232,21 +238,38 @@ def test_get_crb_repos_filters_by_architecture(monkeypatch): - ), - ] - repo_mapping = RepositoriesMapping(mapping=[], repositories=repos) -+ handler = RepoMapDataHandler(repo_mapping) - -- result = repositoriesblocklist._get_crb_repos(repo_mapping, '9') -+ result = repositoriesblocklist._get_crb_repos(handler, False) - - assert result == {'crb-x86_64'} - - --def test_no_report_for_non_rhel_distro(monkeypatch, repomap_opts_only): -+def test_no_report_for_non_rhel_distro(monkeypatch): - """No exclusion report generated for non-RHEL distros, but repos are still excluded.""" - monkeypatch.setattr(api, 'current_actor', CurrentActorMocked( -- arch='x86_64', src_ver='8.10', dst_ver='9.6', dst_distro='centos' -+ arch='x86_64', src_ver='8.10', dst_ver='9.6', src_distro='centos', dst_distro='centos' - )) - monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) - -+ centos_repomap = RepositoriesMapping( -+ mapping=[RepoMapEntry(source='rhel8-CRB', target=['rhel9-CRB'])], -+ repositories=[ -+ PESIDRepositoryEntry( -+ pesid='rhel8-CRB', major_version='8', -+ repoid='codeready-builder-for-centos-8-x86_64-rpms', -+ rhui='', arch='x86_64', channel='ga', repo_type='rpm', distro='centos', -+ ), -+ PESIDRepositoryEntry( -+ pesid='rhel9-CRB', major_version='9', -+ repoid='codeready-builder-for-centos-9-x86_64-rpms', -+ rhui='', arch='x86_64', channel='ga', repo_type='rpm', distro='centos', -+ ), -+ ] -+ ) -+ handler = RepoMapDataHandler(centos_repomap) - result = repositoriesblocklist._calc_internal_blocklist( -- repomap_opts_only, _NO_TASKS, enabled_repoids=set() -+ handler, _NO_TASKS, enabled_repoids=set() - ) - - assert result -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_setuptargetrepos.py b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_setuptargetrepos.py -index 652bf30a..5192e65a 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_setuptargetrepos.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_setuptargetrepos.py -@@ -1,6 +1,7 @@ - import pytest - - from leapp.libraries.actor import setuptargetrepos -+from leapp.libraries.actor.repomap_calc import RepoMapDataHandler - from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked - from leapp.libraries.stdlib import api - from leapp.models import ( -@@ -27,14 +28,13 @@ def test_minimal_execution(monkeypatch): - """ - Tests whether the actor does not fail if no messages except the RepositoriesMapping are provided. - """ -- msgs = [ -- RepositoriesMapping(mapping=[], repositories=[]) -- ] -+ repos_mapping = RepositoriesMapping(mapping=[], repositories=[]) -+ msgs = [repos_mapping] - - monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) - monkeypatch.setattr(api, 'produce', produce_mocked()) - -- setuptargetrepos.setup_target_repos(msgs[0]) -+ setuptargetrepos.setup_target_repos(RepoMapDataHandler(repos_mapping)) - - - def test_custom_repos(monkeypatch): -@@ -62,7 +62,8 @@ def test_custom_repos(monkeypatch): - monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) - monkeypatch.setattr(api, 'produce', produce_mocked()) - -- setuptargetrepos.setup_target_repos(repositories_mapping, blacklisted_repoids={'rhel-8-blacklisted-rpms'}) -+ handler = RepoMapDataHandler(repositories_mapping) -+ setuptargetrepos.setup_target_repos(handler, blacklisted_repoids={'rhel-8-blacklisted-rpms'}) - - assert api.produce.called - -@@ -82,8 +83,9 @@ def test_repositories_setup_tasks(monkeypatch): - monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) - monkeypatch.setattr(api, 'produce', produce_mocked()) - -+ handler = RepoMapDataHandler(repositories_mapping) - setuptargetrepos.setup_target_repos( -- repositories_mapping, -+ handler, - blacklisted_repoids={'rhel-8-blacklisted-rpms'}, - external_repoids_requests={'rhel-8-server-rpms', 'rhel-8-blacklisted-rpms'}) - -@@ -192,7 +194,8 @@ def test_repos_mapping_for_distro(monkeypatch, src_distro, dst_distro): - ) - monkeypatch.setattr(api, 'produce', produce_mocked()) - -- setuptargetrepos.setup_target_repos(repomap, blacklisted_repoids={'{}-9-blacklisted-rpms'.format(dst_distro)}) -+ handler = RepoMapDataHandler(repomap) -+ setuptargetrepos.setup_target_repos(handler, blacklisted_repoids={'{}-9-blacklisted-rpms'.format(dst_distro)}) - assert api.produce.called - - distro_repos = api.produce.model_instances[0].distro_repos -@@ -228,8 +231,9 @@ def test_pes_requested_repoids_added_to_target(monkeypatch): - monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) - monkeypatch.setattr(api, 'produce', produce_mocked()) - -+ handler = RepoMapDataHandler(repositories_mapping) - setuptargetrepos.setup_target_repos( -- repositories_mapping, -+ handler, - pes_requested_repoids={'pes-repo-1', 'pes-repo-2', 'pes-blocked'}, - blacklisted_repoids={'pes-blocked'}, - ) -@@ -255,8 +259,9 @@ def test_pes_and_external_repoids_combined(monkeypatch): - monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) - monkeypatch.setattr(api, 'produce', produce_mocked()) - -+ handler = RepoMapDataHandler(repositories_mapping) - setuptargetrepos.setup_target_repos( -- repositories_mapping, -+ handler, - pes_requested_repoids={'pes-repo'}, - blacklisted_repoids={'blocked-repo'}, - external_repoids_requests={'ext-repo', 'blocked-repo'}, -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py -index 970fd797..750f6f9e 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py -@@ -23,7 +23,7 @@ def test_process_orchestration(monkeypatch): - """ - call_log = [] - -- fake_repo_map = object() -+ fake_repomap_handler = object() - fake_blocklist = frozenset({'blocked-repo'}) - fake_external_tasks = ExternalRepoSetupTasks( - to_enable=frozenset({'ext-repo'}), to_block=frozenset(), custom=frozenset() -@@ -37,20 +37,20 @@ def test_process_orchestration(monkeypatch): - self.external_tasks = fake_external_tasks - self.enabled_repoids = fake_enabled_repoids - -- def mock_load_repositories_mapping(): -- call_log.append('load_repositories_mapping') -- return fake_repo_map -+ def mock_init_repomap_handler(): -+ call_log.append('_init_repomap_handler') -+ return fake_repomap_handler - - def mock_compute_blocklist(repo_mapping, external_tasks, enabled_repoids): - call_log.append('compute_blocklist') -- assert repo_mapping is fake_repo_map -+ assert repo_mapping is fake_repomap_handler - assert external_tasks is fake_external_tasks - assert enabled_repoids is fake_enabled_repoids - return fake_blocklist - - def mock_scan_pes_events(repo_mapping, blacklisted_repoids, enabled_repoids): - call_log.append('scan_pes_events') -- assert repo_mapping is fake_repo_map -+ assert repo_mapping is fake_repomap_handler - assert blacklisted_repoids is fake_blocklist - assert enabled_repoids is fake_enabled_repoids - return fake_pes_repoids -@@ -58,7 +58,7 @@ def test_process_orchestration(monkeypatch): - def mock_setup_target_repos(repo_mapping, pes_requested_repoids=None, - blacklisted_repoids=None, external_repoids_requests=None): - call_log.append('setup_target_repos') -- assert repo_mapping is fake_repo_map -+ assert repo_mapping is fake_repomap_handler - assert pes_requested_repoids is fake_pes_repoids - assert blacklisted_repoids is fake_blocklist - assert external_repoids_requests is fake_external_tasks.to_enable -@@ -68,8 +68,8 @@ def test_process_orchestration(monkeypatch): - FakeInputData, - ) - monkeypatch.setattr( -- 'leapp.libraries.actor.targetcontentresolver.load_repositories_mapping', -- mock_load_repositories_mapping, -+ 'leapp.libraries.actor.targetcontentresolver._init_repomap_handler', -+ mock_init_repomap_handler, - ) - monkeypatch.setattr( - 'leapp.libraries.actor.targetcontentresolver.repositoriesblocklist.compute_blocklist', -@@ -88,7 +88,7 @@ def test_process_orchestration(monkeypatch): - - assert call_log == [ - 'InputData', -- 'load_repositories_mapping', -+ '_init_repomap_handler', - 'compute_blocklist', - 'scan_pes_events', - 'setup_target_repos', --- -2.54.0 - diff --git a/0103-7-9-targetcontentresolver-setuptargetrepos-minor-upd.patch b/0103-7-9-targetcontentresolver-setuptargetrepos-minor-upd.patch deleted file mode 100644 index f9979ff..0000000 --- a/0103-7-9-targetcontentresolver-setuptargetrepos-minor-upd.patch +++ /dev/null @@ -1,262 +0,0 @@ -From c8efe616de0859c22de73573bb55d65949f2d180 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Sun, 7 Jun 2026 01:53:07 +0200 -Subject: [PATCH 103/105] [7/9] targetcontentresolver: setuptargetrepos: minor - updates - -* accept enabled repositories -* update the blacklist -> blocklist wording -* drop unneeded rename blocklisted_repoids -> excluded_repoids - - the rename was there just for the purpose to check whether - the blocklisted_repoids input parameter is a set or not. but we - know it will be always the set -* make all input parameters in setup_target_repos mandatory - - dropping default value which does not make sense anymore - -Unit tests adjusted with assistance of AI. - -Jira: RHEL-115867 -Co-authored-by: Matej Matuska -Assisted-by: Claude Code (Opus 4.6) -Signed-off-by: Petr Stodulka ---- - .../libraries/setuptargetrepos.py | 15 ++--- - .../libraries/targetcontentresolver.py | 3 +- - .../tests/test_setuptargetrepos.py | 56 ++++++++++++++----- - .../tests/test_targetcontentresolver.py | 7 ++- - 4 files changed, 54 insertions(+), 27 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/setuptargetrepos.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/setuptargetrepos.py -index 4f7ffd9f..4896bf9b 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/setuptargetrepos.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/setuptargetrepos.py -@@ -1,5 +1,4 @@ - from leapp.libraries.actor import repomap_calc --from leapp.libraries.actor.pes_events_scanner import get_enabled_repoids - from leapp.libraries.common.config import get_source_distro_id, get_target_distro_id - from leapp.libraries.common.config.version import get_source_major_version, get_source_version - from leapp.libraries.stdlib import api -@@ -57,21 +56,19 @@ def _get_mapped_repoids(repomap, src_repoids): - - - @suppress_deprecation(RHELTargetRepository) --def setup_target_repos(repomap_handler, pes_requested_repoids=None, -- blacklisted_repoids=None, external_repoids_requests=None): -+def setup_target_repos(repomap_handler, enabled_repoids, pes_requested_repoids, -+ blocklisted_repoids, external_repoids_requests): - """ - Determine the final list of target repositories. - - :param repomap_handler: Operator to work with the repositories mapping data - :type repomap_handler: repomap_calc.RepoMapDataHandler - :param pes_requested_repoids: Set of repoids derived from PES events that need to be enabled. -- :param blacklisted_repoids: Set of repoids to exclude from target repos. If None, an empty set is used. -+ :param blocklisted_repoids: Set of repoids to exclude from target repos. If None, an empty set is used. - :param external_repoids_requests: Set of repoids requested by external actors (e.g. satellite_upgrade_facts). - """ - # Load relevant data from messages - used_repoids_dict = _get_used_repo_dict() -- enabled_repoids = get_enabled_repoids() -- excluded_repoids = blacklisted_repoids if blacklisted_repoids is not None else set() - custom_repos = _get_custom_target_repos() - repoids_from_installed_packages = _get_repoids_from_installed_packages() - -@@ -117,7 +114,7 @@ def setup_target_repos(repomap_handler, pes_requested_repoids=None, - .format(target_pesid) - ) - continue -- if target_pesidrepo.repoid in excluded_repoids: -+ if target_pesidrepo.repoid in blocklisted_repoids: - api.current_logger().debug('Skipping the {} repo (excluded).'.format(target_pesidrepo.repoid)) - continue - target_distro_repoids.add(target_pesidrepo.repoid) -@@ -129,7 +126,7 @@ def setup_target_repos(repomap_handler, pes_requested_repoids=None, - all_requested_repoids.update(external_repoids_requests or set()) - - for repo in all_requested_repoids: -- if repo in excluded_repoids: -+ if repo in blocklisted_repoids: - api.current_logger().debug('Skipping the {} repo from setup task (excluded).'.format(repo)) - continue - target_distro_repoids.add(repo) -@@ -140,7 +137,7 @@ def setup_target_repos(repomap_handler, pes_requested_repoids=None, - else: - rhel_repos = [] - distro_repos = [DistroTargetRepository(repoid=repoid) for repoid in sorted(target_distro_repoids)] -- custom_repos = [repo for repo in custom_repos if repo.repoid not in excluded_repoids] -+ custom_repos = [repo for repo in custom_repos if repo.repoid not in blocklisted_repoids] - custom_repos = sorted(custom_repos, key=lambda x: x.repoid) - - # produce message about skipped repositories -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -index 942a2680..374f1498 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -@@ -136,7 +136,8 @@ def process(): - - setuptargetrepos.setup_target_repos( - repomap_handler, -+ indata.enabled_repoids, - pes_requested_repoids=pes_requested_repoids, -- blacklisted_repoids=blocklisted_repoids, -+ blocklisted_repoids=blocklisted_repoids, - external_repoids_requests=indata.external_tasks.to_enable, - ) -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_setuptargetrepos.py b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_setuptargetrepos.py -index 5192e65a..b2b4b8db 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_setuptargetrepos.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_setuptargetrepos.py -@@ -34,22 +34,28 @@ def test_minimal_execution(monkeypatch): - monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) - monkeypatch.setattr(api, 'produce', produce_mocked()) - -- setuptargetrepos.setup_target_repos(RepoMapDataHandler(repos_mapping)) -+ setuptargetrepos.setup_target_repos( -+ RepoMapDataHandler(repos_mapping), -+ enabled_repoids=set(), -+ pes_requested_repoids=set(), -+ blocklisted_repoids=set(), -+ external_repoids_requests=set(), -+ ) - - - def test_custom_repos(monkeypatch): - """ - Tests whether the CustomRepos provided to the actor are propagated to the TargetRepositories after -- blacklist filtering is applied on them. -+ blocklist filtering is applied on them. - """ - custom = CustomTargetRepository(repoid='rhel-8-server-rpms', - name='RHEL 8 Server (RPMs)', - baseurl='https://.../dist/rhel/server/8/os', - enabled=True) - -- blacklisted = CustomTargetRepository(repoid='rhel-8-blacklisted-rpms', -- name='RHEL 8 Blacklisted (RPMs)', -- baseurl='https://.../dist/rhel/blacklisted/8/os', -+ blocklisted = CustomTargetRepository(repoid='rhel-8-blocklisted-rpms', -+ name='RHEL 8 Blocklisted (RPMs)', -+ baseurl='https://.../dist/rhel/blocklisted/8/os', - enabled=True) - - repositories_mapping = RepositoriesMapping( -@@ -57,13 +63,19 @@ def test_custom_repos(monkeypatch): - repositories=[] - ) - -- msgs = [custom, blacklisted, repositories_mapping] -+ msgs = [custom, blocklisted, repositories_mapping] - - monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) - monkeypatch.setattr(api, 'produce', produce_mocked()) - - handler = RepoMapDataHandler(repositories_mapping) -- setuptargetrepos.setup_target_repos(handler, blacklisted_repoids={'rhel-8-blacklisted-rpms'}) -+ setuptargetrepos.setup_target_repos( -+ handler, -+ enabled_repoids=set(), -+ pes_requested_repoids=set(), -+ blocklisted_repoids={'rhel-8-blocklisted-rpms'}, -+ external_repoids_requests=set(), -+ ) - - assert api.produce.called - -@@ -75,7 +87,7 @@ def test_custom_repos(monkeypatch): - def test_repositories_setup_tasks(monkeypatch): - """ - Tests whether the actor propagates requested repoids -- to the resulting TargetRepositories (and blacklist filtering is applied to them). -+ to the resulting TargetRepositories (and blocklist filtering is applied to them). - """ - repositories_mapping = RepositoriesMapping(mapping=[], repositories=[]) - msgs = [repositories_mapping] -@@ -86,8 +98,11 @@ def test_repositories_setup_tasks(monkeypatch): - handler = RepoMapDataHandler(repositories_mapping) - setuptargetrepos.setup_target_repos( - handler, -- blacklisted_repoids={'rhel-8-blacklisted-rpms'}, -- external_repoids_requests={'rhel-8-server-rpms', 'rhel-8-blacklisted-rpms'}) -+ enabled_repoids=set(), -+ pes_requested_repoids=set(), -+ blocklisted_repoids={'rhel-8-blacklisted-rpms'}, -+ external_repoids_requests={'rhel-8-server-rpms', 'rhel-8-blacklisted-rpms'}, -+ ) - - assert api.produce.called - -@@ -195,7 +210,17 @@ def test_repos_mapping_for_distro(monkeypatch, src_distro, dst_distro): - monkeypatch.setattr(api, 'produce', produce_mocked()) - - handler = RepoMapDataHandler(repomap) -- setuptargetrepos.setup_target_repos(handler, blacklisted_repoids={'{}-9-blacklisted-rpms'.format(dst_distro)}) -+ enabled_repoids = { -+ '{}-8-server-rpms'.format(src_distro), -+ '{}-8-blacklisted-rpms'.format(src_distro), -+ } -+ setuptargetrepos.setup_target_repos( -+ handler, -+ enabled_repoids=enabled_repoids, -+ pes_requested_repoids=set(), -+ blocklisted_repoids={'{}-9-blacklisted-rpms'.format(dst_distro)}, -+ external_repoids_requests=set(), -+ ) - assert api.produce.called - - distro_repos = api.produce.model_instances[0].distro_repos -@@ -223,7 +248,7 @@ def test_repos_mapping_for_distro(monkeypatch, src_distro, dst_distro): - def test_pes_requested_repoids_added_to_target(monkeypatch): - """ - Tests whether PES-requested repoids are added to the target repositories -- and that blacklisted PES-requested repoids are excluded. -+ and that blocklisted PES-requested repoids are excluded. - """ - repositories_mapping = RepositoriesMapping(mapping=[], repositories=[]) - msgs = [repositories_mapping] -@@ -234,8 +259,10 @@ def test_pes_requested_repoids_added_to_target(monkeypatch): - handler = RepoMapDataHandler(repositories_mapping) - setuptargetrepos.setup_target_repos( - handler, -+ enabled_repoids=set(), - pes_requested_repoids={'pes-repo-1', 'pes-repo-2', 'pes-blocked'}, -- blacklisted_repoids={'pes-blocked'}, -+ blocklisted_repoids={'pes-blocked'}, -+ external_repoids_requests=set(), - ) - - assert api.produce.called -@@ -262,8 +289,9 @@ def test_pes_and_external_repoids_combined(monkeypatch): - handler = RepoMapDataHandler(repositories_mapping) - setuptargetrepos.setup_target_repos( - handler, -+ enabled_repoids=set(), - pes_requested_repoids={'pes-repo'}, -- blacklisted_repoids={'blocked-repo'}, -+ blocklisted_repoids={'blocked-repo'}, - external_repoids_requests={'ext-repo', 'blocked-repo'}, - ) - -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py -index 750f6f9e..c0ca2d93 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py -@@ -55,12 +55,13 @@ def test_process_orchestration(monkeypatch): - assert enabled_repoids is fake_enabled_repoids - return fake_pes_repoids - -- def mock_setup_target_repos(repo_mapping, pes_requested_repoids=None, -- blacklisted_repoids=None, external_repoids_requests=None): -+ def mock_setup_target_repos(repo_mapping, enabled_repoids, pes_requested_repoids, -+ blocklisted_repoids, external_repoids_requests): - call_log.append('setup_target_repos') - assert repo_mapping is fake_repomap_handler -+ assert enabled_repoids is fake_enabled_repoids - assert pes_requested_repoids is fake_pes_repoids -- assert blacklisted_repoids is fake_blocklist -+ assert blocklisted_repoids is fake_blocklist - assert external_repoids_requests is fake_external_tasks.to_enable - - monkeypatch.setattr( --- -2.54.0 - diff --git a/0104-8-9-targetcontentresolver-pes_events_scanner-refacto.patch b/0104-8-9-targetcontentresolver-pes_events_scanner-refacto.patch deleted file mode 100644 index 1337c9c..0000000 --- a/0104-8-9-targetcontentresolver-pes_events_scanner-refacto.patch +++ /dev/null @@ -1,849 +0,0 @@ -From e8be7466b224df83691796a913792c759f736cd3 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Sat, 6 Jun 2026 12:30:21 +0200 -Subject: [PATCH 104/105] [8/9] targetcontentresolver: pes_events_scanner: - refactoring - -* Distinguish public & private functions -* Require installed rpms and enabled modules as input parameters - instead of consuming them directly - * Deduplication of enabled modules is moved to the orchestrator - library -* Code-stylistic changes -* Updated the blacklist -> blocklist wording in the code -* Drop dead code - -Unit tests adjusted with assistance of AI. - -Jira: RHEL-115867 -Co-authored-by: Matej Matuska -Assisted-by: Claude Code (Opus 4.6) -Signed-off-by: Petr Stodulka ---- - .../libraries/pes_events_scanner.py | 227 +++++++----------- - .../libraries/targetcontentresolver.py | 25 +- - .../tests/test_pes_event_scanner.py | 60 +++-- - .../tests/test_targetcontentresolver.py | 32 ++- - 4 files changed, 162 insertions(+), 182 deletions(-) - -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/pes_events_scanner.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/pes_events_scanner.py -index 96f26a59..2ea63f72 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/pes_events_scanner.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/pes_events_scanner.py -@@ -2,7 +2,6 @@ from collections import defaultdict, namedtuple - from functools import partial - - from leapp import reporting --from leapp.exceptions import StopActorExecutionError - from leapp.libraries.actor import repomap_calc - from leapp.libraries.actor.pes_event_parsing import Action, get_pes_events, Package - from leapp.libraries.common import rpms -@@ -10,15 +9,7 @@ from leapp.libraries.common.config import get_target_distro_id, version - from leapp.libraries.common.distro import DISTRO_REPORT_NAMES - from leapp.libraries.stdlib import api - from leapp.libraries.stdlib.config import is_verbose --from leapp.models import ( -- DistributionSignedRPM, -- EnabledModules, -- Module, -- PESIDRepositoryEntry, -- PESRpmTransactionTasks, -- RepositoriesFacts, -- RpmTransactionTasks --) -+from leapp.models import Module, PESIDRepositoryEntry, PESRpmTransactionTasks, RpmTransactionTasks - - SKIPPED_PKGS_MSG = ( - 'packages will be skipped because they are available only in ' -@@ -32,48 +23,22 @@ SKIPPED_PKGS_MSG = ( - TransactionConfiguration = namedtuple('TransactionConfiguration', ('to_install', 'to_remove', 'to_keep')) - - --def get_cloud_provider_name(cloud_provider_variant): -- for cloud_provider_prefix in ('aws', 'azure', 'google'): -- if cloud_provider_variant.startswith(cloud_provider_prefix): -- return cloud_provider_prefix -- return cloud_provider_variant -- -- --def get_best_pesid_candidate(candidate_a, candidate_b, cloud_provider): -- cdn_candidate = None -- for candidate in (candidate_a, candidate_b): -- if candidate.rhui == cloud_provider: -- return candidate -- if not candidate.rhui: -- cdn_candidate = candidate -- -- # None of the candidate matches cloud provider and none of them is from CDN - -- # do not return anything as we don't want to get content from different cloud providers -- return cdn_candidate -- -- --def get_installed_pkgs(): -+def _rpms_to_package_set(rpm_list): - installed_pkgs = set() -- -- installed_rh_signed_rpm_msgs = api.consume(DistributionSignedRPM) -- installed_rh_signed_rpm_msg = next(installed_rh_signed_rpm_msgs, None) -- if list(installed_rh_signed_rpm_msgs): -- api.current_logger().warning('Unexpectedly received more than one DistributionSignedRPM message.') -- if not installed_rh_signed_rpm_msg: -- raise StopActorExecutionError('Cannot parse PES data properly due to missing list of installed packages', -- details={'Problem': 'Did not receive a message with installed Red Hat-signed ' -- 'packages (DistributionSignedRPM)'}) -- -- for pkg in installed_rh_signed_rpm_msg.items: -+ for rpm in rpm_list: - modulestream = None -- if pkg.module and pkg.stream: -- modulestream = (pkg.module, pkg.stream) -- installed_pkgs.add(Package(name=pkg.name, repository=pkg.repository, modulestream=modulestream)) -+ if rpm.module and rpm.stream: -+ modulestream = (rpm.module, rpm.stream) -+ installed_pkgs.add(Package( -+ name=rpm.name, -+ repository=rpm.repository, -+ modulestream=modulestream -+ )) - - return installed_pkgs - - --def get_transaction_configuration(): -+def _get_transaction_configuration(): - """ - Get pkgs to install, keep and remove from RpmTransactionTasks messages. - -@@ -94,7 +59,7 @@ def get_transaction_configuration(): - return transaction_configuration - - --def get_relevant_releases(events): -+def _get_relevant_releases(events): - """ - Get releases present in the PES Events that are relevant for this IPU. - -@@ -110,24 +75,11 @@ def get_relevant_releases(events): - return sorted(releases) - - --def _get_enabled_modules(): -- # TODO(pstodulk): take a look -- enabled_modules_msgs = api.consume(EnabledModules) -- enabled_modules_msg = next(enabled_modules_msgs, None) -- if list(enabled_modules_msgs): -- api.current_logger().warning('Unexpectedly received more than one EnabledModules message.') -- if not enabled_modules_msg: -- raise StopActorExecutionError('Cannot parse PES data properly due to missing list of enabled modules', -- details={'Problem': 'Did not receive a message with enabled module ' -- 'streams (EnabledModules)'}) -- return enabled_modules_msg.modules -- -- --def compute_pkg_changes_between_consequent_releases(source_installed_pkgs, -- events, -- release, -- seen_pkgs, -- pkgs_to_demodularize): -+def _compute_pkg_changes_between_consequent_releases(source_installed_pkgs, -+ events, -+ release, -+ seen_pkgs, -+ pkgs_to_demodularize): - logger = api.current_logger() - # Start with the installed packages and modify the set according to release events - target_pkgs = set(source_installed_pkgs) -@@ -194,7 +146,7 @@ def compute_pkg_changes_between_consequent_releases(source_installed_pkgs, - return (target_pkgs, pkgs_to_demodularize) - - --def remove_undesired_events(events, relevant_to_releases): -+def _remove_undesired_events(events, relevant_to_releases): - """ - Conservatively remove events that needless, or cause problems for the current implementation: - - (needless) events with to_release not in relevant releases -@@ -243,7 +195,7 @@ def remove_undesired_events(events, relevant_to_releases): - return cleaned_events - - --def compute_packages_on_target_system(source_pkgs, events, releases): -+def _compute_packages_on_target_system(source_pkgs, events, releases): - - seen_pkgs = set(source_pkgs) # Used to track whether PRESENCE events can be applied - target_pkgs = set(source_pkgs) -@@ -257,9 +209,9 @@ def compute_packages_on_target_system(source_pkgs, events, releases): - did_processing_cross_major_version = True - pkgs_to_demodularize = {pkg for pkg in target_pkgs if pkg.modulestream} - -- target_pkgs, pkgs_to_demodularize = compute_pkg_changes_between_consequent_releases(target_pkgs, events, -- release, seen_pkgs, -- pkgs_to_demodularize) -+ target_pkgs, pkgs_to_demodularize = _compute_pkg_changes_between_consequent_releases(target_pkgs, events, -+ release, seen_pkgs, -+ pkgs_to_demodularize) - seen_pkgs = seen_pkgs.union(target_pkgs) - - demodularized_pkgs = {Package(pkg.name, pkg.repository, None) for pkg in pkgs_to_demodularize} -@@ -268,34 +220,33 @@ def compute_packages_on_target_system(source_pkgs, events, releases): - return (demodularized_target_pkgs, pkgs_to_demodularize) - - --def compute_rpm_tasks_from_pkg_set_diff(source_pkgs, target_pkgs, pkgs_to_demodularize): -+def _compute_rpm_tasks_from_pkg_set_diff(source_pkgs, target_pkgs, pkgs_to_demodularize, modules_to_reset): - source_state_pkg_names = {pkg.name for pkg in source_pkgs} - target_state_pkg_names = {pkg.name for pkg in target_pkgs} - - pkgs_to_install = sorted(target_state_pkg_names.difference(source_state_pkg_names)) - pkgs_to_remove = sorted(source_state_pkg_names.difference(target_state_pkg_names)) - -- if pkgs_to_install or pkgs_to_remove: -- # NOTE(mhecko): Here we do not want to consider any package that does not have a reference in PES data. There -- # might be missing modularity information, and although the algorithm is correct, trying to enable -- # a non-existent modulestream due to missing modulestream information results in a crash. -- target_pkgs_without_demodularized_pkgs = target_pkgs.difference(pkgs_to_demodularize) -+ if not (pkgs_to_install or pkgs_to_remove): -+ return None - -- # Collect the enabled modules as tuples in a set, so we produce every module to reset exactly once -- enabled_modules = {(module.name, module.stream) for module in _get_enabled_modules()} -- modules_to_reset = [Module(name=ms[0], stream=ms[1]) for ms in enabled_modules] -+ # NOTE(mhecko): Here we do not want to consider any package that does not have a reference in PES data. There -+ # might be missing modularity information, and although the algorithm is correct, trying to enable -+ # a non-existent modulestream due to missing modulestream information results in a crash. -+ target_pkgs_without_demodularized_pkgs = target_pkgs.difference(pkgs_to_demodularize) - -- target_modulestreams = {pkg.modulestream for pkg in target_pkgs_without_demodularized_pkgs if pkg.modulestream} -- modules_to_enable = [Module(name=ms[0], stream=ms[1]) for ms in target_modulestreams] -+ target_modulestreams = {pkg.modulestream for pkg in target_pkgs_without_demodularized_pkgs if pkg.modulestream} -+ modules_to_enable = [Module(name=ms[0], stream=ms[1]) for ms in target_modulestreams] - -- return PESRpmTransactionTasks(to_install=pkgs_to_install, -- to_remove=pkgs_to_remove, -- modules_to_enable=modules_to_enable, -- modules_to_reset=modules_to_reset) -- return None -+ return PESRpmTransactionTasks( -+ to_install=pkgs_to_install, -+ to_remove=pkgs_to_remove, -+ modules_to_enable=modules_to_enable, -+ modules_to_reset=modules_to_reset -+ ) - - --def report_skipped_packages(title, message, skipped_pkgs, remediation=None): -+def _report_skipped_packages(title, message, skipped_pkgs, remediation=None): - skipped_pkgs = sorted(skipped_pkgs) - - def make_summary_entry_for_skipped_pkg(pkg): -@@ -320,44 +271,25 @@ def report_skipped_packages(title, message, skipped_pkgs, remediation=None): - api.current_logger().info(summary) - - --def remove_new_packages_from_blacklisted_repos(source_pkgs, target_pkgs, blacklisted_repoids): -+def _remove_new_packages_from_blocklisted_repos(source_pkgs, target_pkgs, blocklisted_repoids): - """ -- Remove newly installed packages from blacklisted repositories that were computed to be on the target system. -+ Remove newly installed packages from blocklisted repositories that were computed to be on the target system. - -- :param blacklisted_repoids: Set of repoids to exclude. If None, an empty set is used. -+ :param blocklisted_repoids: Set of repoids to exclude. If None, an empty set is used. - """ -- if blacklisted_repoids is None: -- blacklisted_repoids = set() - new_pkgs = target_pkgs.difference(source_pkgs) -- pkgs_from_blacklisted_repos = set(pkg for pkg in new_pkgs if pkg.repository in blacklisted_repoids) -+ pkgs_from_blocklisted_repos = set(pkg for pkg in new_pkgs if pkg.repository in blocklisted_repoids) - -- if pkgs_from_blacklisted_repos: -- report_skipped_packages( -+ if pkgs_from_blocklisted_repos: -+ _report_skipped_packages( - title='Packages available in excluded repositories will not be installed', - message=SKIPPED_PKGS_MSG, -- skipped_pkgs=pkgs_from_blacklisted_repos, -+ skipped_pkgs=pkgs_from_blocklisted_repos, - ) -- return blacklisted_repoids, target_pkgs.difference(pkgs_from_blacklisted_repos) -+ return target_pkgs.difference(pkgs_from_blocklisted_repos) - - --def get_enabled_repoids(): -- """ -- Collects repoids of all enabled repositories on the source system. -- -- :param repositories_facts: Iterable of RepositoriesFacts containing repositories related info about source system. -- :return: Set of all enabled repository IDs present on the source system. -- :rtype: Set[str] -- """ -- enabled_repoids = set() -- for repos in api.consume(RepositoriesFacts): -- for repo_file in repos.repositories: -- for repo in repo_file.data: -- if repo.enabled: -- enabled_repoids.add(repo.repoid) -- return enabled_repoids -- -- --def get_pesid_to_repoid_map(target_pesids, repomap_handler, enabled_repoids): -+def _get_pesid_to_repoid_map(target_pesids, repomap_handler, enabled_repoids): - """ - Get a dictionary mapping all PESID repositories to their corresponding repoid. - -@@ -430,7 +362,7 @@ def get_pesid_to_repoid_map(target_pesids, repomap_handler, enabled_repoids): - return repositories_mapping - - --def replace_pesids_with_repoids_in_packages(packages, source_pkgs_repoids, repomap_handler, enabled_repoids): -+def _replace_pesids_with_repoids_in_packages(packages, source_pkgs_repoids, repomap_handler, enabled_repoids): - """Replace packages with PESID in their .repository field with ones that have repoid providing the package.""" - # We want to map only PESIDs - if some package had no events, it will its repository set to source system repoid - packages_with_pesid = {pkg for pkg in packages if pkg.repository not in source_pkgs_repoids} -@@ -438,7 +370,7 @@ def replace_pesids_with_repoids_in_packages(packages, source_pkgs_repoids, repom - - required_target_pesids = {pkg.repository for pkg in packages_with_pesid} - -- pesid_to_target_repoid_map = get_pesid_to_repoid_map(required_target_pesids, repomap_handler, enabled_repoids) -+ pesid_to_target_repoid_map = _get_pesid_to_repoid_map(required_target_pesids, repomap_handler, enabled_repoids) - - packages_with_unknown_target_repoid = { - pkg -@@ -447,7 +379,7 @@ def replace_pesids_with_repoids_in_packages(packages, source_pkgs_repoids, repom - } - - if packages_with_unknown_target_repoid: -- report_skipped_packages( -+ _report_skipped_packages( - title='Packages from unknown repositories may not be installed', - message='packages may not be installed or upgraded due to repositories unknown to leapp:', - skipped_pkgs=packages_with_unknown_target_repoid, -@@ -458,7 +390,8 @@ def replace_pesids_with_repoids_in_packages(packages, source_pkgs_repoids, repom - ' You can also review the projected DNF upgrade transaction result' - ' in the logs to see what is going to happen, as this does not necessarily mean' - ' that the listed packages will not be upgraded. You can also' -- ' install any missing packages after the in-place upgrade manually.'.format( -+ ' install any missing packages after the in-place upgrade manually.' -+ .format( - 'RHEL (provided by Red Hat on CDN)' - if get_target_distro_id() == 'rhel' - else DISTRO_REPORT_NAMES.target -@@ -474,7 +407,7 @@ def replace_pesids_with_repoids_in_packages(packages, source_pkgs_repoids, repom - return packages_with_repoid.union(packages_without_pesid) - - --def apply_transaction_configuration(source_pkgs, transaction_configuration): -+def _apply_transaction_configuration(source_pkgs, transaction_configuration): - source_pkgs_with_conf_applied = set(source_pkgs) - - source_pkgs_with_conf_applied = source_pkgs.union(transaction_configuration.to_install) -@@ -494,7 +427,7 @@ def apply_transaction_configuration(source_pkgs, transaction_configuration): - return source_pkgs_with_conf_applied - - --def remove_leapp_related_events(events): -+def _remove_leapp_related_events(events): - major_vers = ['7', '8', '9'] - leapp_pkgs = rpms.get_leapp_dep_packages(major_vers) + rpms.get_leapp_packages(major_vers) - res = [] -@@ -508,7 +441,7 @@ def remove_leapp_related_events(events): - return res - - --def include_instructions_from_transaction_configuration(rpm_tasks, transaction_configuration, installed_pkgs): -+def _include_instructions_from_transaction_configuration(rpm_tasks, transaction_configuration, installed_pkgs): - """ - Extend current rpm_tasks applying data from transaction_configuration - -@@ -552,13 +485,13 @@ def include_instructions_from_transaction_configuration(rpm_tasks, transaction_c - modules_to_reset=modules_to_reset) - - --def scan_pes_events(repomap_handler, blacklisted_repoids, enabled_repoids): -+def scan_pes_events(repomap_handler, blocklisted_repoids, enabled_repoids, installed_rpms, enabled_modules): - """ - Process PES events and compute RPM transaction tasks. - - :param repomap_handler: Operator to work with the repositories mapping data - :type repomap_handler: repomap_calc.RepoMapDataHandler -- :param blacklisted_repoids: Set of repoids to exclude from target packages. If None, an empty set is used. -+ :param blocklisted_repoids: Set of repoids to exclude from target packages. If None, an empty set is used. - :returns: Set of target repoids that need to be enabled, or None if no PES events are found. - :rtype: Optional[set] - """ -@@ -567,41 +500,55 @@ def scan_pes_events(repomap_handler, blacklisted_repoids, enabled_repoids): - if not events: - return None - -- releases = get_relevant_releases(events) -- installed_pkgs = get_installed_pkgs() -- transaction_configuration = get_transaction_configuration() -- pkgs_to_begin_computation_with = apply_transaction_configuration(installed_pkgs, transaction_configuration) -+ releases = _get_relevant_releases(events) -+ installed_pkgs = _rpms_to_package_set(installed_rpms) -+ transaction_configuration = _get_transaction_configuration() -+ pkgs_to_begin_computation_with = _apply_transaction_configuration(installed_pkgs, transaction_configuration) - - # Keep track of what repoids have the source packages to be able to determine what are the PESIDs of the computed - # packages of the target system, so we can distinguish what needs to be repomapped - repoids_of_source_pkgs = {pkg.repository for pkg in pkgs_to_begin_computation_with} - -- events = remove_leapp_related_events(events) -- events = remove_undesired_events(events, releases) -+ events = _remove_leapp_related_events(events) -+ events = _remove_undesired_events(events, releases) - - # Apply events - compute what packages should the target system have -- target_pkgs, pkgs_to_demodularize = compute_packages_on_target_system(pkgs_to_begin_computation_with, -- events, releases) -+ target_pkgs, pkgs_to_demodularize = _compute_packages_on_target_system( -+ pkgs_to_begin_computation_with, -+ events, -+ releases -+ ) - - # Packages coming out of the events have PESID as their repository, however, we need real repoid -- target_pkgs = replace_pesids_with_repoids_in_packages( -+ target_pkgs = _replace_pesids_with_repoids_in_packages( - target_pkgs, - repoids_of_source_pkgs, - repomap_handler, - enabled_repoids - ) - -- # Apply the desired repository blacklisting -- blacklisted_repoids, target_pkgs = remove_new_packages_from_blacklisted_repos(pkgs_to_begin_computation_with, -- target_pkgs, blacklisted_repoids) -+ # Apply the desired repository blocklisting -+ target_pkgs = _remove_new_packages_from_blocklisted_repos( -+ pkgs_to_begin_computation_with, -+ target_pkgs, -+ blocklisted_repoids -+ ) - - # Look at the target packages and determine what repositories to enable -- pes_requested_repoids = set(p.repository for p in target_pkgs) - blacklisted_repoids - repoids_of_source_pkgs -+ pes_requested_repoids = set(p.repository for p in target_pkgs) - blocklisted_repoids - repoids_of_source_pkgs - - # Compare the packages on source system and the computed packages on target system and determine what to install -- rpm_tasks = compute_rpm_tasks_from_pkg_set_diff(pkgs_to_begin_computation_with, target_pkgs, pkgs_to_demodularize) -- rpm_tasks = include_instructions_from_transaction_configuration(rpm_tasks, transaction_configuration, -- installed_pkgs) -+ rpm_tasks = _compute_rpm_tasks_from_pkg_set_diff( -+ pkgs_to_begin_computation_with, -+ target_pkgs, -+ pkgs_to_demodularize, -+ enabled_modules -+ ) -+ rpm_tasks = _include_instructions_from_transaction_configuration( -+ rpm_tasks, -+ transaction_configuration, -+ installed_pkgs -+ ) - if rpm_tasks: - api.produce(rpm_tasks) - -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -index 374f1498..83460d99 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -@@ -7,6 +7,9 @@ from leapp.libraries.actor.repomap_loader import load_repositories_mapping - from leapp.libraries.stdlib import api - from leapp.models import ( - CustomTargetRepository, -+ DistributionSignedRPM, -+ EnabledModules, -+ Module, - RepositoriesBlacklisted, - RepositoriesFacts, - RepositoriesSetupTasks, -@@ -26,6 +29,8 @@ class InputData(): - self._missing_messages = [] - - self._get_enabled_repoids() -+ self._get_installed_rpms() -+ self._get_enabled_modules() - self._get_external_reposetup_tasks() - - if self._missing_messages: -@@ -92,6 +97,22 @@ class InputData(): - if repo.enabled: - self.enabled_repoids.add(repo.repoid) - -+ def _get_enabled_modules(self): -+ self.enabled_modules = [] -+ modules_facts = self._treat_consume_msg(EnabledModules) -+ if modules_facts is None: -+ return -+ -+ # NOTE(pstodulk): This deduplication is weird, not sure why the input -+ # data would not be unique. But let's rather keep for now. -+ uniq_modules = {(module.name, module.stream) for module in modules_facts.modules} -+ self.enabled_modules = [Module(name=ms[0], stream=ms[1]) for ms in uniq_modules] -+ -+ def _get_installed_rpms(self): -+ distro_rpms = self._treat_consume_msg(DistributionSignedRPM) -+ if distro_rpms: -+ self.installed_rpms = distro_rpms.items -+ - - def _init_repomap_handler(): - repomap_msg = load_repositories_mapping() -@@ -131,7 +152,9 @@ def process(): - pes_requested_repoids = pes_events_scanner.scan_pes_events( - repomap_handler, - blocklisted_repoids, -- indata.enabled_repoids -+ indata.enabled_repoids, -+ indata.installed_rpms, -+ indata.enabled_modules - ) - - setuptargetrepos.setup_target_repos( -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_pes_event_scanner.py b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_pes_event_scanner.py -index c6f16823..b0beebbb 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_pes_event_scanner.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_pes_event_scanner.py -@@ -5,10 +5,10 @@ import pytest - from leapp.libraries.actor import pes_events_scanner - from leapp.libraries.actor.pes_event_parsing import Event - from leapp.libraries.actor.pes_events_scanner import ( -+ _compute_packages_on_target_system, -+ _compute_rpm_tasks_from_pkg_set_diff, - Action, - api, -- compute_packages_on_target_system, -- compute_rpm_tasks_from_pkg_set_diff, - Package, - reporting, - TransactionConfiguration -@@ -16,8 +16,6 @@ from leapp.libraries.actor.pes_events_scanner import ( - from leapp.libraries.actor.repomap_calc import RepoMapDataHandler - from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked, produce_mocked - from leapp.models import ( -- DistributionSignedRPM, -- EnabledModules, - PESIDRepositoryEntry, - PESRpmTransactionTasks, - RepoMapEntry, -@@ -135,7 +133,7 @@ def pkgs_into_tuples(pkgs): - def test_event_application_fundamentals(monkeypatch, installed_pkgs, events, releases, expected_target_pkgs): - """Trivial checks validating that the core event application algorithm reflects event semantics as expected.""" - monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) -- actual_target_pkgs, dummy_demodularized_pkgs = compute_packages_on_target_system(installed_pkgs, events, releases) -+ actual_target_pkgs, dummy_demodularized_pkgs = _compute_packages_on_target_system(installed_pkgs, events, releases) - - # Perform strict comparison - actual_pkg_tuple_set = {(pkg.name, pkg.repository, pkg.modulestream) for pkg in actual_target_pkgs} -@@ -174,7 +172,9 @@ def test_compute_pkg_state(monkeypatch): - Package('reintroduced', 'rhel7-repo', None), - } - -- target_pkgs, dummy_demodularized_pkgs = compute_packages_on_target_system(installed_pkgs, events, [(8, 0), (8, 1)]) -+ target_pkgs, dummy_demodularized_pkgs = _compute_packages_on_target_system( -+ installed_pkgs, events, [(8, 0), (8, 1)] -+ ) - - expected_target_pkgs = { - Package('split01', 'rhel8-repo', None), -@@ -186,7 +186,6 @@ def test_compute_pkg_state(monkeypatch): - - - def test_compute_rpm_tasks_from_pkg_set_diff(monkeypatch): -- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[EnabledModules(modules=[])])) - source_pkgs = { - Package('removed1', '7repo', None), - Package('removed2', '7repo', None), -@@ -201,7 +200,7 @@ def test_compute_rpm_tasks_from_pkg_set_diff(monkeypatch): - Package('installed2', '8repo2', None), - } - -- rpm_tasks = compute_rpm_tasks_from_pkg_set_diff(source_pkgs, target_pkgs, set()) -+ rpm_tasks = _compute_rpm_tasks_from_pkg_set_diff(source_pkgs, target_pkgs, set(), []) - - assert rpm_tasks.to_install == ['installed1', 'installed2'] - assert rpm_tasks.to_remove == ['removed1', 'removed2'] -@@ -236,9 +235,9 @@ def test_actor_performs(monkeypatch): - - _RPM = partial(RPM, epoch='', packager='', version='', release='', arch='', pgpsig='') - -- installed_pkgs = DistributionSignedRPM(items=[ -+ installed_rpms = [ - _RPM(name='split-in'), _RPM(name='moved-in'), _RPM(name='removed') -- ]) -+ ] - - repositories_mapping = RepositoriesMapping( - mapping=[ -@@ -251,7 +250,6 @@ def test_actor_performs(monkeypatch): - repo_type='rpm', channel='ga', rhui='', distro='rhel')] - ) - -- enabled_modules = EnabledModules(modules=[]) - repo_facts = RepositoriesFacts( - repositories=[RepositoryFile(file='', data=[RepositoryData(repoid='rhel8-repo', name='RHEL8 repo')])] - ) -@@ -260,7 +258,7 @@ def test_actor_performs(monkeypatch): - api, - "current_actor", - CurrentActorMocked( -- msgs=[installed_pkgs, repositories_mapping, enabled_modules, repo_facts], -+ msgs=[repositories_mapping, repo_facts], - src_ver="8.10", - dst_ver="9.1", - ), -@@ -273,7 +271,7 @@ def test_actor_performs(monkeypatch): - monkeypatch.setattr(api, 'produce', produced_messages) - monkeypatch.setattr(reporting, 'create_report', created_report) - -- pes_events_scanner.scan_pes_events(repomap_handler, set(), {'rhel8-repo'}) -+ pes_events_scanner.scan_pes_events(repomap_handler, set(), {'rhel8-repo'}, installed_rpms, []) - - assert produced_messages.called - -@@ -299,14 +297,14 @@ def test_transaction_configuration_has_effect(monkeypatch): - ) - - packages = {_Pkg('pkg-a'), _Pkg('pkg-c')} -- _result = pes_events_scanner.apply_transaction_configuration(packages, transaction_cfg) -+ _result = pes_events_scanner._apply_transaction_configuration(packages, transaction_cfg) - result = {(p.name, p.repository, p.modulestream) for p in _result} - expected = {('pkg-a', None, None), ('pkg-b', None, None)} - - assert result == expected - - --def test_repository_blacklist_is_correctly_applied(monkeypatch): -+def test_repository_blocklist_is_correctly_applied(monkeypatch): - _Pkg = partial(Package, modulestream=None) - - monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) -@@ -315,11 +313,10 @@ def test_repository_blacklist_is_correctly_applied(monkeypatch): - source_pkgs = {_Pkg('pkg-b', 'repo-b')} - target_pkgs = {_Pkg('pkg-a', 'repo-a'), _Pkg('pkg-b', 'repo-b'), _Pkg('pkg-c', 'repo-c'), _Pkg('pkg-d', 'repo-d')} - -- blacklisted_repoids, target_pkgs = pes_events_scanner.remove_new_packages_from_blacklisted_repos( -+ target_pkgs = pes_events_scanner._remove_new_packages_from_blocklisted_repos( - source_pkgs, target_pkgs, {'repo-a', 'repo-b', 'repo-c'}) - result = pkgs_into_tuples(target_pkgs) - -- assert blacklisted_repoids == {'repo-a', 'repo-b', 'repo-c'} - assert result == {('pkg-b', 'repo-b', None), ('pkg-d', 'repo-d', None)} - - assert reporting.create_report.called -@@ -340,17 +337,17 @@ def test_blacklisted_repoid_is_not_produced(monkeypatch): - (9, 0), (9, 1), []), - ] - -- monkeypatch.setattr(pes_events_scanner, 'get_installed_pkgs', lambda: installed_pkgs) -+ monkeypatch.setattr(pes_events_scanner, '_rpms_to_package_set', lambda rpm_list: installed_pkgs) - monkeypatch.setattr(pes_events_scanner, 'get_pes_events', lambda folder, filename: events) -- monkeypatch.setattr(pes_events_scanner, 'apply_transaction_configuration', lambda pkgs, transaction_cfg: pkgs) -- monkeypatch.setattr(pes_events_scanner, 'replace_pesids_with_repoids_in_packages', -+ monkeypatch.setattr(pes_events_scanner, '_apply_transaction_configuration', lambda pkgs, transaction_cfg: pkgs) -+ monkeypatch.setattr(pes_events_scanner, '_replace_pesids_with_repoids_in_packages', - lambda pkgs, src_pkgs_repoids, repo_map_msg, enabled_repoids: pkgs) - - monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) - monkeypatch.setattr(api, 'produce', produce_mocked()) - monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) - -- setup_tasks = pes_events_scanner.scan_pes_events(None, {'blacklisted-rhel9'}, set()) -+ setup_tasks = pes_events_scanner.scan_pes_events(None, {'blacklisted-rhel9'}, set(), [], []) - - assert not reporting.create_report.called - -@@ -383,7 +380,7 @@ def test_modularity_info_distinguishes_pkgs(monkeypatch, installed_pkgs, expecte - ] - - monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) -- target_pkgs, dummy_demodularized_pkgs = compute_packages_on_target_system(installed_pkgs, events, [(8, 1)]) -+ target_pkgs, dummy_demodularized_pkgs = _compute_packages_on_target_system(installed_pkgs, events, [(8, 1)]) - - assert pkgs_into_tuples(target_pkgs) == expected_target_pkgs - -@@ -403,7 +400,7 @@ def test_pkgs_are_demodularized_when_crossing_major_version(monkeypatch): - Package('demodularized', 'repo', ('module-demodularized', 'stream')) - } - -- target_pkgs, demodularized_pkgs = compute_packages_on_target_system(installed_pkgs, events, [(8, 0)]) -+ target_pkgs, demodularized_pkgs = _compute_packages_on_target_system(installed_pkgs, events, [(8, 0)]) - - expected_target_pkgs = { - Package('modular', 'repo1-out', ('module2', 'stream')), -@@ -473,7 +470,7 @@ def test_remove_leapp_related_events(monkeypatch): - {Package('other-pkg-with-leapp-in-the-name', 'repoid-rhel8', None)}, (7, 0), (8, 0), []), - ] - -- out_events = pes_events_scanner.remove_leapp_related_events(in_events) -+ out_events = pes_events_scanner._remove_leapp_related_events(in_events) - assert out_events == expected_out_events - - -@@ -483,7 +480,7 @@ def test_transaction_configuration_is_applied(monkeypatch): - Package(name='split-in', repository='rhel7-base', modulestream=None), - Package(name='pkg-not-in-events', repository='rhel7-base', modulestream=None), - } -- monkeypatch.setattr(pes_events_scanner, 'get_installed_pkgs', lambda *args, **kwags: installed_pkgs) -+ monkeypatch.setattr(pes_events_scanner, '_rpms_to_package_set', lambda rpm_list: installed_pkgs) - - Pkg = partial(Package, modulestream=None) - events = [ -@@ -496,14 +493,13 @@ def test_transaction_configuration_is_applied(monkeypatch): - (7, 9), (8, 0), []), - ] - monkeypatch.setattr(pes_events_scanner, 'get_pes_events', lambda *args, **kwargs: events) -- monkeypatch.setattr(pes_events_scanner, 'remove_leapp_related_events', lambda events: events) -- monkeypatch.setattr(pes_events_scanner, 'remove_undesired_events', lambda events, releases: events) -- monkeypatch.setattr(pes_events_scanner, '_get_enabled_modules', lambda *args: []) -- monkeypatch.setattr(pes_events_scanner, 'replace_pesids_with_repoids_in_packages', -+ monkeypatch.setattr(pes_events_scanner, '_remove_leapp_related_events', lambda events: events) -+ monkeypatch.setattr(pes_events_scanner, '_remove_undesired_events', lambda events, releases: events) -+ monkeypatch.setattr(pes_events_scanner, '_replace_pesids_with_repoids_in_packages', - lambda target_pkgs, repoids_of_source_pkgs, repo_map_msg, enabled_repoids: target_pkgs) - monkeypatch.setattr(pes_events_scanner, -- 'remove_new_packages_from_blacklisted_repos', -- lambda source_pkgs, target_pkgs, bl: (set(), target_pkgs)) -+ '_remove_new_packages_from_blocklisted_repos', -+ lambda source_pkgs, target_pkgs, bl: target_pkgs) - - msgs = [ - RpmTransactionTasks(to_remove=['pkg-not-in-events']), -@@ -516,7 +512,7 @@ def test_transaction_configuration_is_applied(monkeypatch): - - monkeypatch.setattr(api, 'produce', produce_mocked()) - -- setup_tasks = pes_events_scanner.scan_pes_events(None, set(), set()) -+ setup_tasks = pes_events_scanner.scan_pes_events(None, set(), set(), [], []) - - assert api.produce.called == 1 - assert setup_tasks is not None -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py -index c0ca2d93..f4fbe32e 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/tests/test_targetcontentresolver.py -@@ -7,11 +7,14 @@ from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked - from leapp.libraries.stdlib import api - from leapp.models import ( - CustomTargetRepository, -+ DistributionSignedRPM, -+ EnabledModules, - RepositoriesBlacklisted, - RepositoriesFacts, - RepositoriesSetupTasks, - RepositoryData, -- RepositoryFile -+ RepositoryFile, -+ RPM - ) - from leapp.utils.deprecation import suppress_deprecation - -@@ -29,6 +32,8 @@ def test_process_orchestration(monkeypatch): - to_enable=frozenset({'ext-repo'}), to_block=frozenset(), custom=frozenset() - ) - fake_enabled_repoids = frozenset({'enabled-repo'}) -+ fake_installed_rpms = [] -+ fake_enabled_modules = [] - fake_pes_repoids = frozenset({'pes-repo'}) - - class FakeInputData: -@@ -36,6 +41,8 @@ def test_process_orchestration(monkeypatch): - call_log.append('InputData') - self.external_tasks = fake_external_tasks - self.enabled_repoids = fake_enabled_repoids -+ self.installed_rpms = fake_installed_rpms -+ self.enabled_modules = fake_enabled_modules - - def mock_init_repomap_handler(): - call_log.append('_init_repomap_handler') -@@ -48,11 +55,13 @@ def test_process_orchestration(monkeypatch): - assert enabled_repoids is fake_enabled_repoids - return fake_blocklist - -- def mock_scan_pes_events(repo_mapping, blacklisted_repoids, enabled_repoids): -+ def mock_scan_pes_events(repo_mapping, blacklisted_repoids, enabled_repoids, installed_rpms, enabled_modules): - call_log.append('scan_pes_events') - assert repo_mapping is fake_repomap_handler - assert blacklisted_repoids is fake_blocklist - assert enabled_repoids is fake_enabled_repoids -+ assert installed_rpms is fake_installed_rpms -+ assert enabled_modules is fake_enabled_modules - return fake_pes_repoids - - def mock_setup_target_repos(repo_mapping, enabled_repoids, pes_requested_repoids, -@@ -96,6 +105,11 @@ def test_process_orchestration(monkeypatch): - ] - - -+def _make_base_msgs(): -+ """Create DistributionSignedRPM and EnabledModules messages required by InputData.""" -+ return [DistributionSignedRPM(items=[]), EnabledModules(modules=[])] -+ -+ - def _make_repo_facts(repoids_enabled=None, repoids_disabled=None): - repos_data = [] - for repoid in (repoids_enabled or []): -@@ -112,7 +126,7 @@ def test_inputdata_collects_enabled_repoids(monkeypatch): - repoids_enabled=['repo-a', 'repo-b'], - repoids_disabled=['repo-c'], - ) -- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[repo_facts])) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[repo_facts] + _make_base_msgs())) - - indata = InputData() - -@@ -120,7 +134,7 @@ def test_inputdata_collects_enabled_repoids(monkeypatch): - - - def test_inputdata_raises_when_repofacts_missing(monkeypatch): -- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[])) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=_make_base_msgs())) - - with pytest.raises(StopActorExecutionError): - InputData() -@@ -135,7 +149,7 @@ def test_inputdata_aggregates_external_tasks(monkeypatch): - - monkeypatch.setattr( - api, 'current_actor', -- CurrentActorMocked(msgs=[repo_facts, setup_tasks_1, setup_tasks_2, custom_1, custom_2]) -+ CurrentActorMocked(msgs=[repo_facts, setup_tasks_1, setup_tasks_2, custom_1, custom_2] + _make_base_msgs()) - ) - - indata = InputData() -@@ -147,7 +161,7 @@ def test_inputdata_aggregates_external_tasks(monkeypatch): - - def test_inputdata_empty_external_tasks(monkeypatch): - repo_facts = _make_repo_facts(repoids_enabled=['repo-a']) -- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[repo_facts])) -+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[repo_facts] + _make_base_msgs())) - - indata = InputData() - -@@ -161,7 +175,7 @@ def test_inputdata_warns_on_duplicate_repofacts(monkeypatch): - repo_facts_2 = _make_repo_facts(repoids_enabled=['repo-b']) - monkeypatch.setattr( - api, 'current_actor', -- CurrentActorMocked(msgs=[repo_facts_1, repo_facts_2]) -+ CurrentActorMocked(msgs=[repo_facts_1, repo_facts_2] + _make_base_msgs()) - ) - monkeypatch.setattr(api, 'current_logger', logger_mocked()) - -@@ -179,7 +193,7 @@ def test_inputdata_consumes_deprecated_blacklisted(monkeypatch): - - monkeypatch.setattr( - api, 'current_actor', -- CurrentActorMocked(msgs=[repo_facts, blacklisted]) -+ CurrentActorMocked(msgs=[repo_facts, blacklisted] + _make_base_msgs()) - ) - - indata = InputData() -@@ -196,7 +210,7 @@ def test_inputdata_merges_blacklisted_with_setup_tasks(monkeypatch): - - monkeypatch.setattr( - api, 'current_actor', -- CurrentActorMocked(msgs=[repo_facts, setup_tasks, blacklisted]) -+ CurrentActorMocked(msgs=[repo_facts, setup_tasks, blacklisted] + _make_base_msgs()) - ) - - indata = InputData() --- -2.54.0 - diff --git a/0105-9-9-targetcontentresolver-Make-TODO-notes.patch b/0105-9-9-targetcontentresolver-Make-TODO-notes.patch deleted file mode 100644 index e3768b7..0000000 --- a/0105-9-9-targetcontentresolver-Make-TODO-notes.patch +++ /dev/null @@ -1,52 +0,0 @@ -From a3a0c1d59f72c5c367b2541a510e01d08c470266 Mon Sep 17 00:00:00 2001 -From: Petr Stodulka -Date: Mon, 22 Jun 2026 15:02:05 +0200 -Subject: [PATCH 105/105] [9/9] targetcontentresolver: Make TODO notes - -The current refactoring progressed significantly, but there are still -anti-patterns and bugs in the code, which cannot be easily resolved -without the second part of the refactoring. Due to lack of time, we -decided to stop here, considering the situation much better in -comparison to the previous state. Making notes in the code about -additional expected changes so we do not forget them :) - -Co-authored-by: Matej Matuska -Jira: RHEL-115867 -Signed-off-by: Petr Stodulka ---- - .../libraries/targetcontentresolver.py | 19 +++++++++++++++++++ - 1 file changed, 19 insertions(+) - -diff --git a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -index 83460d99..f969d0f9 100644 ---- a/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -+++ b/repos/system_upgrade/common/actors/targetcontentresolver/libraries/targetcontentresolver.py -@@ -142,6 +142,25 @@ def process(): - source repos, installed package repos, and custom/blacklisted repo - configuration. - """ -+ # TODO: update pes_even_scanner to work just with PESIDs instead of repoids. -+ # The blocklisted events needs to be then evaluated / calculated after -+ # the setuptargetrepos calculations. Similarly we can update the blocklist -+ # library. The setuptargetrepos tells exactly what's going to be allowed -+ # (repoids) and based on that we can finish the calculations. -+ # This includes: -+ # * all reports will be generated (or directly called) from the orchestrator -+ # * all/most of messages should be produced in the orchestrator -+ # * we will need at least 2 public functions in pes_events_scanner -+ # # and repositoriesblocklist libs: -+ # # * these that we have right now -+ # # * additional ones for the final calculation / filtering based on -+ # # the real list of blocklisted repoids -+ # TL;DR; the workflow will be like this: -+ # * Make interim pes-events calculation (work just with PESIDS) -+ # * Make interim blocklist calculation -+ # * Calc expected & really blocked target repositories -+ # * take the result and finish the calculation -+ # * generate reports & messages - indata = InputData() - repomap_handler = _init_repomap_handler() - blocklisted_repoids = repositoriesblocklist.compute_blocklist( --- -2.54.0 - diff --git a/leapp-repository.spec b/leapp-repository.spec index 699b68c..302d451 100644 --- a/leapp-repository.spec +++ b/leapp-repository.spec @@ -51,8 +51,8 @@ py2_byte_compile "%1" "%2"} # RHEL 8+ packages to be consistent with other leapp projects in future. Name: leapp-repository -Version: 0.24.0 -Release: 3%{?dist} +Version: 0.25.0 +Release: 1%{?dist} Summary: Repositories for leapp License: ASL 2.0 @@ -66,113 +66,6 @@ BuildArch: noarch ### PATCHES HERE # Patch0001: filename.patch -Patch0001: 0001-Remove-legacy-overlay-solution-leftovers.patch -Patch0002: 0002-update-upgrade-paths-8.10-9.9-10.3.patch -Patch0003: 0003-Enable-logrotate.timer-during-8to9-IPU.patch -Patch0004: 0004-feat-net-naming-scheme-enable-by-default-on-CS-9to10.patch -Patch0005: 0005-Leapp_resume.service-Silence-AVC-SELinux-warnings.patch -Patch0006: 0006-Add-API-to-get-a-distro-report-name.patch -Patch0007: 0007-livemode-copy-upgrade-kernel-s-hmac-into-boot.patch -Patch0008: 0008-fix-livemode-make-live.dir-is-relative-to-device-roo.patch -Patch0009: 0009-fix-livemode-change-location-of-source-system-tree.patch -Patch0010: 0010-Respect-distro-names-in-reports.patch -Patch0011: 0011-checkblacklistca-Respect-distro-name-in-reports.patch -Patch0012: 0012-blsgrubcfgonppc64-Respect-distro-name-in-reports.patch -Patch0013: 0013-checkselinux-Respect-distro-name-in-report.patch -Patch0014: 0014-checkosrelease-Respect-distro-name-in-report.patch -Patch0015: 0015-rocecheck-Remove-outadated-check-for-source-version.patch -Patch0016: 0016-rocecheck-Respect-distro-name-in-report.patch -Patch0017: 0017-opensshpermitrootlogincheck-Remove-7to8-related-code.patch -Patch0018: 0018-opensshpermitrootlogincheck-Respect-target-distro-na.patch -Patch0019: 0019-checkifcfg_ifcfg-Respect-distro-name-in-report.patch -Patch0020: 0020-opensslproviders-Use-distro-name-in-the-leapp-commen.patch -Patch0021: 0021-opensslconfigcheck-Dont-make-the-recommendation-as-r.patch -Patch0022: 0022-checkmicroarchitecture-Respect-distro-name-in-report.patch -Patch0023: 0023-checkmemory-Respect-distro-name-in-reports.patch -Patch0024: 0024-targetuserspacecreator-Respect-distro-name-in-report.patch -Patch0025: 0025-checkdetecteddevicesanddirvers-Respect-distro-name-i.patch -Patch0026: 0026-reportleftoverpackages-Respect-distro-name-in-report.patch -Patch0027: 0027-fix-overlaygen-exclude-hugetlbfs-from-overlay-mounts.patch -Patch0028: 0028-biosdevname-Fix-check-of-naming-scheme-and-tests.patch -Patch0029: 0029-persistentnetnamesdisable-refactoring-fix-ethX-detec.patch -Patch0030: 0030-Fix-scan-of-net-interfaces-non-PCI-conflicting-broke.patch -Patch0031: 0031-persistentnetnamesconfig-Deprecate-legacy-functional.patch -Patch0032: 0032-persistentnetnamesdisable-Disable-net.ifnames-correc.patch -Patch0033: 0033-persistentnetnamesdisable-Mention-net.naming-scheme-.patch -Patch0034: 0034-fix-quoting-in-list-representation-of-commands.patch -Patch0035: 0035-fixup-fix-quoting-in-list-representation-of-commands.patch -Patch0036: 0036-pulseaudiocheck-Add-PulseAudio-config-check-for-RHEL.patch -Patch0037: 0037-fix-checkyumpluginsenabled-correctly-create-remediat.patch -Patch0038: 0038-fix-rhui-consider-source-clients-always-signed.patch -Patch0039: 0039-Ensure-that-greenboot-rpms-are-removed-during-IPU.patch -Patch0040: 0040-Regenerate-grub-config-during-8-9-conversions.patch -Patch0041: 0041-python-tests-deps-update-version-requirements-per-py.patch -Patch0042: 0042-Drop-dependency-on-mock.patch -Patch0043: 0043-remove-single-quoting-from-remediation-commands-1520.patch -Patch0044: 0044-Update-leapp-data-data-stream-4.3-CTC-1.patch -# CTC 2 - 1 -Patch0045: 0045-repofileutils-disable-ConfigParser-interpolation-whe.patch -Patch0046: 0046-Drop-six-dependency-and-simplify-config-parsing-to-P.patch -Patch0047: 0047-mounting-ignore-failures-when-callling-mount-a.patch -Patch0048: 0048-mount_unit_generator-do-not-make-nofail-mounts-requi.patch -Patch0049: 0049-leapp_resume.service-use-multi-user.target-instead-o.patch -Patch0050: 0050-LiveMode-update-do-upgrade.sh-to-reflect-upstream-ch.patch -Patch0051: 0051-LiveMode-improve-do-upgrade.sh-output-when-.noreboot.patch -Patch0052: 0052-multipathutil-handle-the-overrides-protocol-subsecti.patch -Patch0053: 0053-multipath-move-config_reader-Actor-and-8to9-Models-t.patch -Patch0054: 0054-multipath-Add-MultipathConfig9to10-and-MultipathConf.patch -Patch0055: 0055-multipath-Add-MultipathConfRead9to10-config_reader-a.patch -Patch0056: 0056-multipath-Add-MultipathConfCheck9to10-mpath_conf_che.patch -Patch0057: 0057-multipath-Add-MultipathUpgradeConfUpdate9to10-mpath_.patch -Patch0058: 0058-multipath-Add-bindings-wwids-prkeys-file-copying-to-.patch -Patch0059: 0059-multipath-Add-mpathfiles-library-and-use-it-in-el9to.patch -Patch0060: 0060-model-upgrade_cmdline-add-to_remove-field.patch -Patch0061: 0061-checkluks-emit-messages-only-once.patch -Patch0062: 0062-checkluks-remove-rd.luks.uuid-from-the-upgrade-entry.patch -Patch0063: 0063-conftest-exit-actor-context-when-switching-repos.patch -Patch0064: 0064-testutils-logger_mocked-accepts-kwargs.patch -Patch0065: 0065-conftest-Add-leapp_tmpdir-fixture-for-standardized-t.patch -Patch0066: 0066-Reorganize-DNF-libraries-into-dnflibs-package.patch -Patch0067: 0067-Update-imports-to-use-new-dnflibs-modules.patch -Patch0068: 0068-dnflibs-introduce-DNFError-exception.patch -Patch0069: 0069-dnflibs.dnfplugin-Drop-dead-code-_handle_transaction.patch -Patch0070: 0070-dnflibs.dnfplugin-Drop-deprecated-DNF_PLUGIN_PATH.patch -Patch0071: 0071-dnflibs.dnfplugin-Introduce-new-DNF-exceptions-and-t.patch -Patch0072: 0072-dnflibs-dnfmodule-Move-init-of-dnf.Base-to-dnflibs-a.patch -Patch0073: 0073-dnflibs.dnfconfig-Raise-proper-exceptions-and-add-te.patch -Patch0074: 0074-DOCSTRINGS-Document-DNF-related-exception-propagatio.patch -Patch0075: 0075-pulseaudiocheck-fix-for-users-with-None-home-directo.patch -Patch0076: 0076-fix-target_initramfs-always-regenerate-initramfs-153.patch -Patch0077: 0077-Add-AI-assistant-harness-with-AGENTS.md-and-task-ski.patch -Patch0078: 0078-el8toel9-networkdeprecations-warn-about-ifcfg-device.patch -Patch0079: 0079-Remove-enforcing-1-from-upgrade-and-target-kernel-bo.patch -Patch0080: 0080-Fix-utils-actor_path.py-crash-on-invalid-repo-paths.patch -Patch0081: 0081-fix-breadcrumbs-use-distro-aware-OS-names-for-source.patch -Patch0082: 0082-leapp_resume.service-use-install_exec_t-SELinux-cont.patch -Patch0083: 0083-config-rhui-Add-config-for-setting-rpm-gpg-keys-to-r.patch -Patch0084: 0084-Add-inhibitor-for-invalid-fstab-entries-on-pseudo-fi.patch -Patch0085: 0085-lib-version-Remove-deprecated-is_rhel_realtime-funct.patch -Patch0086: 0086-lib-kernel-Fix-rt-kernel-detection-on-centos.patch -Patch0087: 0087-Add-CLAUDE.md-and-YAML-frontmatter-to-skills-for-Cla.patch -Patch0088: 0088-Add-inhibitor-for-incorrect-var-run-symlink-configur.patch -Patch0089: 0089-checkvarrunsymlink-Log-warning-when-var-run-is-missi.patch -Patch0090: 0090-Add-scandnfcomps-actor-to-scan-installed-DNF-comps.patch -Patch0091: 0091-IPU-9-10-Fix-the-upgrade-of-Graphical-Server.patch -Patch0092: 0092-Bump-leapp-framework.patch -Patch0093: 0093-raid-mdadm-include-configuration-in-initramfs.patch -Patch0094: 0094-feat-upgrade_initramfs_gen-write-rd.md.uuids-into-cm.patch -Patch0095: 0095-Update-actions-checkout-action-to-v7.patch -Patch0096: 0096-Update-distro-values-for-rhui-jobs-follow-up-on-9to1.patch -Patch0097: 0097-1-9-Merge-and-refactor-of-repo-related-actors.patch -Patch0098: 0098-2-9-targetcontentresolver-Init-message-consumption-c.patch -Patch0099: 0099-3-9-targetcontentresolver-RepositoriesBlacklisted-us.patch -Patch0100: 0100-4-9-targetcontentresolver-repositoriesblacklist-rede.patch -Patch0101: 0101-5-9-repositoriesmapping-scan_repositories-load_repos.patch -Patch0102: 0102-6-9-targetcontentresolver-operate-with-RepoMapDataHa.patch -Patch0103: 0103-7-9-targetcontentresolver-setuptargetrepos-minor-upd.patch -Patch0104: 0104-8-9-targetcontentresolver-pes_events_scanner-refacto.patch -Patch0105: 0105-9-9-targetcontentresolver-Make-TODO-notes.patch - %description %{summary} @@ -236,7 +129,7 @@ Requires: leapp # uncompressing redhat-release package from the ISO. Requires: cpio -# Subpackage for managing fapolicyd rules for %{lpr_name} installed only if +# Subpackage for managing fapolicyd rules for %%{lpr_name} installed only if # fapolicyd is present on the system Requires: (%{lpr_name}-fapolicyd = %{version}-%{release} if fapolicyd) @@ -352,111 +245,6 @@ Requires: fapolicyd # APPLY PATCHES HERE # %%patch -P 0001 -p1 -%patch -P 0001 -p1 -%patch -P 0002 -p1 -%patch -P 0003 -p1 -%patch -P 0004 -p1 -%patch -P 0005 -p1 -%patch -P 0006 -p1 -%patch -P 0007 -p1 -%patch -P 0008 -p1 -%patch -P 0009 -p1 -%patch -P 0010 -p1 -%patch -P 0011 -p1 -%patch -P 0012 -p1 -%patch -P 0013 -p1 -%patch -P 0014 -p1 -%patch -P 0015 -p1 -%patch -P 0016 -p1 -%patch -P 0017 -p1 -%patch -P 0018 -p1 -%patch -P 0019 -p1 -%patch -P 0020 -p1 -%patch -P 0021 -p1 -%patch -P 0022 -p1 -%patch -P 0023 -p1 -%patch -P 0024 -p1 -%patch -P 0025 -p1 -%patch -P 0026 -p1 -%patch -P 0027 -p1 -%patch -P 0028 -p1 -%patch -P 0029 -p1 -%patch -P 0030 -p1 -%patch -P 0031 -p1 -%patch -P 0032 -p1 -%patch -P 0033 -p1 -%patch -P 0034 -p1 -%patch -P 0035 -p1 -%patch -P 0036 -p1 -%patch -P 0037 -p1 -%patch -P 0038 -p1 -%patch -P 0039 -p1 -%patch -P 0040 -p1 -%patch -P 0041 -p1 -%patch -P 0042 -p1 -%patch -P 0043 -p1 -%patch -P 0044 -p1 -%patch -P 0045 -p1 -%patch -P 0046 -p1 -%patch -P 0047 -p1 -%patch -P 0048 -p1 -%patch -P 0049 -p1 -%patch -P 0050 -p1 -%patch -P 0051 -p1 -%patch -P 0052 -p1 -%patch -P 0053 -p1 -%patch -P 0054 -p1 -%patch -P 0055 -p1 -%patch -P 0056 -p1 -%patch -P 0057 -p1 -%patch -P 0058 -p1 -%patch -P 0059 -p1 -%patch -P 0060 -p1 -%patch -P 0061 -p1 -%patch -P 0062 -p1 -%patch -P 0063 -p1 -%patch -P 0064 -p1 -%patch -P 0065 -p1 -%patch -P 0066 -p1 -%patch -P 0067 -p1 -%patch -P 0068 -p1 -%patch -P 0069 -p1 -%patch -P 0070 -p1 -%patch -P 0071 -p1 -%patch -P 0072 -p1 -%patch -P 0073 -p1 -%patch -P 0074 -p1 -%patch -P 0075 -p1 -%patch -P 0076 -p1 -%patch -P 0077 -p1 -%patch -P 0078 -p1 -%patch -P 0079 -p1 -%patch -P 0080 -p1 -%patch -P 0081 -p1 -%patch -P 0082 -p1 -%patch -P 0083 -p1 -%patch -P 0084 -p1 -%patch -P 0085 -p1 -%patch -P 0086 -p1 -%patch -P 0087 -p1 -%patch -P 0088 -p1 -%patch -P 0089 -p1 -%patch -P 0090 -p1 -%patch -P 0091 -p1 -%patch -P 0092 -p1 -%patch -P 0093 -p1 -%patch -P 0094 -p1 -%patch -P 0095 -p1 -%patch -P 0096 -p1 -%patch -P 0097 -p1 -%patch -P 0098 -p1 -%patch -P 0099 -p1 -%patch -P 0100 -p1 -%patch -P 0101 -p1 -%patch -P 0102 -p1 -%patch -P 0103 -p1 -%patch -P 0104 -p1 -%patch -P 0105 -p1 %build cp -a leapp*deps*el%{next_major_ver}.noarch.rpm repos/system_upgrade/%{repo_shortname}/files/bundled-rpms/ @@ -552,7 +340,16 @@ fi %changelog -- Thu Jul 25 2026 Karolina Kula - 0.24.0-3 +* Thu Jul 30 2026 Matej Matuska - 0.25.0-1 +- Rebase on upstream version 0.25.0 +- Inhibit the upgrade when machine ID is missing or invalid +- Fix check of Ceph/LUKS volumes based on UUIDs +- Do not crash when failing to mount a pseudo filesystem during upgrade +- Prevent hanging system upgrade boot on systems with RAID and defined ‘resume’ on kernel command line +- Introduce upgrades with conversions from Rocky Linux +- Resolves: RHEL-188370, RHEL-151413, RHEL-155964, RHEL-155965, RHEL-155972, RHEL-180965, RHEL-187004, RHEL-193717, RHEL-133938 + +* Thu Jun 25 2026 Karolina Kula - 0.24.0-3 - Requires leapp-framework 6.6+ - Initial implementation of upgrades on systems with configured Software RAID - Install the kernel variant matching the system memory page size on ARM systems @@ -577,8 +374,7 @@ fi - LiveMode: Fix system version determination in the upgrade environment on CentOS Stream - LiveMode: Fix upgrade getting stuck when the `.leapp_upgrade_failed` file exists - Unify behaviour of upgrades on systems with enabled CRB repositories and explicitly required CRB repositories by user during the upgrade -- Resolves: RHEL-111860, RHEL-115867, RHEL-121205, RHEL-147438, RHEL-148697, RHEL-151509, -RHEL-154369, RHEL-154370, RHEL-156580, RHEL-161560, RHEL-161684, RHEL-174874 +- Resolves: RHEL-111860, RHEL-115867, RHEL-121205, RHEL-147438, RHEL-148697, RHEL-151509, RHEL-154369, RHEL-154370, RHEL-156580, RHEL-161560, RHEL-161684, RHEL-174874 * Fri Apr 17 2026 Petr Stodulka - 0.24.0-2 - Introduce new upgrade path 9.9 -> 10.3 diff --git a/plans/tier0.fmf b/plans/tier0.fmf index 3725d8c..eb31d76 100644 --- a/plans/tier0.fmf +++ b/plans/tier0.fmf @@ -41,7 +41,12 @@ adjust: url: https://gitlab.cee.redhat.com/oamg/leapp-tests name: /plans/newstyle/upgrades/tests/destructive/test_basic.py::TestBasicRhsm$ ref: next - + /TestBasicLivemode: + plan: + import: + url: https://gitlab.cee.redhat.com/oamg/leapp-tests + name: /plans/newstyle/upgrades/tests/destructive/test_basic.py::TestBasicLivemode$ + ref: next /nondestructive: /tier0only: plan: @@ -49,3 +54,14 @@ adjust: url: https://gitlab.cee.redhat.com/oamg/leapp-tests name: /plans/newstyle/nondestructive/tier0only ref: next + +/verification: + context+: + verification: verification_99_103_rc # the only dynamic part to be changed per milestone/release + plan: + import: + url: https://gitlab.cee.redhat.com/oamg/leapp-tests + name: /plans/newstyle/.* # import all tests by name, the filtering magic happens on the leapp-tests level based on the context + scope: all-plans + importing: become-parent + ref: next diff --git a/sources b/sources index cb8304f..1dbc534 100644 --- a/sources +++ b/sources @@ -1,2 +1,2 @@ SHA512 (deps-pkgs-13.tar.gz) = 6892fc9ff2699fbdbc7d42e19944e0effc5b6e587deb0cd27d8285b8c3a9b88a09c0221214728ad7ed7156dbd8972331d5dc65c7ce80177d2f7f32d62167e67b -SHA512 (leapp-repository-0.24.0.tar.gz) = f324769b3614b0844f14dd0288f077372941661958729b2c3f013960b29eb205897d6893aeb23b331bc6c7e084e1b6726d3d473fe82054a3a3f52fa1f78f34dd +SHA512 (leapp-repository-0.25.0.tar.gz) = dfc05a9d1769e183935d75db6e9837c8ca9b8a63af1c90bf0086cd8f26d61a589029ffdcbaa9894f99bcfb788d34d82d304841b117eb9fa5f3d98b104afa526a