diff --git a/SOURCES/leapp-repository-0.22.0-elevate.patch b/SOURCES/leapp-repository-0.22.0-elevate.patch index fc0dbe3..40bcb5f 100644 --- a/SOURCES/leapp-repository-0.22.0-elevate.patch +++ b/SOURCES/leapp-repository-0.22.0-elevate.patch @@ -11,10 +11,34 @@ index 0bb92d3d..a04c7ded 100644 # pycharm .idea diff --git a/.packit.yaml b/.packit.yaml -index 75788a25..515586dc 100644 +index 75788a25..607dff93 100644 --- a/.packit.yaml +++ b/.packit.yaml -@@ -295,6 +295,80 @@ jobs: +@@ -168,6 +168,7 @@ jobs: + env: + SOURCE_RELEASE: "8.10" + TARGET_RELEASE: "9.4" ++ LEAPP_TARGET_PRODUCT_CHANNEL: "EUS" + + # On-demand minimal beaker tests + - &beaker-minimal-810to94 +@@ -194,6 +195,7 @@ jobs: + env: + SOURCE_RELEASE: "8.10" + TARGET_RELEASE: "9.4" ++ LEAPP_TARGET_PRODUCT_CHANNEL: "EUS" + + # On-demand kernel-rt tests + - &kernel-rt-810to94 +@@ -220,6 +222,7 @@ jobs: + env: + SOURCE_RELEASE: "8.10" + TARGET_RELEASE: "9.4" ++ LEAPP_TARGET_PRODUCT_CHANNEL: "EUS" + + # Tests: 8.10 -> 9.6 + - &sanity-810to96 +@@ -295,6 +298,80 @@ jobs: SOURCE_RELEASE: "8.10" TARGET_RELEASE: "9.6" @@ -95,7 +119,7 @@ index 75788a25..515586dc 100644 # ###################################################################### # # ############################## 9 TO 10 ################################ # # ###################################################################### # -@@ -351,6 +425,9 @@ jobs: +@@ -351,6 +428,9 @@ jobs: <<: *sanity-abstract-9to10 trigger: pull_request identifier: sanity-9.6to10.0 @@ -105,7 +129,7 @@ index 75788a25..515586dc 100644 tf_extra_params: test: tmt: -@@ -377,6 +454,9 @@ jobs: +@@ -377,6 +457,9 @@ jobs: - beaker-minimal-9.6to10.0 - 9.6to10.0 identifier: sanity-9.6to10.0-beaker-minimal-ondemand @@ -115,7 +139,7 @@ index 75788a25..515586dc 100644 tf_extra_params: test: tmt: -@@ -419,3 +499,86 @@ jobs: +@@ -419,3 +502,86 @@ jobs: env: SOURCE_RELEASE: "9.6" TARGET_RELEASE: "10.0" @@ -3722,6 +3746,89 @@ index 7d5b563e..dadfe7de 100644 os.environ['LEAPP_NO_RHSM'] = '1' elif not os.path.exists('/usr/sbin/subscription-manager'): os.environ['LEAPP_NO_RHSM'] = '1' +diff --git a/docs/source/libraries-and-api/deprecations-list.md b/docs/source/libraries-and-api/deprecations-list.md +index b3abfc5d..8b419800 100644 +--- a/docs/source/libraries-and-api/deprecations-list.md ++++ b/docs/source/libraries-and-api/deprecations-list.md +@@ -1,7 +1,7 @@ + # Deprecated functionality + Deprecated functionality is listed under the first version that the functionality +-is deprecated in. Note that functionality may be deprecated in later versions +-but are not listed again. ++is deprecated in. Note that functionality may be removed in later versions ++but will not be listed again. + The dates in brackets correspond to the end of the deprecation protection period, + after which the related functionality can be removed at any time. + +@@ -17,7 +17,8 @@ Only the versions in which a deprecation has been made are listed. + - Shared libraries + - **`leapp.libraries.common.config.version.SUPPORTED_VERSIONS`** - The `SUPPORTED_VERSIONS` dict has been deprecated as it is problematic with the new design. Use `leapp.libraries.common.config.version.is_supported_version()` or `IPUConfig.supported_upgrade_paths` instead. + - **`leapp.libraries.common.config.version.is_rhel_alt()`** - The function can return only `False` nowadays as RHEL-ALT 7 is EOL for years and future version of leapp-repository will not support RHEL 7 anymore. +- ++- Models ++ - **`InstalledUnsignedRPM`** - Replaced by the distribution agnostic `ThirdPartyRPM`. + + ## v0.20.0 (till September 2024) + - Models +diff --git a/etc/fapolicyd/rules.d/31-leapp-repository.rules b/etc/fapolicyd/rules.d/31-leapp-repository.rules +new file mode 100644 +index 00000000..2fd3dc6e +--- /dev/null ++++ b/etc/fapolicyd/rules.d/31-leapp-repository.rules +@@ -0,0 +1,2 @@ ++allow perm=any all : dir=/var/lib/leapp/ ++ +diff --git a/etc/leapp/files/device_driver_deprecation_data.json b/etc/leapp/files/device_driver_deprecation_data.json +index 4531fb08..6d5d6ef9 100644 +--- a/etc/leapp/files/device_driver_deprecation_data.json ++++ b/etc/leapp/files/device_driver_deprecation_data.json +@@ -2726,7 +2726,7 @@ + ], + "deprecation_announced": "", + "device_id": "0x9005:0x0200:0x9005:0x0200", +- "device_name": "", ++ "device_name": "Adaptec: AAC-RAID: Themisto Jupiter Platform", + "device_type": "pci", + "driver_name": "aacraid", + "maintained_in_rhel": [ +@@ -2824,6 +2824,19 @@ + 7 + ] + }, ++ { ++ "available_in_rhel": [ ++ 7 ++ ], ++ "deprecation_announced": "", ++ "device_id": "0x9005:0x0285:0x1028:0x0291", ++ "device_name": "Adaptec: AAC-RAID: CERC SATA RAID 2 PCI SATA 6ch (DellCorsair)", ++ "device_type": "pci", ++ "driver_name": "aacraid", ++ "maintained_in_rhel": [ ++ 7 ++ ] ++ }, + { + "available_in_rhel": [ + 7 +@@ -3363,7 +3376,7 @@ + ], + "deprecation_announced": "", + "device_id": "0x9005:0x0287:0x9005:0x0800", +- "device_name": "", ++ "device_name": "Adaptec: AAC-RAID: Themisto Jupiter Platform", + "device_type": "pci", + "driver_name": "aacraid", + "maintained_in_rhel": [ +@@ -3376,7 +3389,7 @@ + ], + "deprecation_announced": "", + "device_id": "0x9005:0x0288", +- "device_name": "", ++ "device_name": "Adaptec: AAC-RAID: NEMER/ARK Catch All", + "device_type": "pci", + "driver_name": "aacraid", + "maintained_in_rhel": [ diff --git a/etc/leapp/files/pes-events.json b/etc/leapp/files/pes-events.json index e9da4873..da62837f 100644 --- a/etc/leapp/files/pes-events.json @@ -19204,6 +19311,234 @@ index e9da4873..da62837f 100644 } ] } +diff --git a/etc/leapp/files/repomap.json b/etc/leapp/files/repomap.json +index 0cd5601a..87646393 100644 +--- a/etc/leapp/files/repomap.json ++++ b/etc/leapp/files/repomap.json +@@ -1,5 +1,5 @@ + { +- "datetime": "202507171303Z", ++ "datetime": "202508131404Z", + "version_format": "1.3.0", + "provided_data_streams": [ + "4.0" +@@ -312,6 +312,24 @@ + "target": [ + "rhel10-rhui-custom-client-at-alibaba" + ] ++ }, ++ { ++ "source": "rhel9-rhui-client-config-server-9-sap", ++ "target": [ ++ "rhel10-rhui-client-config-server-10-sap" ++ ] ++ }, ++ { ++ "source": "rhel9-rhui-microsoft-azure-sap-apps", ++ "target": [ ++ "rhel10-rhui-microsoft-azure-sap-apps" ++ ] ++ }, ++ { ++ "source": "rhel9-rhui-microsoft-sap-ha", ++ "target": [ ++ "rhel10-rhui-microsoft-sap-ha" ++ ] + } + ] + } +@@ -501,6 +519,15 @@ + "distro": "rhel", + "rhui": "aws" + }, ++ { ++ "major_version": "10", ++ "repoid": "rhel-10-for-x86_64-baseos-e4s-rhui-rpms", ++ "arch": "x86_64", ++ "channel": "e4s", ++ "repo_type": "rpm", ++ "distro": "rhel", ++ "rhui": "azure" ++ }, + { + "major_version": "10", + "repoid": "rhel-10-for-x86_64-baseos-e4s-rpms", +@@ -509,6 +536,15 @@ + "repo_type": "rpm", + "distro": "rhel" + }, ++ { ++ "major_version": "10", ++ "repoid": "rhel-10-for-x86_64-baseos-eus-rhui-rpms", ++ "arch": "x86_64", ++ "channel": "eus", ++ "repo_type": "rpm", ++ "distro": "rhel", ++ "rhui": "azure" ++ }, + { + "major_version": "10", + "repoid": "rhel-10-for-x86_64-baseos-eus-rpms", +@@ -729,6 +765,15 @@ + "distro": "rhel", + "rhui": "aws" + }, ++ { ++ "major_version": "10", ++ "repoid": "rhel-10-for-x86_64-appstream-e4s-rhui-rpms", ++ "arch": "x86_64", ++ "channel": "e4s", ++ "repo_type": "rpm", ++ "distro": "rhel", ++ "rhui": "azure" ++ }, + { + "major_version": "10", + "repoid": "rhel-10-for-x86_64-appstream-e4s-rpms", +@@ -737,6 +782,15 @@ + "repo_type": "rpm", + "distro": "rhel" + }, ++ { ++ "major_version": "10", ++ "repoid": "rhel-10-for-x86_64-appstream-eus-rhui-rpms", ++ "arch": "x86_64", ++ "channel": "eus", ++ "repo_type": "rpm", ++ "distro": "rhel", ++ "rhui": "azure" ++ }, + { + "major_version": "10", + "repoid": "rhel-10-for-x86_64-appstream-eus-rpms", +@@ -1303,6 +1357,15 @@ + "distro": "rhel", + "rhui": "aws" + }, ++ { ++ "major_version": "10", ++ "repoid": "rhel-10-for-x86_64-sap-netweaver-e4s-rhui-rpms", ++ "arch": "x86_64", ++ "channel": "e4s", ++ "repo_type": "rpm", ++ "distro": "rhel", ++ "rhui": "azure" ++ }, + { + "major_version": "10", + "repoid": "rhel-10-for-x86_64-sap-netweaver-e4s-rpms", +@@ -1311,6 +1374,15 @@ + "repo_type": "rpm", + "distro": "rhel" + }, ++ { ++ "major_version": "10", ++ "repoid": "rhel-10-for-x86_64-sap-netweaver-eus-rhui-rpms", ++ "arch": "x86_64", ++ "channel": "eus", ++ "repo_type": "rpm", ++ "distro": "rhel", ++ "rhui": "azure" ++ }, + { + "major_version": "10", + "repoid": "rhel-10-for-x86_64-sap-netweaver-eus-rpms", +@@ -1357,6 +1429,15 @@ + "distro": "rhel", + "rhui": "aws" + }, ++ { ++ "major_version": "10", ++ "repoid": "rhel-10-for-x86_64-sap-solutions-e4s-rhui-rpms", ++ "arch": "x86_64", ++ "channel": "e4s", ++ "repo_type": "rpm", ++ "distro": "rhel", ++ "rhui": "azure" ++ }, + { + "major_version": "10", + "repoid": "rhel-10-for-x86_64-sap-solutions-e4s-rpms", +@@ -1523,6 +1604,15 @@ + "distro": "rhel", + "rhui": "aws" + }, ++ { ++ "major_version": "10", ++ "repoid": "rhel-10-for-x86_64-highavailability-e4s-rhui-rpms", ++ "arch": "x86_64", ++ "channel": "e4s", ++ "repo_type": "rpm", ++ "distro": "rhel", ++ "rhui": "azure" ++ }, + { + "major_version": "10", + "repoid": "rhel-10-for-x86_64-highavailability-e4s-rpms", +@@ -1618,6 +1708,48 @@ + } + ] + }, ++ { ++ "pesid": "rhel10-rhui-client-config-server-10-sap", ++ "entries": [ ++ { ++ "major_version": "10", ++ "repoid": "rhui-client-config-server-10-sap-bundle", ++ "arch": "x86_64", ++ "channel": "ga", ++ "repo_type": "rpm", ++ "distro": "rhel", ++ "rhui": "aws" ++ } ++ ] ++ }, ++ { ++ "pesid": "rhel10-rhui-microsoft-azure-sap-apps", ++ "entries": [ ++ { ++ "major_version": "10", ++ "repoid": "rhui-microsoft-azure-rhel10-sapapps", ++ "arch": "x86_64", ++ "channel": "eus", ++ "repo_type": "rpm", ++ "distro": "rhel", ++ "rhui": "azure" ++ } ++ ] ++ }, ++ { ++ "pesid": "rhel10-rhui-microsoft-sap-ha", ++ "entries": [ ++ { ++ "major_version": "10", ++ "repoid": "rhui-microsoft-azure-rhel10-sap-ha", ++ "arch": "x86_64", ++ "channel": "e4s", ++ "repo_type": "rpm", ++ "distro": "rhel", ++ "rhui": "azure" ++ } ++ ] ++ }, + { + "pesid": "rhel7-base", + "entries": [ +@@ -4166,6 +4298,14 @@ + "repo_type": "rpm", + "distro": "rhel" + }, ++ { ++ "major_version": "8", ++ "repoid": "rhel-8-for-x86_64-highavailability-aus-rpms", ++ "arch": "x86_64", ++ "channel": "aus", ++ "repo_type": "rpm", ++ "distro": "rhel" ++ }, + { + "major_version": "8", + "repoid": "rhel-8-for-x86_64-highavailability-beta-rpms", diff --git a/etc/leapp/transaction/to_reinstall b/etc/leapp/transaction/to_reinstall new file mode 100644 index 00000000..c6694a8e @@ -19213,6 +19548,71 @@ index 00000000..c6694a8e +### List of packages (each on new line) to be reinstalled to the upgrade transaction +### Useful for packages that have identical version strings but contain binary changes between major OS versions +### Packages that aren't installed will be skipped +diff --git a/packaging/leapp-repository.spec b/packaging/leapp-repository.spec +index 34768de1..6d96d665 100644 +--- a/packaging/leapp-repository.spec ++++ b/packaging/leapp-repository.spec +@@ -130,6 +130,10 @@ Requires: leapp + # uncompressing redhat-release package from the ISO. + Requires: cpio + ++# 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) ++ + # The leapp-repository rpm is renamed to %%{lpr_name} + Obsoletes: leapp-repository < 0.14.0-%{release} + Provides: leapp-repository = %{version}-%{release} +@@ -227,6 +231,15 @@ Requires: libdb-utils + %{summary} + + ++%package -n %{lpr_name}-fapolicyd ++Summary: Manage fapolicyd rules for %{lpr_name} during the upgrade ++ ++Requires: fapolicyd ++ ++%description -n %{lpr_name}-fapolicyd ++%{summary} ++ ++ + %prep + %setup -n %{name}-%{version} + %setup -q -n %{name}-%{version} -D -T -a 1 +@@ -250,6 +263,10 @@ install -m 0755 -d %{buildroot}%{_sysconfdir}/leapp/files/ + install -m 0644 etc/leapp/transaction/* %{buildroot}%{_sysconfdir}/leapp/transaction + install -m 0644 etc/leapp/files/* %{buildroot}%{_sysconfdir}/leapp/files + ++# install rules necessary for fapolicy ++mkdir -p %{buildroot}%{_sysconfdir}/fapolicyd/rules.d/ ++install -m 0644 etc/fapolicyd/rules.d/31-leapp-repository.rules %{buildroot}%{_sysconfdir}/fapolicyd/rules.d ++ + # uncomment to install existing configs if any exists + #install -m 0644 etc/leapp/actor_conf.d/* %%{buildroot}%%{_sysconfdir}/leapp/actor_conf.d + +@@ -291,6 +308,12 @@ done; + %endif + + ++%posttrans -n %{lpr_name}-fapolicyd ++if systemctl is-active --quiet fapolicyd; then ++ systemctl restart fapolicyd ++fi ++ ++ + %files -n %{lpr_name} + %doc README.md + %license LICENSE +@@ -313,6 +336,9 @@ done; + # no files here + + ++%files -n %{lpr_name}-fapolicyd ++%attr(644, root, fapolicyd) %config %{_sysconfdir}/fapolicyd/rules.d/31-leapp-repository.rules ++ + # DO NOT TOUCH SECTION BELOW IN UPSTREAM + %changelog + * Mon Apr 16 2018 Vinzenz Feenstra - %{version}-%{release} diff --git a/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py b/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py index b28ec57c..6882488a 100644 --- a/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py @@ -19298,6 +19698,71 @@ index 00000000..52f5af9d + api.produce(ActiveVendorList(data=list(active_vendors))) + else: + self.log.info("No active vendors found, vendor list not generated") +diff --git a/repos/system_upgrade/common/actors/checkleftoverpackages/actor.py b/repos/system_upgrade/common/actors/checkleftoverpackages/actor.py +index a4e32923..e60075a6 100644 +--- a/repos/system_upgrade/common/actors/checkleftoverpackages/actor.py ++++ b/repos/system_upgrade/common/actors/checkleftoverpackages/actor.py +@@ -1,6 +1,6 @@ + from leapp.actors import Actor + from leapp.libraries.actor import checkleftoverpackages +-from leapp.models import InstalledUnsignedRPM, LeftoverPackages, TransactionCompleted ++from leapp.models import LeftoverPackages, ThirdPartyRPM, TransactionCompleted + from leapp.tags import IPUWorkflowTag, RPMUpgradePhaseTag + + +@@ -13,7 +13,7 @@ class CheckLeftoverPackages(Actor): + """ + + name = 'check_leftover_packages' +- consumes = (TransactionCompleted, InstalledUnsignedRPM) ++ consumes = (TransactionCompleted, ThirdPartyRPM) + produces = (LeftoverPackages,) + tags = (RPMUpgradePhaseTag, IPUWorkflowTag) + +diff --git a/repos/system_upgrade/common/actors/checkleftoverpackages/libraries/checkleftoverpackages.py b/repos/system_upgrade/common/actors/checkleftoverpackages/libraries/checkleftoverpackages.py +index 83160f1b..75c1e91e 100644 +--- a/repos/system_upgrade/common/actors/checkleftoverpackages/libraries/checkleftoverpackages.py ++++ b/repos/system_upgrade/common/actors/checkleftoverpackages/libraries/checkleftoverpackages.py +@@ -3,7 +3,7 @@ import re + from leapp.libraries.common.config.version import get_source_major_version + from leapp.libraries.common.rpms import get_installed_rpms, get_leapp_dep_packages, get_leapp_packages + from leapp.libraries.stdlib import api +-from leapp.models import InstalledUnsignedRPM, LeftoverPackages, RPM ++from leapp.models import LeftoverPackages, RPM, ThirdPartyRPM + + + def process(): +@@ -15,7 +15,7 @@ def process(): + return + + leftover_pkgs_to_remove = [] +- unsigned = [pkg.name for pkg in next(api.consume(InstalledUnsignedRPM), InstalledUnsignedRPM()).items] ++ unsigned = [pkg.name for pkg in next(api.consume(ThirdPartyRPM), ThirdPartyRPM()).items] + + for rpm in installed_rpms: + rpm = rpm.strip() +diff --git a/repos/system_upgrade/common/actors/checkleftoverpackages/tests/test_checkleftoverpackages.py b/repos/system_upgrade/common/actors/checkleftoverpackages/tests/test_checkleftoverpackages.py +index 75e0120c..33c16a96 100644 +--- a/repos/system_upgrade/common/actors/checkleftoverpackages/tests/test_checkleftoverpackages.py ++++ b/repos/system_upgrade/common/actors/checkleftoverpackages/tests/test_checkleftoverpackages.py +@@ -3,7 +3,7 @@ import pytest + from leapp.libraries.actor import checkleftoverpackages + from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked + from leapp.libraries.stdlib import api +-from leapp.models import InstalledUnsignedRPM, LeftoverPackages, RPM ++from leapp.models import LeftoverPackages, RPM, ThirdPartyRPM + + + @pytest.mark.parametrize( +@@ -44,7 +44,7 @@ def test_package_to_be_removed(monkeypatch, source_major_version, rpm_name, rele + UnsignedRPM = RPM(name='unsigned', version='0.1', release=release, epoch='0', + packager='packager', arch='noarch', pgpsig='OTHER_SIG') + +- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[InstalledUnsignedRPM(items=[UnsignedRPM])])) ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[ThirdPartyRPM(items=[UnsignedRPM])])) + monkeypatch.setattr(checkleftoverpackages, 'get_installed_rpms', get_installed_rpms_mocked) + monkeypatch.setattr(checkleftoverpackages, 'get_source_major_version', lambda: str(source_major_version)) + monkeypatch.setattr(api, 'produce', produce_mocked()) 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 56a94b5d..46c5d9b6 100755 --- a/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/do-upgrade.sh @@ -19307,28 +19772,244 @@ index 56a94b5d..46c5d9b6 100755 mount -o "remount,$old_opts" "$NEWROOT" exit $result - +diff --git a/repos/system_upgrade/common/actors/distributionsignedrpmcheck/actor.py b/repos/system_upgrade/common/actors/distributionsignedrpmcheck/actor.py +new file mode 100644 +index 00000000..de558a48 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/distributionsignedrpmcheck/actor.py +@@ -0,0 +1,36 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor.distributionsignedrpmcheck import check_third_party_pkgs ++from leapp.models import ThirdPartyRPM ++from leapp.reporting import Report ++from leapp.tags import ChecksPhaseTag, IPUWorkflowTag ++ ++ ++class DistributionSignedRpmCheck(Actor): ++ """ ++ Check if there are any packages that are not signed by distribution GPG keys. ++ ++ We are recognizing two (three) types of packages: ++ * Distribution packages - RPMs that are part of the system distribution (RHEL, ++ Centos Stream, Fedora, ...) - which are recognized based on the signature ++ by known GPG keys for the particular distribution. ++ * Third-party packages - RPMs that are not signed by such GPG keys - ++ including RPMs not signed at all. Such RPMs are considered in general as ++ third party content. ++ ( ++ * some packages are known to not be signed as they are created by ++ delivered product (which can be part of the distribution). This includes ++ e.g. katello RPMs created in a Satellite server. We do not report ++ such packages known to us. ++ ) ++ ++ All such third-party installed packages are reported to inform the user to ++ take care of them before, during or after the upgrade. ++ """ ++ ++ name = 'distribution_signed_rpm_check' ++ consumes = (ThirdPartyRPM,) ++ produces = (Report,) ++ tags = (IPUWorkflowTag, ChecksPhaseTag) ++ ++ def process(self): ++ check_third_party_pkgs() +diff --git a/repos/system_upgrade/common/actors/distributionsignedrpmcheck/libraries/distributionsignedrpmcheck.py b/repos/system_upgrade/common/actors/distributionsignedrpmcheck/libraries/distributionsignedrpmcheck.py +new file mode 100644 +index 00000000..1c0df635 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/distributionsignedrpmcheck/libraries/distributionsignedrpmcheck.py +@@ -0,0 +1,80 @@ ++from leapp import reporting ++from leapp.libraries.stdlib import api ++from leapp.libraries.stdlib.config import is_verbose ++from leapp.models import ThirdPartyRPM ++ ++FMT_LIST_SEPARATOR = "\n - " ++ ++ ++def _generate_report(packages): ++ """Generate a report with installed packages not signed by the distribution""" ++ ++ if not packages: ++ return ++ ++ title = "Packages not signed by the distribution vendor found on the system" ++ summary = ( ++ "The official solution for in-place upgrades contains instructions for" ++ " the migration of packages signed by the vendor of the system" ++ " distribution. Third-party content is not known to the upgrade" ++ " process and it might need to be handled extra." ++ " Third-party packages may be removed automatically during the upgrade if" ++ " they depend on official distribution content which is not present on" ++ " the target system - therefore RPM dependencies of such packages" ++ " cannot be satisfied and hence such packages cannot be installed on" ++ " the target system.\n\n" ++ "The following packages have not been signed by the vendor of the" ++ " distribution:{}{}" ++ ).format(FMT_LIST_SEPARATOR, FMT_LIST_SEPARATOR.join(packages)) ++ hint = ( ++ "The most simple solution that does not require additional knowledge" ++ " about the upgrade process is the uninstallation of such packages" ++ " before the upgrade and installing these (or their newer versions" ++ " compatible with the target system) back after the upgrade. Also you" ++ " can just try to upgrade the system on a testing machine (or after" ++ " the full system backup) to see the result.\n" ++ "However, it is common use case to migrate or upgrade installed third" ++ " party packages together with the system during the in-place upgrade" ++ " process. To examine how to customize the process to deal with such" ++ " packages, follow the documentation in the attached link" ++ " for more details." ++ ) ++ reporting.create_report( ++ [ ++ reporting.Title(title), ++ reporting.Summary(summary), ++ reporting.Severity(reporting.Severity.HIGH), ++ reporting.Groups([reporting.Groups.SANITY]), ++ reporting.Remediation(hint=hint), ++ reporting.ExternalLink( ++ url="https://red.ht/customize-rhel-upgrade-actors", ++ title="Handling the migration of your custom and third-party applications", ++ ), ++ # setting a stable key of the original, semantically equal, report ++ # which was concerned with RHEL only ++ reporting.Key("13f0791ae5f19f50e7d0d606fb6501f91b1efb2c") ++ ] ++ ) ++ ++ if is_verbose(): ++ api.show_message(summary) ++ ++ ++def get_third_party_pkgs(): ++ """Get a list of installed packages not signed by the distribution""" ++ ++ rpm_messages = api.consume(ThirdPartyRPM) ++ data = next(rpm_messages, ThirdPartyRPM()) ++ if list(rpm_messages): ++ api.current_logger().warning( ++ "Unexpectedly received more than one ThirdPartyRPM message." ++ ) ++ ++ third_party_pkgs = list(set(pkg.name for pkg in data.items)) ++ third_party_pkgs.sort() ++ return third_party_pkgs ++ ++ ++def check_third_party_pkgs(): ++ """Check and generate a report if the system contains third-party installed packages""" ++ _generate_report(get_third_party_pkgs()) +diff --git a/repos/system_upgrade/common/actors/distributionsignedrpmcheck/tests/test_distributionsignedrpmcheck.py b/repos/system_upgrade/common/actors/distributionsignedrpmcheck/tests/test_distributionsignedrpmcheck.py +new file mode 100644 +index 00000000..54271409 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/distributionsignedrpmcheck/tests/test_distributionsignedrpmcheck.py +@@ -0,0 +1,81 @@ ++from leapp import reporting ++from leapp.libraries.actor import distributionsignedrpmcheck ++from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked ++from leapp.libraries.stdlib import api ++from leapp.models import RPM, ThirdPartyRPM ++ ++RH_PACKAGER = 'Red Hat, Inc. ' ++ ++ ++def test_actor_execution_without_third_party_pkgs(monkeypatch): ++ third_party_rpm = ThirdPartyRPM(items=[]) ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[third_party_rpm])) ++ monkeypatch.setattr(api, "show_message", lambda x: True) ++ monkeypatch.setattr(reporting, "create_report", create_report_mocked()) ++ ++ packages = distributionsignedrpmcheck.get_third_party_pkgs() ++ ++ assert not packages ++ distributionsignedrpmcheck._generate_report(packages) ++ assert reporting.create_report.called == 0 ++ ++ ++def test_actor_execution_with_third_party_pkgs(monkeypatch): ++ installed_rpm = ThirdPartyRPM( ++ items=[ ++ RPM( ++ name="sample02", ++ version="0.1", ++ release="1.sm01", ++ epoch="1", ++ packager=RH_PACKAGER, ++ arch="noarch", ++ pgpsig="SOME_OTHER_SIG_X", ++ ), ++ RPM( ++ name="sample04", ++ version="0.1", ++ release="1.sm01", ++ epoch="1", ++ packager=RH_PACKAGER, ++ arch="noarch", ++ pgpsig="SOME_OTHER_SIG_X", ++ ), ++ RPM( ++ name="sample06", ++ version="0.1", ++ release="1.sm01", ++ epoch="1", ++ packager=RH_PACKAGER, ++ arch="noarch", ++ pgpsig="SOME_OTHER_SIG_X", ++ ), ++ RPM( ++ name="sample08", ++ version="0.1", ++ release="1.sm01", ++ epoch="1", ++ packager=RH_PACKAGER, ++ arch="noarch", ++ pgpsig="SOME_OTHER_SIG_X", ++ ), ++ ] ++ ) ++ monkeypatch.setattr(api, "current_actor", CurrentActorMocked(msgs=[installed_rpm])) ++ monkeypatch.setattr(api, "show_message", lambda x: True) ++ monkeypatch.setattr(reporting, "create_report", create_report_mocked()) ++ ++ packages = distributionsignedrpmcheck.get_third_party_pkgs() ++ assert len(packages) == 4 ++ distributionsignedrpmcheck._generate_report(packages) ++ ++ assert reporting.create_report.called == 1 ++ assert ( ++ "Packages not signed by the distribution vendor found on the system" ++ in reporting.create_report.report_fields["title"] ++ ) ++ # the key of the pre-generalization, original RHEL focused report ++ assert ( ++ reporting.create_report.report_fields["key"] ++ == "13f0791ae5f19f50e7d0d606fb6501f91b1efb2c" ++ ) diff --git a/repos/system_upgrade/common/actors/distributionsignedrpmscanner/actor.py b/repos/system_upgrade/common/actors/distributionsignedrpmscanner/actor.py -index 56016513..7ae1dd5a 100644 +index 56016513..9e7bbf4a 100644 --- a/repos/system_upgrade/common/actors/distributionsignedrpmscanner/actor.py +++ b/repos/system_upgrade/common/actors/distributionsignedrpmscanner/actor.py -@@ -1,6 +1,6 @@ +@@ -1,14 +1,14 @@ from leapp.actors import Actor from leapp.libraries.actor import distributionsignedrpmscanner -from leapp.models import DistributionSignedRPM, InstalledRedHatSignedRPM, InstalledRPM, InstalledUnsignedRPM -+from leapp.models import DistributionSignedRPM, InstalledRedHatSignedRPM, InstalledRPM, InstalledUnsignedRPM, VendorSignatures ++from leapp.models import DistributionSignedRPM, InstalledRPM, InstalledUnsignedRPM, ThirdPartyRPM, VendorSignatures from leapp.tags import FactsPhaseTag, IPUWorkflowTag from leapp.utils.deprecation import suppress_deprecation -@@ -8,7 +8,7 @@ from leapp.utils.deprecation import suppress_deprecation - @suppress_deprecation(InstalledRedHatSignedRPM) + +-@suppress_deprecation(InstalledRedHatSignedRPM) ++@suppress_deprecation(InstalledUnsignedRPM) class DistributionSignedRpmScanner(Actor): """ - Provide data about distribution signed & unsigned RPM packages. -+ Provide data about distribution plus vendors signed & unsigned RPM packages. ++ Provide data about distribution signed & third-party plus vendors RPM packages. For various checks and actions done during the upgrade it's important to know what packages are signed by GPG keys of the installed linux system -@@ -22,11 +22,18 @@ class DistributionSignedRpmScanner(Actor): +@@ -22,12 +22,19 @@ class DistributionSignedRpmScanner(Actor): common/files/distro//gpg_signatures.json where is distribution ID of the installed system (e.g. centos, rhel). @@ -19344,18 +20025,121 @@ index 56016513..7ae1dd5a 100644 name = 'distribution_signed_rpm_scanner' - consumes = (InstalledRPM,) +- produces = (DistributionSignedRPM, InstalledRedHatSignedRPM, InstalledUnsignedRPM,) + consumes = (InstalledRPM, VendorSignatures) - produces = (DistributionSignedRPM, InstalledRedHatSignedRPM, InstalledUnsignedRPM,) ++ produces = (DistributionSignedRPM, InstalledUnsignedRPM, ThirdPartyRPM) tags = (IPUWorkflowTag, FactsPhaseTag) + def process(self): +diff --git a/repos/system_upgrade/common/actors/distributionsignedrpmscanner/libraries/distributionsignedrpmscanner.py b/repos/system_upgrade/common/actors/distributionsignedrpmscanner/libraries/distributionsignedrpmscanner.py +index 51d6eeb5..18c859e2 100644 +--- a/repos/system_upgrade/common/actors/distributionsignedrpmscanner/libraries/distributionsignedrpmscanner.py ++++ b/repos/system_upgrade/common/actors/distributionsignedrpmscanner/libraries/distributionsignedrpmscanner.py +@@ -2,7 +2,8 @@ from leapp.libraries.common import rhui + from leapp.libraries.common.config import get_env + from leapp.libraries.common.distro import get_distribution_data + from leapp.libraries.stdlib import api +-from leapp.models import DistributionSignedRPM, InstalledRedHatSignedRPM, InstalledRPM, InstalledUnsignedRPM ++from leapp.models import DistributionSignedRPM, InstalledRPM, InstalledUnsignedRPM, ThirdPartyRPM ++from leapp.utils.deprecation import suppress_deprecation + + + def is_distro_signed(pkg, distro_keys): +@@ -29,6 +30,7 @@ def is_exceptional(pkg, allowlist): + return pkg.name == 'gpg-pubkey' or pkg.name.startswith('katello-ca-consumer') or pkg.name in allowlist + + ++@suppress_deprecation(InstalledUnsignedRPM) + def process(): + distribution = api.current_actor().configuration.os_release.release_id + distro_keys = get_distribution_data(distribution).get('keys', []) +@@ -36,18 +38,17 @@ def process(): + rhui_pkgs = rhui.get_all_known_rhui_pkgs_for_current_upg() + + signed_pkgs = DistributionSignedRPM() +- rh_signed_pkgs = InstalledRedHatSignedRPM() + unsigned_pkgs = InstalledUnsignedRPM() ++ thirdparty_pkgs = ThirdPartyRPM() + + for rpm_pkgs in api.consume(InstalledRPM): + for pkg in rpm_pkgs.items: + if all_signed or is_distro_signed(pkg, distro_keys) or is_exceptional(pkg, rhui_pkgs): + signed_pkgs.items.append(pkg) +- if distribution == 'rhel': +- rh_signed_pkgs.items.append(pkg) +- continue +- unsigned_pkgs.items.append(pkg) ++ else: ++ unsigned_pkgs.items.append(pkg) ++ thirdparty_pkgs.items.append(pkg) + + api.produce(signed_pkgs) +- api.produce(rh_signed_pkgs) + api.produce(unsigned_pkgs) ++ api.produce(thirdparty_pkgs) 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 f138bcb2..af21bc8d 100644 +index f138bcb2..f55a2295 100644 --- a/repos/system_upgrade/common/actors/distributionsignedrpmscanner/tests/test_distributionsignedrpmscanner.py +++ b/repos/system_upgrade/common/actors/distributionsignedrpmscanner/tests/test_distributionsignedrpmscanner.py -@@ -110,6 +110,39 @@ def test_actor_execution_with_signed_unsigned_data_centos(current_actor_context) +@@ -5,13 +5,13 @@ from leapp.libraries.common.config import mock_configs + from leapp.models import ( + DistributionSignedRPM, + fields, +- InstalledRedHatSignedRPM, + InstalledRPM, + InstalledUnsignedRPM, + IPUConfig, + Model, + OSRelease, +- RPM ++ RPM, ++ ThirdPartyRPM + ) + + RH_PACKAGER = 'Red Hat, Inc. ' +@@ -33,11 +33,11 @@ class MockModel(Model): + def test_no_installed_rpms(current_actor_context): + current_actor_context.run(config_model=mock_configs.CONFIG) + assert current_actor_context.consume(DistributionSignedRPM) +- assert current_actor_context.consume(InstalledRedHatSignedRPM) + assert current_actor_context.consume(InstalledUnsignedRPM) ++ assert current_actor_context.consume(ThirdPartyRPM) + + +-def test_actor_execution_with_signed_unsigned_data(current_actor_context): ++def test_actor_execution_with_signed_and_third_party_pkgs(current_actor_context): + installed_rpm = [ + RPM(name='sample01', version='0.1', release='1.sm01', epoch='1', packager=RH_PACKAGER, arch='noarch', + pgpsig='RSA/SHA256, Mon 01 Jan 1970 00:00:00 AM -03, Key ID 199e2f91fd431d51'), +@@ -62,13 +62,13 @@ def test_actor_execution_with_signed_unsigned_data(current_actor_context): + current_actor_context.run(config_model=mock_configs.CONFIG) + assert current_actor_context.consume(DistributionSignedRPM) + assert len(current_actor_context.consume(DistributionSignedRPM)[0].items) == 5 +- assert current_actor_context.consume(InstalledRedHatSignedRPM) +- assert len(current_actor_context.consume(InstalledRedHatSignedRPM)[0].items) == 5 + assert current_actor_context.consume(InstalledUnsignedRPM) + assert len(current_actor_context.consume(InstalledUnsignedRPM)[0].items) == 4 ++ assert current_actor_context.consume(ThirdPartyRPM) ++ assert len(current_actor_context.consume(ThirdPartyRPM)[0].items) == 4 + + +-def test_actor_execution_with_signed_unsigned_data_centos(current_actor_context): ++def test_actor_execution_with_signed_and_third_party_pkgs_centos(current_actor_context): + CENTOS_PACKAGER = 'CentOS BuildSystem ' + config = mock_configs.CONFIG + +@@ -104,10 +104,41 @@ def test_actor_execution_with_signed_unsigned_data_centos(current_actor_context) + current_actor_context.run(config_model=config) + assert current_actor_context.consume(DistributionSignedRPM) + assert len(current_actor_context.consume(DistributionSignedRPM)[0].items) == 3 +- assert current_actor_context.consume(InstalledRedHatSignedRPM) +- assert not current_actor_context.consume(InstalledRedHatSignedRPM)[0].items + assert current_actor_context.consume(InstalledUnsignedRPM) assert len(current_actor_context.consume(InstalledUnsignedRPM)[0].items) == 6 - - ++ assert current_actor_context.consume(ThirdPartyRPM) ++ assert len(current_actor_context.consume(ThirdPartyRPM)[0].items) == 6 ++ ++ +def test_actor_execution_with_signed_unsigned_data_almalinux(current_actor_context): + ALMALINUX_PACKAGER = 'AlmaLinux Packaging Team ' + config = mock_configs.CONFIG @@ -19383,15 +20167,66 @@ index f138bcb2..af21bc8d 100644 + current_actor_context.run(config_model=config) + assert current_actor_context.consume(DistributionSignedRPM) + assert len(current_actor_context.consume(DistributionSignedRPM)[0].items) == 2 -+ assert current_actor_context.consume(InstalledRedHatSignedRPM) -+ assert not current_actor_context.consume(InstalledRedHatSignedRPM)[0].items + assert current_actor_context.consume(InstalledUnsignedRPM) + assert len(current_actor_context.consume(InstalledUnsignedRPM)[0].items) == 2 -+ -+ - def test_actor_execution_with_unknown_distro(current_actor_context): - config = mock_configs.CONFIG + + def test_actor_execution_with_unknown_distro(current_actor_context): +@@ -124,8 +155,8 @@ def test_actor_execution_with_unknown_distro(current_actor_context): + current_actor_context.feed(InstalledRPM(items=[])) + current_actor_context.run(config_model=config) + assert not current_actor_context.consume(DistributionSignedRPM) +- assert not current_actor_context.consume(InstalledRedHatSignedRPM) + assert not current_actor_context.consume(InstalledUnsignedRPM) ++ assert not current_actor_context.consume(ThirdPartyRPM) + + + def test_all_rpms_signed(current_actor_context): +@@ -144,9 +175,8 @@ def test_all_rpms_signed(current_actor_context): + current_actor_context.run(config_model=mock_configs.CONFIG_ALL_SIGNED) + assert current_actor_context.consume(DistributionSignedRPM) + assert len(current_actor_context.consume(DistributionSignedRPM)[0].items) == 4 +- assert current_actor_context.consume(InstalledRedHatSignedRPM) +- assert len(current_actor_context.consume(InstalledRedHatSignedRPM)[0].items) == 4 + assert not current_actor_context.consume(InstalledUnsignedRPM)[0].items ++ assert not current_actor_context.consume(ThirdPartyRPM)[0].items + + + def test_katello_pkg_goes_to_signed(current_actor_context): +@@ -164,9 +194,8 @@ def test_katello_pkg_goes_to_signed(current_actor_context): + current_actor_context.run(config_model=mock_configs.CONFIG_ALL_SIGNED) + assert current_actor_context.consume(DistributionSignedRPM) + assert len(current_actor_context.consume(DistributionSignedRPM)[0].items) == 1 +- assert current_actor_context.consume(InstalledRedHatSignedRPM) +- assert len(current_actor_context.consume(InstalledRedHatSignedRPM)[0].items) == 1 + assert not current_actor_context.consume(InstalledUnsignedRPM)[0].items ++ assert not current_actor_context.consume(ThirdPartyRPM)[0].items + + + def test_gpg_pubkey_pkg(current_actor_context): +@@ -181,10 +210,10 @@ def test_gpg_pubkey_pkg(current_actor_context): + current_actor_context.run(config_model=mock_configs.CONFIG) + assert current_actor_context.consume(DistributionSignedRPM) + assert len(current_actor_context.consume(DistributionSignedRPM)[0].items) == 2 +- assert current_actor_context.consume(InstalledRedHatSignedRPM) +- assert len(current_actor_context.consume(InstalledRedHatSignedRPM)[0].items) == 2 + assert current_actor_context.consume(InstalledUnsignedRPM) + assert not current_actor_context.consume(InstalledUnsignedRPM)[0].items ++ assert current_actor_context.consume(ThirdPartyRPM) ++ assert not current_actor_context.consume(ThirdPartyRPM)[0].items + + + def test_create_lookup(): +@@ -238,7 +267,7 @@ def test_has_package(current_actor_context): + current_actor_context.run(config_model=mock_configs.CONFIG) + assert rpms.has_package(DistributionSignedRPM, 'sample01', context=current_actor_context) + assert not rpms.has_package(DistributionSignedRPM, 'nosuchpackage', context=current_actor_context) +- assert rpms.has_package(InstalledRedHatSignedRPM, 'sample01', context=current_actor_context) +- assert not rpms.has_package(InstalledRedHatSignedRPM, 'nosuchpackage', context=current_actor_context) + assert rpms.has_package(InstalledUnsignedRPM, 'sample02', context=current_actor_context) + assert not rpms.has_package(InstalledUnsignedRPM, 'nosuchpackage', context=current_actor_context) ++ assert rpms.has_package(ThirdPartyRPM, 'sample02', context=current_actor_context) ++ assert not rpms.has_package(ThirdPartyRPM, 'nosuchpackage', context=current_actor_context) diff --git a/repos/system_upgrade/common/actors/efibootorderfix/finalization/actor.py b/repos/system_upgrade/common/actors/efibootorderfix/finalization/actor.py index f42909f0..6383a56f 100644 --- a/repos/system_upgrade/common/actors/efibootorderfix/finalization/actor.py @@ -19856,6 +20691,155 @@ index e6741293..7a7e9ebf 100644 if rpm_tasks: + rpm_tasks.to_reinstall = sorted(pkgs_to_reinstall) api.produce(rpm_tasks) +diff --git a/repos/system_upgrade/common/actors/redhatsignedrpmcheck/actor.py b/repos/system_upgrade/common/actors/redhatsignedrpmcheck/actor.py +deleted file mode 100644 +index a3555e52..00000000 +--- a/repos/system_upgrade/common/actors/redhatsignedrpmcheck/actor.py ++++ /dev/null +@@ -1,22 +0,0 @@ +-from leapp.actors import Actor +-from leapp.libraries.actor.redhatsignedrpmcheck import check_unsigned_packages +-from leapp.models import InstalledUnsignedRPM +-from leapp.reporting import Report +-from leapp.tags import ChecksPhaseTag, IPUWorkflowTag +- +- +-class RedHatSignedRpmCheck(Actor): +- """ +- Check if there are packages not signed by Red Hat in use. If yes, warn user about it. +- +- If any any installed RPM package does not contain a valid signature from Red Hat, a message +- containing a warning is produced. +- """ +- +- name = 'red_hat_signed_rpm_check' +- consumes = (InstalledUnsignedRPM,) +- produces = (Report,) +- tags = (IPUWorkflowTag, ChecksPhaseTag) +- +- def process(self): +- check_unsigned_packages() +diff --git a/repos/system_upgrade/common/actors/redhatsignedrpmcheck/libraries/redhatsignedrpmcheck.py b/repos/system_upgrade/common/actors/redhatsignedrpmcheck/libraries/redhatsignedrpmcheck.py +deleted file mode 100644 +index 14ade534..00000000 +--- a/repos/system_upgrade/common/actors/redhatsignedrpmcheck/libraries/redhatsignedrpmcheck.py ++++ /dev/null +@@ -1,62 +0,0 @@ +-from leapp import reporting +-from leapp.libraries.stdlib import api +-from leapp.libraries.stdlib.config import is_verbose +-from leapp.models import InstalledUnsignedRPM +- +- +-def generate_report(packages): +- """ Generate a report if there are unsigned packages installed on the system """ +- if not packages: +- return +- unsigned_packages_new_line = '\n'.join(['- ' + p for p in packages]) +- title = 'Packages not signed by Red Hat found on the system' +- summary = ('The following packages have not been signed by Red Hat' +- ' and may be removed during the upgrade process in case Red Hat-signed' +- ' packages to be removed during the upgrade depend on them:\n{}' +- .format(unsigned_packages_new_line)) +- hint = ( +- 'The most simple solution that does not require additional knowledge' +- ' about the upgrade process' +- ' is the uninstallation of such packages before the upgrade and' +- ' installing these (or their newer versions compatible with the target' +- ' system) back after the upgrade. Also you can just try to upgrade the' +- ' system on a testing machine (or after the full system backup) to see' +- ' the result.\n' +- 'However, it is common use case to migrate or upgrade installed third' +- ' party packages together with the system during the in-place upgrade' +- ' process. To examine how to customize the process to deal with such' +- ' packages, follow the documentation in the attached link' +- ' for more details.' +- ) +- reporting.create_report([ +- reporting.Title(title), +- reporting.Summary(summary), +- reporting.Severity(reporting.Severity.HIGH), +- reporting.Groups([reporting.Groups.SANITY]), +- reporting.Remediation(hint=hint), +- reporting.ExternalLink( +- url='https://red.ht/customize-rhel-upgrade-actors', +- title='Handling the migration of your custom and third-party applications' +- ) +- ]) +- +- if is_verbose(): +- api.show_message(summary) +- +- +-def get_unsigned_packages(): +- """ Get list of unsigned packages installed in the system """ +- rpm_messages = api.consume(InstalledUnsignedRPM) +- data = next(rpm_messages, InstalledUnsignedRPM()) +- if list(rpm_messages): +- api.current_logger().warning('Unexpectedly received more than one InstalledUnsignedRPM message.') +- unsigned_packages = set() +- unsigned_packages.update([pkg.name for pkg in data.items]) +- unsigned_packages = list(unsigned_packages) +- unsigned_packages.sort() +- return unsigned_packages +- +- +-def check_unsigned_packages(): +- """ Check and generate reports if system contains unsigned installed packages""" +- generate_report(get_unsigned_packages()) +diff --git a/repos/system_upgrade/common/actors/redhatsignedrpmcheck/tests/test_redhatsignedrpmcheck.py b/repos/system_upgrade/common/actors/redhatsignedrpmcheck/tests/test_redhatsignedrpmcheck.py +deleted file mode 100644 +index 8ec4c16f..00000000 +--- a/repos/system_upgrade/common/actors/redhatsignedrpmcheck/tests/test_redhatsignedrpmcheck.py ++++ /dev/null +@@ -1,47 +0,0 @@ +-from leapp import reporting +-from leapp.libraries.actor import redhatsignedrpmcheck +-from leapp.libraries.common.testutils import create_report_mocked, produce_mocked +-from leapp.libraries.stdlib import api +-from leapp.models import InstalledUnsignedRPM, RPM +- +-RH_PACKAGER = 'Red Hat, Inc. ' +- +- +-def test_actor_execution_without_unsigned_data(monkeypatch): +- def consume_unsigned_message_mocked(*models): +- installed_rpm = [] +- yield InstalledUnsignedRPM(items=installed_rpm) +- monkeypatch.setattr(api, "consume", consume_unsigned_message_mocked) +- monkeypatch.setattr(api, "produce", produce_mocked()) +- monkeypatch.setattr(api, "show_message", lambda x: True) +- monkeypatch.setattr(reporting, "create_report", create_report_mocked()) +- +- packages = redhatsignedrpmcheck.get_unsigned_packages() +- assert not packages +- redhatsignedrpmcheck.generate_report(packages) +- assert reporting.create_report.called == 0 +- +- +-def test_actor_execution_with_unsigned_data(monkeypatch): +- def consume_unsigned_message_mocked(*models): +- installed_rpm = [ +- RPM(name='sample02', version='0.1', release='1.sm01', epoch='1', packager=RH_PACKAGER, arch='noarch', +- pgpsig='SOME_OTHER_SIG_X'), +- RPM(name='sample04', version='0.1', release='1.sm01', epoch='1', packager=RH_PACKAGER, arch='noarch', +- pgpsig='SOME_OTHER_SIG_X'), +- RPM(name='sample06', version='0.1', release='1.sm01', epoch='1', packager=RH_PACKAGER, arch='noarch', +- pgpsig='SOME_OTHER_SIG_X'), +- RPM(name='sample08', version='0.1', release='1.sm01', epoch='1', packager=RH_PACKAGER, arch='noarch', +- pgpsig='SOME_OTHER_SIG_X')] +- yield InstalledUnsignedRPM(items=installed_rpm) +- +- monkeypatch.setattr(api, "consume", consume_unsigned_message_mocked) +- monkeypatch.setattr(api, "produce", produce_mocked()) +- monkeypatch.setattr(api, "show_message", lambda x: True) +- monkeypatch.setattr(reporting, "create_report", create_report_mocked()) +- +- packages = redhatsignedrpmcheck.get_unsigned_packages() +- assert len(packages) == 4 +- redhatsignedrpmcheck.generate_report(packages) +- assert reporting.create_report.called == 1 +- assert 'Packages not signed by Red Hat found' in reporting.create_report.report_fields['title'] diff --git a/repos/system_upgrade/common/actors/reportsettargetrelease/libraries/reportsettargetrelease.py b/repos/system_upgrade/common/actors/reportsettargetrelease/libraries/reportsettargetrelease.py index 3dcf5d95..37f60179 100644 --- a/repos/system_upgrade/common/actors/reportsettargetrelease/libraries/reportsettargetrelease.py @@ -20156,6 +21140,319 @@ index 43ac1fc4..62aefaf4 100644 + to_reinstall=to_reinstall_filtered, to_keep=load_tasks_file(os.path.join(base_dir, 'to_keep'), logger), to_remove=load_tasks_file(os.path.join(base_dir, 'to_remove'), logger)) +diff --git a/repos/system_upgrade/common/actors/scan_default_initramfs/actor.py b/repos/system_upgrade/common/actors/scan_default_initramfs/actor.py +new file mode 100644 +index 00000000..f0713c48 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/scan_default_initramfs/actor.py +@@ -0,0 +1,20 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor import scan_default_initramfs as scan_default_initramfs_lib ++from leapp.models import DefaultInitramfsInfo, DefaultSourceBootEntry ++from leapp.tags import FactsPhaseTag, IPUWorkflowTag ++ ++ ++class ScanDefaultInitramfs(Actor): ++ """ ++ Scan details of the default boot entry's initramfs image. ++ ++ Information such as used dracut modules are collected. ++ """ ++ ++ name = 'scan_default_initramfs' ++ consumes = (DefaultSourceBootEntry,) ++ produces = (DefaultInitramfsInfo,) ++ tags = (IPUWorkflowTag, FactsPhaseTag) ++ ++ def process(self): ++ scan_default_initramfs_lib.scan_default_initramfs() +diff --git a/repos/system_upgrade/common/actors/scan_default_initramfs/libraries/scan_default_initramfs.py b/repos/system_upgrade/common/actors/scan_default_initramfs/libraries/scan_default_initramfs.py +new file mode 100644 +index 00000000..8c205bbe +--- /dev/null ++++ b/repos/system_upgrade/common/actors/scan_default_initramfs/libraries/scan_default_initramfs.py +@@ -0,0 +1,41 @@ ++from leapp.exceptions import StopActorExecutionError ++from leapp.libraries.stdlib import api, CalledProcessError, run ++from leapp.models import DefaultInitramfsInfo, DefaultSourceBootEntry ++ ++ ++def scan_default_initramfs(): ++ default_boot_entry = next(api.consume(DefaultSourceBootEntry), None) ++ if not default_boot_entry: ++ raise StopActorExecutionError('Actor did not receive default boot entry info.') ++ ++ target_initramfs_path = default_boot_entry.initramfs_path ++ try: ++ initramfs_info = run(['lsinitrd', '-m', target_initramfs_path], split=True)['stdout'] ++ ++ except CalledProcessError as err: ++ details = {'details': str(err)} ++ msg = 'Failed to list details (lsinitrd) of the default boot entry\'s initramfs.' ++ raise StopActorExecutionError(msg, details=details) ++ ++ dracut_modules_lines = iter(initramfs_info) ++ ++ for line in dracut_modules_lines: # Consume everything until `dracut-modules:` is seen ++ line = line.strip() ++ if line == 'dracut modules:': ++ break ++ ++ dracut_modules = [] ++ for module_line in dracut_modules_lines: ++ module_line = module_line.strip() ++ if module_line.startswith('========'): ++ break ++ ++ dracut_modules.append(module_line) ++ ++ api.current_logger().debug(('Default boot entry\'s initramfs ({}) has ' ++ 'the following dracut modules: {}').format(default_boot_entry.initramfs_path, ++ dracut_modules)) ++ ++ default_initramfs_info_msg = DefaultInitramfsInfo(path=default_boot_entry.initramfs_path, ++ used_dracut_modules=dracut_modules) ++ api.produce(default_initramfs_info_msg) +diff --git a/repos/system_upgrade/common/actors/scan_default_initramfs/tests/test_scan_default_initramfs.py b/repos/system_upgrade/common/actors/scan_default_initramfs/tests/test_scan_default_initramfs.py +new file mode 100644 +index 00000000..f4b4431c +--- /dev/null ++++ b/repos/system_upgrade/common/actors/scan_default_initramfs/tests/test_scan_default_initramfs.py +@@ -0,0 +1,82 @@ ++import pytest ++ ++from leapp.exceptions import StopActorExecutionError ++from leapp.libraries.actor import scan_default_initramfs ++from leapp.libraries.common import testutils ++from leapp.libraries.stdlib import CalledProcessError ++from leapp.models import DefaultInitramfsInfo, DefaultSourceBootEntry ++ ++ ++def test_scan_default_initramfs(monkeypatch): ++ lsinitrd_output = [ ++ 'Image: /boot/initramfs-5.14.0-570.12.1.el9_6.x86_64.img: 38M', ++ '========================================================================', ++ 'Early CPIO image', ++ '========================================================================', ++ 'drwxr-xr-x 3 root root 0 Mar 11 04:02 .', ++ '-rw-r--r-- 1 root root 2 Mar 11 04:02 early_cpio', ++ 'drwxr-xr-x 3 root root 0 Mar 11 04:02 kernel', ++ 'drwxr-xr-x 3 root root 0 Mar 11 04:02 kernel/x86', ++ 'drwxr-xr-x 2 root root 0 Mar 11 04:02 kernel/x86/microcode', ++ '-rw-r--r-- 1 root root 220160 Mar 11 04:02 kernel/x86/microcode/GenuineIntel.bin', ++ '========================================================================', ++ 'Version: dracut-057-87.git20250311.el9_6', ++ '', ++ 'dracut modules:', ++ 'bash', ++ 'systemd', ++ '========================================', ++ ] ++ ++ def run_mock(command, split=False): ++ if command == ['lsinitrd', '-m', '/boot/initramfs-upgrade.x86_64.img']: ++ return {'stdout': lsinitrd_output} ++ assert False, f'Unexpected command: {command}' ++ ++ default_source_entry_msg = DefaultSourceBootEntry( ++ kernel_path='/boot/vmlinuz-upgrade.x86_64', ++ initramfs_path='/boot/initramfs-upgrade.x86_64.img' ++ ) ++ ++ actor_mock = testutils.CurrentActorMocked(msgs=[default_source_entry_msg]) ++ produce_mock = testutils.produce_mocked() ++ ++ monkeypatch.setattr(scan_default_initramfs.api, 'current_actor', actor_mock) ++ monkeypatch.setattr(scan_default_initramfs.api, 'produce', produce_mock) ++ monkeypatch.setattr(scan_default_initramfs, 'run', run_mock) ++ ++ scan_default_initramfs.scan_default_initramfs() ++ ++ assert produce_mock.called ++ assert len(produce_mock.model_instances) == 1 ++ assert isinstance(produce_mock.model_instances[0], DefaultInitramfsInfo) ++ ++ initramfs_info = produce_mock.model_instances[0] ++ assert initramfs_info.used_dracut_modules == ['bash', 'systemd'] ++ ++ ++def test_no_default_boot_entry(monkeypatch): ++ monkeypatch.setattr(scan_default_initramfs.api, 'current_actor', testutils.CurrentActorMocked(msgs=[])) ++ ++ with pytest.raises(StopActorExecutionError): ++ scan_default_initramfs.scan_default_initramfs() ++ ++ ++def test_lsinitrd_error(monkeypatch): ++ default_boot_entry = DefaultSourceBootEntry( ++ kernel_path='/boot/vmlinuz-upgrade.x86_64', ++ initramfs_path='/boot/initramfs-upgrade.x86_64.img' ++ ) ++ ++ def run_mock(command, split=False): ++ if command == ['lsinitrd', '-m', '/boot/initramfs-upgrade.x86_64.img']: ++ raise CalledProcessError('Simulated lsinitrd call error (in tests)', command, 1) ++ assert False, f'Unexpected command: {command}' ++ ++ actor_mock = testutils.CurrentActorMocked(msgs=[default_boot_entry]) ++ monkeypatch.setattr(scan_default_initramfs.api, 'current_actor', actor_mock) ++ monkeypatch.setattr(scan_default_initramfs.api, 'produce', testutils.produce_mocked()) ++ monkeypatch.setattr(scan_default_initramfs, 'run', run_mock) ++ ++ with pytest.raises(StopActorExecutionError): ++ scan_default_initramfs.scan_default_initramfs() +diff --git a/repos/system_upgrade/common/actors/scan_source_boot_loader/actor.py b/repos/system_upgrade/common/actors/scan_source_boot_loader/actor.py +new file mode 100644 +index 00000000..d38c903b +--- /dev/null ++++ b/repos/system_upgrade/common/actors/scan_source_boot_loader/actor.py +@@ -0,0 +1,18 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor import scan_source_boot_entry as scan_source_boot_entry_lib ++from leapp.models import DefaultSourceBootEntry ++from leapp.tags import FactsPhaseTag, IPUWorkflowTag ++ ++ ++class ScanSourceBootEntry(Actor): ++ """ ++ Scan the default boot entry of the source system. ++ """ ++ ++ name = 'scan_source_boot_entry' ++ consumes = () ++ produces = (DefaultSourceBootEntry,) ++ tags = (IPUWorkflowTag, FactsPhaseTag) ++ ++ def process(self): ++ scan_source_boot_entry_lib.scan_default_source_boot_entry() +diff --git a/repos/system_upgrade/common/actors/scan_source_boot_loader/libraries/scan_source_boot_entry.py b/repos/system_upgrade/common/actors/scan_source_boot_loader/libraries/scan_source_boot_entry.py +new file mode 100644 +index 00000000..e25cb251 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/scan_source_boot_loader/libraries/scan_source_boot_entry.py +@@ -0,0 +1,55 @@ ++import os ++ ++from leapp.exceptions import StopActorExecutionError ++from leapp.libraries.stdlib import api, CalledProcessError, run ++from leapp.models import DefaultSourceBootEntry ++ ++ ++def extract_path_with_img_extension(initramfs_path): ++ try: ++ img_extension_start = initramfs_path.rindex('.img') # .index() returns the starting position ++ initramfs_path = initramfs_path[:img_extension_start + len('.img')] ++ ++ if not os.path.exists(initramfs_path): ++ msg = 'Failed to extract the path to the default\'s boot entry initramfs.' ++ details = {'details': f'The current initramfs path {initramfs_path}'} ++ raise StopActorExecutionError(msg, details=details) ++ ++ except ValueError: ++ details = {'details': f'The current initramfs path {initramfs_path}'} ++ # The system is using some non-traditional naming scheme, no point in trying to extract image path ++ # Better safe, than sorry, we stop the upgrade here rather than crashing because of a weird path ++ msg = ('The initrd path of the default kernel does not contain the `.img` extension, ' ++ 'thus the upgrade cannot safely continue.') ++ raise StopActorExecutionError(msg, details=details) ++ return initramfs_path ++ ++ ++def scan_default_source_boot_entry(): ++ try: ++ default_kernel = run(['grubby', '--default-kernel'])['stdout'].strip() ++ default_kernel_info_lines = run(['grubby', '--info', default_kernel], split=True)['stdout'] ++ ++ except CalledProcessError as err: ++ details = {'details': str(err)} ++ raise StopActorExecutionError('Failed to determine default boot entry.', details=details) ++ ++ # Note that there can be multiple entries listed sharing the same default kernel. ++ # The parsing is done in a way that it should not fail in such a case. For the current use ++ # it does not matter -- at the moment we care primarily about the initramfs path, and these ++ # entries should typically share the initramfs. ++ ++ default_kernel_info = {} ++ for line in default_kernel_info_lines: ++ key, value = line.split('=', 1) ++ default_kernel_info[key] = value.strip('"') ++ ++ initramfs_path = default_kernel_info['initrd'] ++ initramfs_path = extract_path_with_img_extension(initramfs_path) ++ ++ default_boot_entry_message = DefaultSourceBootEntry( ++ initramfs_path=initramfs_path, ++ kernel_path=default_kernel_info['kernel'], ++ ) ++ ++ api.produce(default_boot_entry_message) +diff --git a/repos/system_upgrade/common/actors/scan_source_boot_loader/tests/test_scan_source_boot_entry.py b/repos/system_upgrade/common/actors/scan_source_boot_loader/tests/test_scan_source_boot_entry.py +new file mode 100644 +index 00000000..b6b9e473 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/scan_source_boot_loader/tests/test_scan_source_boot_entry.py +@@ -0,0 +1,61 @@ ++import os ++ ++import pytest ++ ++from leapp.exceptions import StopActorExecutionError ++from leapp.libraries.actor import scan_source_boot_entry ++from leapp.libraries.common import testutils ++from leapp.libraries.stdlib import CalledProcessError ++from leapp.models import DefaultSourceBootEntry ++ ++ ++def test_scan_default_source_boot_entry(monkeypatch): ++ grubby_default_kernel_output = '/boot/vmlinuz-upgrade.x86_64\n' ++ grubby_default_kernel_info_lines = [ ++ 'index=3', ++ 'kernel="/boot/vmlinuz-upgrade.x86_64"', ++ 'args="ro console=tty0 console=ttyS0,115200 rd_NO_PLYMOUTH"', ++ 'root="/dev/mapper/rhel_ibm--p8--kvm--03--guest--02-root"', ++ 'initrd="/boot/initramfs-upgrade.x86_64.img $tuned_initrd"', ++ 'title="RHEL-Upgrade-Initramfs"', ++ 'id="f6f57ac447784f60ba924dfbd5776a1b-upgrade.x86_64"', ++ ] ++ ++ def run_mock(command, split=False): ++ if command == ['grubby', '--default-kernel']: ++ return {'stdout': grubby_default_kernel_output} ++ if command == ['grubby', '--info', grubby_default_kernel_output.strip()]: ++ return {'stdout': grubby_default_kernel_info_lines} ++ assert False, f'Unexpected command: {command}' ++ ++ def exists_mock(path): ++ if path == '/boot/initramfs-upgrade.x86_64.img': ++ return True ++ return os.path.exists(path) ++ ++ produce_mock = testutils.produce_mocked() ++ monkeypatch.setattr(scan_source_boot_entry, 'run', run_mock) ++ monkeypatch.setattr(scan_source_boot_entry.api, 'produce', produce_mock) ++ monkeypatch.setattr(scan_source_boot_entry.os.path, 'exists', exists_mock) ++ ++ scan_source_boot_entry.scan_default_source_boot_entry() ++ ++ assert produce_mock.called ++ assert len(produce_mock.model_instances) == 1 ++ assert isinstance(produce_mock.model_instances[0], DefaultSourceBootEntry) ++ ++ boot_entry_info = produce_mock.model_instances[0] ++ assert boot_entry_info.initramfs_path == '/boot/initramfs-upgrade.x86_64.img' ++ assert boot_entry_info.kernel_path == '/boot/vmlinuz-upgrade.x86_64' ++ ++ ++def test_error_during_grubby_call(monkeypatch): ++ def run_mock(command, split=False): ++ if command == ['grubby', '--default-kernel']: ++ raise CalledProcessError('Simulated grubby call error (in tests)', command, 1) ++ assert False, f'Unexpected command: {command}' ++ ++ monkeypatch.setattr(scan_source_boot_entry, 'run', run_mock) ++ ++ with pytest.raises(StopActorExecutionError): ++ scan_source_boot_entry.scan_default_source_boot_entry() diff --git a/repos/system_upgrade/common/actors/scanvendorrepofiles/actor.py b/repos/system_upgrade/common/actors/scanvendorrepofiles/actor.py new file mode 100644 index 00000000..a5e481cb @@ -21294,7 +22591,7 @@ index 00000000..d4165d58 +=jk2t +-----END PGP PUBLIC KEY BLOCK----- diff --git a/repos/system_upgrade/common/files/distro/centos/gpg-signatures.json b/repos/system_upgrade/common/files/distro/centos/gpg-signatures.json -index 547b13e7..73a9598f 100644 +index 547b13e7..6dfa5b0f 100644 --- a/repos/system_upgrade/common/files/distro/centos/gpg-signatures.json +++ b/repos/system_upgrade/common/files/distro/centos/gpg-signatures.json @@ -2,7 +2,24 @@ @@ -21321,7 +22618,7 @@ index 547b13e7..73a9598f 100644 + "gpg-pubkey-6275f250-5e26cb2e", + "gpg-pubkey-73e3b907-6581b071" + ], -+ "10": [] ++ "10": ["gpg-pubkey-8483c65d-5ccc5b19"] + } } diff --git a/repos/system_upgrade/common/files/distro/cloudlinux/gpg-signatures.json b/repos/system_upgrade/common/files/distro/cloudlinux/gpg-signatures.json @@ -21813,6 +23110,89 @@ index e7b074aa..79164cca 100644 if not context.is_isolated(): api.current_logger().error('Trying to set RHSM into the container mode' 'on host. Skipping the action.') +diff --git a/repos/system_upgrade/common/libraries/rhui.py b/repos/system_upgrade/common/libraries/rhui.py +index b3225d5f..5c293304 100644 +--- a/repos/system_upgrade/common/libraries/rhui.py ++++ b/repos/system_upgrade/common/libraries/rhui.py +@@ -252,6 +252,17 @@ RHUI_SETUPS = { + ('cdn.redhat.com-chain.crt', RHUI_PKI_DIR), + ('content-rhel9-sap-bundle-e4s.crt', RHUI_PKI_PRODUCT_DIR) + ], os_version='9', content_channel=ContentChannel.E4S), ++ mk_rhui_setup(clients={'rh-amazon-rhui-client-sap-bundle-e4s'}, leapp_pkg='leapp-rhui-aws-sap-e4s', ++ mandatory_files=[ ++ ('rhui-client-config-server-10-sap-bundle.crt', RHUI_PKI_PRODUCT_DIR), ++ ('rhui-client-config-server-10-sap-bundle.key', RHUI_PKI_DIR), ++ ('leapp-aws-sap-e4s.repo', YUM_REPOS_PATH) ++ ], ++ optional_files=[ ++ ('content-rhel10-sap-bundle-e4s.key', RHUI_PKI_DIR), ++ ('cdn.redhat.com-chain.crt', RHUI_PKI_DIR), ++ ('content-rhel10-sap-bundle-e4s.crt', RHUI_PKI_PRODUCT_DIR) ++ ], os_version='10', content_channel=ContentChannel.E4S), + ], + RHUIFamily(RHUIProvider.AZURE, client_files_folder='azure'): [ + mk_rhui_setup(clients={'rhui-azure-rhel7'}, os_version='7', +@@ -312,6 +323,17 @@ RHUI_SETUPS = { + ], + extra_info={'agent_pkg': 'WALinuxAgent'}, + os_version='9', content_channel=ContentChannel.EUS), ++ mk_rhui_setup(clients={'rhui-azure-rhel10-sapapps'}, leapp_pkg='leapp-rhui-azure-sap', ++ mandatory_files=[ ++ ('leapp-azure-sap-apps.repo', YUM_REPOS_PATH), ++ ('RPM-GPG-KEY-microsoft-azure-release-new', '/etc/pki/rpm-gpg/') ++ ], ++ optional_files=[ ++ ('key-sapapps.pem', RHUI_PKI_DIR), ++ ('content-sapapps.crt', RHUI_PKI_PRODUCT_DIR) ++ ], ++ extra_info={'agent_pkg': 'WALinuxAgent'}, ++ os_version='10', content_channel=ContentChannel.EUS), + ], + RHUIFamily(RHUIProvider.AZURE, variant=RHUIVariant.SAP_HA, client_files_folder='azure-sap-ha'): [ + mk_rhui_setup(clients={'rhui-azure-rhel7-base-sap-ha'}, os_version='7', content_channel=ContentChannel.E4S), +@@ -339,6 +361,17 @@ RHUI_SETUPS = { + ], + extra_info={'agent_pkg': 'WALinuxAgent'}, + os_version='9', content_channel=ContentChannel.E4S), ++ mk_rhui_setup(clients={'rhui-azure-rhel10-sap-ha'}, leapp_pkg='leapp-rhui-azure-sap', ++ mandatory_files=[ ++ ('leapp-azure-sap-ha.repo', YUM_REPOS_PATH), ++ ('RPM-GPG-KEY-microsoft-azure-release-new', '/etc/pki/rpm-gpg/') ++ ], ++ optional_files=[ ++ ('key-sap-ha.pem', RHUI_PKI_DIR), ++ ('content-sap-ha.crt', RHUI_PKI_PRODUCT_DIR) ++ ], ++ extra_info={'agent_pkg': 'WALinuxAgent'}, ++ os_version='10', content_channel=ContentChannel.E4S), + ], + RHUIFamily(RHUIProvider.GOOGLE, client_files_folder='google'): [ + mk_rhui_setup(clients={'google-rhui-client-rhel7'}, os_version='7'), +diff --git a/repos/system_upgrade/common/libraries/rpms.py b/repos/system_upgrade/common/libraries/rpms.py +index dde4d2b6..bd3a2961 100644 +--- a/repos/system_upgrade/common/libraries/rpms.py ++++ b/repos/system_upgrade/common/libraries/rpms.py +@@ -28,9 +28,9 @@ _LEAPP_PACKAGES_MAP = { + }, + LeappComponents.REPOSITORY: {'7': {'pkgs': ['leapp-upgrade-el7toel8'], + 'deps': ['leapp-upgrade-el7toel8-deps']}, +- '8': {'pkgs': ['leapp-upgrade-el8toel9'], ++ '8': {'pkgs': ['leapp-upgrade-el8toel9', 'leapp-upgrade-el8toel9-fapolicyd'], + 'deps': ['leapp-upgrade-el8toel9-deps']}, +- '9': {'pkgs': ['leapp-upgrade-el9toel10'], ++ '9': {'pkgs': ['leapp-upgrade-el9toel10', 'leapp-upgrade-el9toel10-fapolicyd'], + 'deps': ['leapp-upgrade-el9toel10-deps']} + }, + LeappComponents.COCKPIT: {'7': {'pkgs': ['cockpit-leapp']}, +@@ -87,7 +87,7 @@ def create_lookup(model, field, keys, context=stdlib.api): + + def has_package(model, package_name, arch=None, version=None, release=None, context=stdlib.api): + """ +- Expects a model DistributionSignedRPM or InstalledUnsignedRPM. ++ Expects a DistributionSignedRPM or ThirdPartyRPM model. + Can be useful in cases like a quick item presence check, ex. check in actor that + a certain package is installed. + diff --git a/repos/system_upgrade/common/libraries/tests/test_gpg.py b/repos/system_upgrade/common/libraries/tests/test_gpg.py index 82b51abb..47617ad8 100644 --- a/repos/system_upgrade/common/libraries/tests/test_gpg.py @@ -21935,6 +23315,23 @@ index 190fd0de..b643cd0d 100644 + + assert context_mocked.remove_called == [] + assert context_mocked.copy_to_called == [] +diff --git a/repos/system_upgrade/common/libraries/tests/test_rpms.py b/repos/system_upgrade/common/libraries/tests/test_rpms.py +index a527407d..13f87651 100644 +--- a/repos/system_upgrade/common/libraries/tests/test_rpms.py ++++ b/repos/system_upgrade/common/libraries/tests/test_rpms.py +@@ -37,10 +37,10 @@ def test_parse_config_modification(): + + + @pytest.mark.parametrize('major_version,component,result', [ +- (None, None, ['leapp', 'python3-leapp', 'leapp-upgrade-el8toel9', 'snactor']), ++ (None, None, ['leapp', 'python3-leapp', 'leapp-upgrade-el8toel9', 'leapp-upgrade-el8toel9-fapolicyd', 'snactor']), + ('7', None, ['leapp', 'python2-leapp', 'leapp-upgrade-el7toel8', 'snactor']), + (['7', '8'], None, ['leapp', 'python2-leapp', 'leapp-upgrade-el7toel8', +- 'python3-leapp', 'leapp-upgrade-el8toel9', 'snactor']), ++ 'python3-leapp', 'leapp-upgrade-el8toel9', 'leapp-upgrade-el8toel9-fapolicyd', 'snactor']), + ('8', 'framework', ['leapp', 'python3-leapp']), + ]) + def test_get_leapp_packages(major_version, component, result, monkeypatch): diff --git a/repos/system_upgrade/common/models/activevendorlist.py b/repos/system_upgrade/common/models/activevendorlist.py new file mode 100644 index 00000000..de4056fb @@ -21948,6 +23345,63 @@ index 00000000..de4056fb +class ActiveVendorList(Model): + topic = VendorTopic + data = fields.List(fields.String()) +diff --git a/repos/system_upgrade/common/models/initramfs.py b/repos/system_upgrade/common/models/initramfs.py +index 03b71125..b65cca33 100644 +--- a/repos/system_upgrade/common/models/initramfs.py ++++ b/repos/system_upgrade/common/models/initramfs.py +@@ -3,6 +3,15 @@ from leapp.topics import BootPrepTopic, SystemInfoTopic + from leapp.utils.deprecation import deprecated + + ++# Note that this is the only model about the source (rather than upgrade) initramfs ++class DefaultInitramfsInfo(Model): ++ """ Information about the initramfs image that corresponds to the default source boot entry """ ++ topic = SystemInfoTopic ++ ++ path = fields.String() ++ used_dracut_modules = fields.List(fields.String(), default=[]) ++ ++ + class DracutModule(Model): + """ + Specify a dracut module that should be included into the initramfs +diff --git a/repos/system_upgrade/common/models/installedrpm.py b/repos/system_upgrade/common/models/installedrpm.py +index cc9fd508..5f18951a 100644 +--- a/repos/system_upgrade/common/models/installedrpm.py ++++ b/repos/system_upgrade/common/models/installedrpm.py +@@ -23,13 +23,30 @@ class InstalledRPM(Model): + + + class DistributionSignedRPM(InstalledRPM): ++ """ ++ Installed packages signed by the vendor of the distribution. ++ """ + pass + + +-@deprecated(since='2024-01-31', message='Replaced by DistributionSignedRPM') +-class InstalledRedHatSignedRPM(InstalledRPM): ++class ThirdPartyRPM(InstalledRPM): ++ """ ++ Installed packages not signed by the vendor of the distribution. ++ ++ This includes: ++ - packages signed by other distribution vendors ++ - packages signed by other software vendors ++ - unsigned packages ++ From the POV of in-place upgrades such packages are considered third-party. ++ ++ This does not include: ++ - packages known not to be signed as they are created by a delivered ++ product (which is possibly part of the distribution). E.g. katello RPMS ++ created in a Satellite server. ++ """ + pass + + ++@deprecated(since='2025-07-09', message='Replaced by ThirdPartyRPM') + class InstalledUnsignedRPM(InstalledRPM): + pass diff --git a/repos/system_upgrade/common/models/repositoriesmap.py b/repos/system_upgrade/common/models/repositoriesmap.py index 842cd807..fc740606 100644 --- a/repos/system_upgrade/common/models/repositoriesmap.py @@ -21969,6 +23423,22 @@ index 7e2870d0..05d4e941 100644 modules_to_enable = fields.List(fields.Model(Module), default=[]) modules_to_reset = fields.List(fields.Model(Module), default=[]) +diff --git a/repos/system_upgrade/common/models/source_boot_entry.py b/repos/system_upgrade/common/models/source_boot_entry.py +new file mode 100644 +index 00000000..31a8584f +--- /dev/null ++++ b/repos/system_upgrade/common/models/source_boot_entry.py +@@ -0,0 +1,10 @@ ++from leapp.models import fields, Model ++from leapp.topics import SystemInfoTopic ++ ++ ++class DefaultSourceBootEntry(Model): ++ """ Default boot entry of the source system. """ ++ topic = SystemInfoTopic ++ ++ initramfs_path = fields.String() ++ kernel_path = fields.String() diff --git a/repos/system_upgrade/common/models/targetrepositories.py b/repos/system_upgrade/common/models/targetrepositories.py index 02c6c5e5..f9fd4238 100644 --- a/repos/system_upgrade/common/models/targetrepositories.py @@ -22096,3 +23566,1019 @@ index daa7b2ca..dd604d8b 100644 def get_workaround_efi_info(): +diff --git a/repos/system_upgrade/el9toel10/actors/check_default_initramfs/actor.py b/repos/system_upgrade/el9toel10/actors/check_default_initramfs/actor.py +new file mode 100644 +index 00000000..c834c495 +--- /dev/null ++++ b/repos/system_upgrade/el9toel10/actors/check_default_initramfs/actor.py +@@ -0,0 +1,22 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor import check_default_initramfs as check_default_initramfs_lib ++from leapp.models import DefaultInitramfsInfo ++from leapp.reporting import Report ++from leapp.tags import ChecksPhaseTag, IPUWorkflowTag ++ ++ ++class CheckDefaultInitramfs(Actor): ++ """ ++ Checks whether the default initramfs uses problematic dracut modules. ++ ++ Checks whether dracut modules that are missing on the target system are used. ++ If yes, the upgrade is inhibited. ++ """ ++ ++ name = 'check_default_initramfs' ++ consumes = (DefaultInitramfsInfo,) ++ produces = (Report,) ++ tags = (IPUWorkflowTag, ChecksPhaseTag) ++ ++ def process(self): ++ check_default_initramfs_lib.check_default_initramfs() +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 +new file mode 100644 +index 00000000..098b5fde +--- /dev/null ++++ b/repos/system_upgrade/el9toel10/actors/check_default_initramfs/libraries/check_default_initramfs.py +@@ -0,0 +1,47 @@ ++import os ++ ++from leapp import reporting ++from leapp.exceptions import StopActorExecutionError ++from leapp.libraries.stdlib import api ++from leapp.models import DefaultInitramfsInfo ++ ++ ++def check_default_initramfs(): ++ default_initramfs_info = next(api.consume(DefaultInitramfsInfo), None) ++ if not default_initramfs_info: ++ msg = 'Actor did not receive information about default boot entry\'s initramfs.' ++ raise StopActorExecutionError(msg) ++ ++ if 'network-legacy' in default_initramfs_info.used_dracut_modules: ++ summary = ( ++ f'Initramfs ({default_initramfs_info.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.' ++ ) ++ 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.' ++ ) ++ report_fields = [ ++ reporting.Title('Use of dracut modules that are missing on the target system detected'), ++ reporting.Summary(summary), ++ reporting.Severity(reporting.Severity.HIGH), ++ reporting.Groups([reporting.Groups.BOOT]), ++ reporting.Remediation(hint=remediation_hint), ++ reporting.Groups([reporting.Groups.INHIBITOR]), ++ reporting.ExternalLink( ++ url='https://access.redhat.com/solutions/7127576', ++ title='leapp upgrade fails to boot after upgrading to RHEL 10.0' ++ ) ++ ] ++ ++ usual_definition_file = '/etc/dracut.conf.d/50-network-legacy.conf' ++ if os.path.exists(usual_definition_file): ++ report_fields.append(reporting.RelatedResource('file', usual_definition_file)) ++ ++ reporting.create_report(report_fields) +diff --git a/repos/system_upgrade/el9toel10/actors/check_default_initramfs/tests/test_check_default_initramfs.py b/repos/system_upgrade/el9toel10/actors/check_default_initramfs/tests/test_check_default_initramfs.py +new file mode 100644 +index 00000000..d28ae308 +--- /dev/null ++++ b/repos/system_upgrade/el9toel10/actors/check_default_initramfs/tests/test_check_default_initramfs.py +@@ -0,0 +1,105 @@ ++import os ++ ++import pytest ++ ++from leapp.exceptions import StopActorExecutionError ++from leapp.libraries.actor import check_default_initramfs ++from leapp.libraries.common import testutils ++from leapp.models import DefaultInitramfsInfo ++from leapp.utils.report import is_inhibitor ++ ++ ++def test_no_default_initramfs_info(monkeypatch): ++ """Test that StopActorExecutionError is raised when no DefaultInitramfsInfo is provided.""" ++ # Mock api.consume to return empty iterator ++ actor_mock = testutils.CurrentActorMocked(msgs=[]) ++ monkeypatch.setattr(check_default_initramfs.api, 'current_actor', actor_mock) ++ ++ with pytest.raises(StopActorExecutionError): ++ check_default_initramfs.check_default_initramfs() ++ ++ ++def test_initramfs_without_network_legacy(monkeypatch): ++ """Test that no report is created when network-legacy module is not present.""" ++ # Create a DefaultInitramfsInfo without the problematic module ++ initramfs_info = DefaultInitramfsInfo( ++ path='/boot/initramfs-upgrade.x86_64.img', ++ used_dracut_modules=['bash', 'systemd', 'kernel-modules', 'resume'] ++ ) ++ ++ actor_mock = testutils.CurrentActorMocked(msgs=[initramfs_info]) ++ create_report_mock = testutils.create_report_mocked() ++ ++ monkeypatch.setattr(check_default_initramfs.api, 'current_actor', actor_mock) ++ monkeypatch.setattr(check_default_initramfs.reporting, 'create_report', create_report_mock) ++ ++ check_default_initramfs.check_default_initramfs() ++ ++ # No report should be created ++ assert not create_report_mock.called ++ ++ ++def test_initramfs_with_network_legacy_without_config_file(monkeypatch): ++ """ ++ Test that a report is created when network-legacy module is present but config file doesn't exist. ++ ++ The typical location of the config file (/etc/dracut.conf.d/50-network-legacy.conf) that ++ adds the 'network-legacy' module is not present. ++ """ ++ # Create a DefaultInitramfsInfo with the problematic module ++ initramfs_info = DefaultInitramfsInfo( ++ path='/boot/initramfs-upgrade.x86_64.img', ++ used_dracut_modules=['bash', 'systemd', 'network-legacy', 'kernel-modules'] ++ ) ++ ++ actor_mock = testutils.CurrentActorMocked(msgs=[initramfs_info]) ++ create_report_mock = testutils.create_report_mocked() ++ ++ monkeypatch.setattr(check_default_initramfs.api, 'current_actor', actor_mock) ++ monkeypatch.setattr(check_default_initramfs.reporting, 'create_report', create_report_mock) ++ # Mock os.path.exists to return False for the config file ++ monkeypatch.setattr(check_default_initramfs.os.path, 'exists', lambda path: False) ++ ++ check_default_initramfs.check_default_initramfs() ++ ++ assert create_report_mock.called ++ assert len(create_report_mock.reports) == 1 ++ ++ report = create_report_mock.reports[0] ++ ++ assert is_inhibitor(report) ++ ++ report_resources = report['detail'].get('related_resources', []) ++ assert not report_resources ++ ++ ++def test_initramfs_with_network_legacy_with_config_file(monkeypatch): ++ """Test that a report with related resource is created when network-legacy module and config file are present.""" ++ initramfs_info = DefaultInitramfsInfo( ++ path='/boot/initramfs-upgrade.x86_64.img', ++ used_dracut_modules=['bash', 'systemd', 'network-legacy', 'kernel-modules'] ++ ) ++ ++ actor_mock = testutils.CurrentActorMocked(msgs=[initramfs_info]) ++ create_report_mock = testutils.create_report_mocked() ++ ++ monkeypatch.setattr(check_default_initramfs.api, 'current_actor', actor_mock) ++ monkeypatch.setattr(check_default_initramfs.reporting, 'create_report', create_report_mock) ++ ++ def mock_exists(path): ++ if path == '/etc/dracut.conf.d/50-network-legacy.conf': ++ return True ++ return os.path.exists(path) # Fall back to original implementation since it is used in pytest internally ++ ++ monkeypatch.setattr(check_default_initramfs.os.path, 'exists', mock_exists) ++ ++ check_default_initramfs.check_default_initramfs() ++ ++ assert create_report_mock.called ++ assert len(create_report_mock.reports) == 1 ++ ++ report = create_report_mock.reports[0] ++ assert is_inhibitor(report) ++ ++ report_resources = report['detail'].get('related_resources', []) ++ assert report_resources +diff --git a/repos/system_upgrade/el9toel10/actors/sssd/sssdchecks/actor.py b/repos/system_upgrade/el9toel10/actors/sssd/sssdchecks/actor.py +new file mode 100644 +index 00000000..d8cdea8d +--- /dev/null ++++ b/repos/system_upgrade/el9toel10/actors/sssd/sssdchecks/actor.py +@@ -0,0 +1,20 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor import sssdchecks ++from leapp.models import KnownHostsProxyConfig ++from leapp.reporting import Report ++from leapp.tags import ChecksPhaseTag, IPUWorkflowTag ++ ++ ++class SSSDCheck(Actor): ++ """ ++ Check SSSD configuration for changes in RHEL10 and report them in model. ++ """ ++ ++ name = 'sssd_check' ++ consumes = (KnownHostsProxyConfig,) ++ produces = (Report,) ++ tags = (IPUWorkflowTag, ChecksPhaseTag) ++ ++ def process(self): ++ for cfg in self.consume(KnownHostsProxyConfig): ++ sssdchecks.check_config(cfg) +diff --git a/repos/system_upgrade/el9toel10/actors/sssd/sssdchecks/libraries/sssdchecks.py b/repos/system_upgrade/el9toel10/actors/sssd/sssdchecks/libraries/sssdchecks.py +new file mode 100644 +index 00000000..0a86fa7b +--- /dev/null ++++ b/repos/system_upgrade/el9toel10/actors/sssd/sssdchecks/libraries/sssdchecks.py +@@ -0,0 +1,33 @@ ++from leapp import reporting ++ ++FMT_LIST_SEPARATOR = '\n - ' ++ ++ ++def check_config(model): ++ if not model: ++ return ++ ++ # If sss_ssh_knownhostsproxy was not configured, there is nothing to do ++ if not model.ssh_config_files: ++ return ++ ++ summary = ( ++ 'SSSD\'s sss_ssh_knownhostsproxy tool is replaced by the more ' ++ 'reliable sss_ssh_knownhosts tool. SSH\'s configuration will be updated ' ++ 'to reflect this by updating every mention of sss_ssh_knownhostsproxy by ' ++ 'the corresponding mention of sss_ssh_knownhosts, even those commented out.\n' ++ 'SSSD\'s ssh service will be enabled if not already done.\n' ++ 'The following files will be updated:{}{}'.format( ++ FMT_LIST_SEPARATOR, ++ FMT_LIST_SEPARATOR.join(model.sssd_config_files + model.ssh_config_files) ++ ) ++ ) ++ ++ report = [ ++ reporting.Title('The sss_ssh_knownhostsproxy will be replaced by sss_ssh_knownhosts'), ++ reporting.Summary(summary), ++ reporting.Groups([reporting.Groups.AUTHENTICATION, reporting.Groups.SECURITY]), ++ reporting.Severity(reporting.Severity.INFO), ++ ] ++ ++ reporting.create_report(report) +diff --git a/repos/system_upgrade/el9toel10/actors/sssd/sssdchecks/tests/component_test_sssdchecks.py b/repos/system_upgrade/el9toel10/actors/sssd/sssdchecks/tests/component_test_sssdchecks.py +new file mode 100644 +index 00000000..08aa309d +--- /dev/null ++++ b/repos/system_upgrade/el9toel10/actors/sssd/sssdchecks/tests/component_test_sssdchecks.py +@@ -0,0 +1,29 @@ ++from leapp.models import KnownHostsProxyConfig, Report ++ ++ ++def test_sssdchecks__no_file(current_actor_context): ++ config = KnownHostsProxyConfig() ++ current_actor_context.feed(config) ++ current_actor_context.run() ++ reports = current_actor_context.consume(Report) ++ assert not reports ++ ++ ++def test_sssdchecks__files(current_actor_context): ++ sssd_files = ['/tmp/file1', '/tmp/file2'] ++ ssh_files = ['/tmp/file3', '/tmp/file4'] ++ all_files = sssd_files + ssh_files ++ ++ config = KnownHostsProxyConfig(sssd_config_files=sssd_files, ssh_config_files=ssh_files) ++ current_actor_context.feed(config) ++ current_actor_context.run() ++ reports = current_actor_context.consume(Report) ++ ++ assert len(reports) == 1 ++ ++ report = reports[0].report ++ assert report['title'] == 'The sss_ssh_knownhostsproxy will be replaced by sss_ssh_knownhosts' ++ assert 'sss_ssh_knownhosts tool.' in report['summary'] ++ ++ FMT_LIST_SEPARATOR = '\n - ' ++ assert "{}{}".format(FMT_LIST_SEPARATOR, FMT_LIST_SEPARATOR.join(all_files)) in report['summary'] +diff --git a/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/actor.py b/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/actor.py +new file mode 100644 +index 00000000..47fee67a +--- /dev/null ++++ b/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/actor.py +@@ -0,0 +1,22 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor import sssdfacts ++from leapp.models import KnownHostsProxyConfig ++from leapp.tags import FactsPhaseTag, IPUWorkflowTag ++ ++ ++class SSSDFacts(Actor): ++ """ ++ Check SSSD configuration for changes in RHEL10 and report them in model. ++ ++ We want to know if the 'ssh' service is enabled and whether ssh was ++ configured to use the sss_ssh_knownhosts tool. ++ """ ++ ++ name = 'sssd_facts' ++ consumes = () ++ produces = (KnownHostsProxyConfig,) ++ tags = (IPUWorkflowTag, FactsPhaseTag) ++ ++ def process(self): ++ self.produce(sssdfacts.get_facts(['/etc/sssd/sssd.conf', '/etc/sssd/conf.d/'], ++ ['/etc/ssh/ssh_config', '/etc/ssh/ssh_config.d/'])) +diff --git a/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/libraries/sssdfacts.py b/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/libraries/sssdfacts.py +new file mode 100644 +index 00000000..0ae9d93f +--- /dev/null ++++ b/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/libraries/sssdfacts.py +@@ -0,0 +1,53 @@ ++import os ++import re ++ ++from leapp.exceptions import StopActorExecutionError ++from leapp.libraries.stdlib import api ++from leapp.models import KnownHostsProxyConfig ++ ++ ++def _does_file_contain_expression(file_path, expression): ++ try: ++ with open(file_path) as in_file: ++ for line in in_file: ++ if re.search(expression, line) is not None: ++ return True ++ return False ++ except FileNotFoundError: ++ api.current_logger().warning( ++ 'Found a file during a recursive walk, but we failed to open it for reading: {}'.format(file_path) ++ ) ++ return False ++ except OSError as e: ++ raise StopActorExecutionError('Could not open file ' + file_path, details={'details': str(e)}) ++ ++ ++def _look_for_files(expression: str, path_list: list[str]) -> list[str]: ++ files_containing_expression = [] ++ for path in path_list: ++ if os.path.isdir(path): ++ for root, dummy_dirs, files in os.walk(path): ++ for file in files: ++ full_path = os.path.join(root, file) ++ if _does_file_contain_expression(full_path, expression): ++ files_containing_expression.append(full_path) ++ else: ++ if _does_file_contain_expression(path, expression): ++ files_containing_expression.append(path) ++ ++ return files_containing_expression ++ ++ ++def get_facts(sssd_config: list[str], ssh_config: list[str]) -> KnownHostsProxyConfig: ++ """ ++ Check SSSD and SSH configuration related to the sss_ssh_knownhostsproxy tool. ++ ++ Checks: ++ - Which files in the SSSD configuration include the `service` keyword, ++ - Which files in the SSH configuration mention the tool. ++ """ ++ ++ sssd_files = _look_for_files(r'^\s*services\s*=', sssd_config) ++ ssh_files = _look_for_files(r'^\s*#?\s*ProxyCommand\s+(/usr/bin/)?sss_ssh_knownhostsproxy\s', ssh_config) ++ ++ return KnownHostsProxyConfig(sssd_config_files=sssd_files, ssh_config_files=ssh_files) +diff --git a/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/tests/files/ssh_proxy_disabled.conf b/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/tests/files/ssh_proxy_disabled.conf +new file mode 100644 +index 00000000..13f757c3 +--- /dev/null ++++ b/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/tests/files/ssh_proxy_disabled.conf +@@ -0,0 +1,15 @@ ++Host * ++ ForwardAgent no ++ ForwardX11 no ++ PasswordAuthentication yes ++ HostbasedAuthentication no ++ GSSAPIAuthentication no ++ GSSAPIDelegateCredentials no ++ GSSAPIKeyExchange no ++ GSSAPITrustDNS no ++ BatchMode no ++# ProxyCommand /usr/bin/sss_ssh_knownhostsproxy -p %p %h ++ CheckHostIP no ++ AddressFamily any ++ ConnectTimeout 0 ++ StrictHostKeyChecking ask +diff --git a/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/tests/files/ssh_proxy_enabled.conf b/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/tests/files/ssh_proxy_enabled.conf +new file mode 100644 +index 00000000..58cc55e5 +--- /dev/null ++++ b/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/tests/files/ssh_proxy_enabled.conf +@@ -0,0 +1,15 @@ ++Host * ++ ForwardAgent no ++ ForwardX11 no ++ PasswordAuthentication yes ++ HostbasedAuthentication no ++ GSSAPIAuthentication no ++ GSSAPIDelegateCredentials no ++ GSSAPIKeyExchange no ++ GSSAPITrustDNS no ++ BatchMode no ++ ProxyCommand /usr/bin/sss_ssh_knownhostsproxy -p %p %h ++ CheckHostIP no ++ AddressFamily any ++ ConnectTimeout 0 ++ StrictHostKeyChecking ask +diff --git a/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/tests/files/ssh_proxy_not_present.conf b/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/tests/files/ssh_proxy_not_present.conf +new file mode 100644 +index 00000000..dd97e397 +--- /dev/null ++++ b/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/tests/files/ssh_proxy_not_present.conf +@@ -0,0 +1,14 @@ ++Host * ++ ForwardAgent no ++ ForwardX11 no ++ PasswordAuthentication yes ++ HostbasedAuthentication no ++ GSSAPIAuthentication no ++ GSSAPIDelegateCredentials no ++ GSSAPIKeyExchange no ++ GSSAPITrustDNS no ++ BatchMode no ++ CheckHostIP no ++ AddressFamily any ++ ConnectTimeout 0 ++ StrictHostKeyChecking ask +diff --git a/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/tests/files/sssd_service_disabled.conf b/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/tests/files/sssd_service_disabled.conf +new file mode 100644 +index 00000000..ad33d8ab +--- /dev/null ++++ b/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/tests/files/sssd_service_disabled.conf +@@ -0,0 +1,17 @@ ++[sssd] ++domains = COMPANY ++services = pam,nss ++ ++[domain/COMPANY] ++id_provider = ldap ++ldap_uri = ldap://ldap.company.com ++ldap_search_base = dc=company,dc=com ++ldap_tls_cacert = /etc/openldap/cacerts/cacert.pem ++ldap_tls_reqcert = never ++auth_provider = krb5 ++ldap_krb5_keytab = /etc/sssd/company.keytab ++krb5_server = hostname ++krb5_kpasswd = hostname ++krb5_realm = IPA.COMPANY.COM ++ldap_sudo_random_offset = 0 ++enumerate = false +diff --git a/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/tests/files/sssd_service_enabled.conf b/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/tests/files/sssd_service_enabled.conf +new file mode 100644 +index 00000000..63a661a6 +--- /dev/null ++++ b/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/tests/files/sssd_service_enabled.conf +@@ -0,0 +1,17 @@ ++[sssd] ++domains = COMPANY ++services = pam,nss,ssh ++ ++[domain/COMPANY] ++id_provider = ldap ++ldap_uri = ldap://ldap.company.com ++ldap_search_base = dc=company,dc=com ++ldap_tls_cacert = /etc/openldap/cacerts/cacert.pem ++ldap_tls_reqcert = never ++auth_provider = krb5 ++ldap_krb5_keytab = /etc/sssd/company.keytab ++krb5_server = hostname ++krb5_kpasswd = hostname ++krb5_realm = IPA.COMPANY.COM ++ldap_sudo_random_offset = 0 ++enumerate = false +diff --git a/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/tests/files/sssd_service_not_present.conf b/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/tests/files/sssd_service_not_present.conf +new file mode 100644 +index 00000000..4d014a75 +--- /dev/null ++++ b/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/tests/files/sssd_service_not_present.conf +@@ -0,0 +1,16 @@ ++[sssd] ++domains = COMPANY ++ ++[domain/COMPANY] ++id_provider = ldap ++ldap_uri = ldap://ldap.company.com ++ldap_search_base = dc=company,dc=com ++ldap_tls_cacert = /etc/openldap/cacerts/cacert.pem ++ldap_tls_reqcert = never ++auth_provider = krb5 ++ldap_krb5_keytab = /etc/sssd/company.keytab ++krb5_server = hostname ++krb5_kpasswd = hostname ++krb5_realm = IPA.COMPANY.COM ++ldap_sudo_random_offset = 0 ++enumerate = false +diff --git a/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/tests/files/subdir/sub_ssh_proxy_enabled.conf b/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/tests/files/subdir/sub_ssh_proxy_enabled.conf +new file mode 100644 +index 00000000..6acd5a7e +--- /dev/null ++++ b/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/tests/files/subdir/sub_ssh_proxy_enabled.conf +@@ -0,0 +1,16 @@ ++Host * ++ ForwardAgent no ++ ForwardX11 no ++ PasswordAuthentication yes ++ HostbasedAuthentication no ++ GSSAPIAuthentication no ++ GSSAPIDelegateCredentials no ++ GSSAPIKeyExchange no ++ GSSAPITrustDNS no ++ BatchMode no ++ ProxyCommand /usr/bin/sss_ssh_knownhostsproxy -p %p %h ++ CheckHostIP no ++ AddressFamily any ++ ConnectTimeout 0 ++ StrictHostKeyChecking ask ++ +diff --git a/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/tests/unit_test_sssdfacts.py b/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/tests/unit_test_sssdfacts.py +new file mode 100644 +index 00000000..a121d772 +--- /dev/null ++++ b/repos/system_upgrade/el9toel10/actors/sssd/sssdfacts/tests/unit_test_sssdfacts.py +@@ -0,0 +1,60 @@ ++import os ++ ++import pytest ++ ++from leapp.libraries.actor import sssdfacts ++ ++CUR_DIR = os.path.dirname(os.path.abspath(__file__)) ++ ++ ++def test_sssdfacts__missing_config(): ++ facts = sssdfacts.get_facts(sssd_config=['/etc/missing-file'], ++ ssh_config=['/etc/missing-file']) ++ ++ assert not facts.sssd_config_files ++ assert not facts.ssh_config_files ++ ++ ++def test_sssdfacts__empty_config(): ++ facts = sssdfacts.get_facts(sssd_config=['/dev/null'], ++ ssh_config=['/dev/null']) ++ assert not facts.sssd_config_files ++ assert not facts.ssh_config_files ++ ++ ++@pytest.mark.parametrize('state', ['not_present', 'disabled', 'enabled']) ++def test_sssdfacts__sssd_service(state): ++ file = os.path.join(CUR_DIR, 'files', 'sssd_service_' + state + '.conf') ++ facts = sssdfacts.get_facts(sssd_config=[file], ++ ssh_config=['/dev/null']) ++ ++ if state == 'not_present': ++ assert not facts.sssd_config_files ++ else: ++ assert len(facts.sssd_config_files) == 1 ++ assert file in facts.sssd_config_files ++ ++ ++@pytest.mark.parametrize('state', ['not_present', 'disabled', 'enabled']) ++def test_sssdfacts__knownhostsproxy(state): ++ file = os.path.join(CUR_DIR, 'files', 'ssh_proxy_' + state + '.conf') ++ ++ facts = sssdfacts.get_facts(sssd_config=['/dev/null'], ++ ssh_config=[file]) ++ ++ if state == 'not_present': ++ assert not facts.ssh_config_files ++ else: ++ assert len(facts.ssh_config_files) == 1 ++ assert file in facts.ssh_config_files ++ ++ ++def test_sssdfacts__directory(): ++ dirpath = os.path.join(CUR_DIR, 'files') ++ facts = sssdfacts.get_facts(sssd_config=['/dev/null'], ++ ssh_config=[dirpath]) ++ ++ assert len(facts.ssh_config_files) == 3 ++ assert os.path.join(CUR_DIR, 'files', 'ssh_proxy_disabled.conf') in facts.ssh_config_files ++ assert os.path.join(CUR_DIR, 'files', 'ssh_proxy_enabled.conf') in facts.ssh_config_files ++ assert os.path.join(CUR_DIR, 'files', 'subdir', 'sub_ssh_proxy_enabled.conf') in facts.ssh_config_files +diff --git a/repos/system_upgrade/el9toel10/actors/sssd/sssdupdate/actor.py b/repos/system_upgrade/el9toel10/actors/sssd/sssdupdate/actor.py +new file mode 100644 +index 00000000..7a548f90 +--- /dev/null ++++ b/repos/system_upgrade/el9toel10/actors/sssd/sssdupdate/actor.py +@@ -0,0 +1,20 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor import sssdupdate ++from leapp.models import KnownHostsProxyConfig ++from leapp.tags import ApplicationsPhaseTag, IPUWorkflowTag ++ ++ ++class SSSDUpdate(Actor): ++ """ ++ Update SSSD's and SSH's configuration to use sss_ssh_knownhosts instead ++ of sss_ssh_knownhosts proxy. ++ """ ++ ++ name = 'sssd_update' ++ consumes = (KnownHostsProxyConfig,) ++ produces = () ++ tags = (IPUWorkflowTag, ApplicationsPhaseTag) ++ ++ def process(self): ++ for cfg in self.consume(KnownHostsProxyConfig): ++ sssdupdate.update_config(cfg) +diff --git a/repos/system_upgrade/el9toel10/actors/sssd/sssdupdate/libraries/sssdupdate.py b/repos/system_upgrade/el9toel10/actors/sssd/sssdupdate/libraries/sssdupdate.py +new file mode 100644 +index 00000000..6d745ead +--- /dev/null ++++ b/repos/system_upgrade/el9toel10/actors/sssd/sssdupdate/libraries/sssdupdate.py +@@ -0,0 +1,81 @@ ++import os ++import re ++ ++from leapp.exceptions import StopActorExecutionError ++ ++ ++def _process_knownhosts(line: str) -> str: ++ if re.search(r'^\s*#?\s*ProxyCommand\s+(/usr/bin/)?sss_ssh_knownhostsproxy', line) is not None: ++ # Update the line, leaving intact any # and any --domain/-d parameter ++ line = line.replace('ProxyCommand', 'KnownHostsCommand') ++ line = line.replace('sss_ssh_knownhostsproxy', 'sss_ssh_knownhosts') ++ line = line.replace('--port=', '') ++ line = line.replace('--port', '') ++ line = line.replace('-p=', '') ++ line = line.replace('-p', '') ++ line = line.replace('%p', '') ++ line = line.replace('%h', '%H') ++ ++ return line ++ ++ ++def _process_enable_svc(line: str) -> str: ++ if re.search(r'^\s*#?\s*services\s*=', line) is not None: ++ if re.search(r'=\s*(.+,)?\s*ssh\s*(,.+)?\s*$', line) is None: ++ line = line.rstrip() ++ line += (',' if line[-1] != '=' else '') + 'ssh\n' ++ ++ return line ++ ++ ++def _update_file(filename, process_function): ++ newname = filename + '.new' ++ oldname = filename + '.old' ++ try: ++ with open(filename, 'r') as input_file, open(newname, 'x') as output_file: ++ istat = os.fstat(input_file.fileno()) ++ os.fchmod(output_file.fileno(), istat.st_mode) ++ for line in input_file: ++ try: ++ output_file.write(process_function(line)) ++ except OSError as e: ++ raise StopActorExecutionError('Failed to write to {}'.format(newname), ++ details={'details': str(e)}) ++ ++ except FileExistsError as e: ++ raise StopActorExecutionError('Temporary file already exists: {}'.format(newname), ++ details={'details': str(e)}) ++ except OSError as e: ++ try: ++ os.unlink(newname) ++ except FileNotFoundError: ++ pass ++ raise StopActorExecutionError('Failed to access the required files', details={'details': str(e)}) ++ ++ # Let's make sure the old configuration is preserverd if something goes wrong ++ os.replace(filename, oldname) ++ os.replace(newname, filename) ++ os.unlink(oldname) ++ ++ ++def _update_ssh_config(filename: str): ++ _update_file(filename, _process_knownhosts) ++ ++ ++def _enable_svc(filename): ++ _update_file(filename, _process_enable_svc) ++ ++ ++def update_config(model): ++ if not model: ++ return ++ ++ # If sss_ssh_knownhostsproxy was not configured, there is nothing to do ++ if not model.ssh_config_files: ++ return ++ ++ for file in model.ssh_config_files: ++ _update_ssh_config(file) ++ ++ for file in model.sssd_config_files: ++ _enable_svc(file) +diff --git a/repos/system_upgrade/el9toel10/actors/sssd/sssdupdate/tests/unit_test_sssdupdate.py b/repos/system_upgrade/el9toel10/actors/sssd/sssdupdate/tests/unit_test_sssdupdate.py +new file mode 100644 +index 00000000..81414838 +--- /dev/null ++++ b/repos/system_upgrade/el9toel10/actors/sssd/sssdupdate/tests/unit_test_sssdupdate.py +@@ -0,0 +1,273 @@ ++import os ++import random ++ ++import pytest ++ ++from leapp.libraries.actor import sssdupdate ++from leapp.models import KnownHostsProxyConfig ++ ++ ++class MockedFile: ++ """ ++ Mocks a file to avoid writing to the filesystem. ++ ++ This is the minimum mocking required for this test. ++ Most of the file functions are not implemented. ++ Only those required by this test. ++ """ ++ def __init__(self, name: str, fs): ++ self.name = name ++ self.fs = fs ++ self.fd = -1 ++ self.mode = 0o644 ++ self.contents = '' ++ ++ def __enter__(self): ++ return self ++ ++ def __exit__(self, exc_type, exc_val, exc_tb): ++ self.close() ++ return False ++ ++ def open(self, fd: int, mode: str): ++ self.fd = fd ++ if mode in ['w', 'x']: ++ self.contents = '' ++ ++ def fileno(self): ++ return self.fd ++ ++ def write(self, text: str) -> int: ++ self.contents += text ++ return len(text) ++ ++ def __iter__(self): ++ return iter(self.contents.splitlines(keepends=True)) ++ ++ def close(self): ++ self.fs._close(self.fd) ++ self.fd = -1 ++ ++ ++class MockedStatResults: ++ def __init__(self, mode): ++ self.st_mode = mode ++ ++ ++class MockedFileSystem: ++ """ ++ Mocks a filesystem. ++ ++ Simulates the minimum services needed by the tests in this file. ++ """ ++ def __init__(self): ++ self._by_name = {} ++ self._by_fd = [] ++ ++ def _get_by_fd(self, fd: int) -> MockedFile: ++ try: ++ return self._by_fd[fd] ++ except IndexError: ++ raise OSError('Bad file descriptor') ++ ++ def _get_by_name(self, name: str) -> MockedFile: ++ try: ++ return self._by_name[name] ++ except KeyError: ++ raise FileNotFoundError(name) ++ ++ def fstat(self, fd: int) -> MockedStatResults: ++ return MockedStatResults(self._get_by_fd(fd).mode) ++ ++ def fchmod(self, fd: int, mode: int): ++ file = self._get_by_fd(fd) ++ file.mode = mode ++ ++ def replace(self, src: str, dest: str): ++ file = self._get_by_name(src) ++ self._by_name[dest] = file ++ ++ def unlink(self, name: str): ++ del self._by_name[name] ++ ++ def open(self, name: str, mode: str) -> MockedFile: ++ if name not in self._by_name: ++ file = MockedFile(name, self) ++ self._by_name[name] = file ++ else: ++ if mode == 'x': ++ raise FileExistsError('File exists: ' + name) ++ file = self._by_name[name] ++ ++ self._by_fd.append(file) ++ fd = len(self._by_fd) - 1 ++ file.open(fd, mode) ++ ++ return file ++ ++ def _close(self, fd: int): ++ # This function is called by MockedFile.close() ++ self._by_fd[fd] = None ++ ++ ++mocked_filesystem = MockedFileSystem() ++ ++ ++@pytest.fixture(autouse=True) ++def mock_filesystem(monkeypatch): ++ def mocked_open(name, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None): ++ return mocked_filesystem.open(name, mode) ++ ++ def mocked_fchmod(fd: int, mode: int): ++ mocked_filesystem.fchmod(fd, mode) ++ ++ def mocked_fstat(fd: int) -> MockedStatResults: ++ return mocked_filesystem.fstat(fd) ++ ++ def mocked_replace(src: str, dest: str): ++ mocked_filesystem.replace(src, dest) ++ ++ def mocked_unlink(name: str): ++ mocked_filesystem.unlink(name) ++ ++ monkeypatch.setattr("builtins.open", mocked_open) ++ monkeypatch.setattr(os, "fchmod", mocked_fchmod) ++ monkeypatch.setattr(os, "fstat", mocked_fstat) ++ monkeypatch.setattr(os, "replace", mocked_replace) ++ monkeypatch.setattr(os, "unlink", mocked_unlink) ++ ++ ++def make_mock_file(contents: str) -> str: ++ name = os.path.join('/tmp', f'file.{str(random.uniform(1, 999999))}') ++ with mocked_filesystem.open(name, 'w') as file: ++ file.write(contents) ++ ++ return name ++ ++ ++def make_files(contents: list[str]) -> list[str]: ++ files = [] ++ for conts in contents: ++ files.append(make_mock_file(conts)) ++ ++ return files ++ ++ ++def check_file(name: str, expected: str): ++ assert name in mocked_filesystem._by_name.keys() # False positive => pylint: disable=consider-iterating-dictionary ++ assert mocked_filesystem._by_name[name].contents == expected ++ ++ ++@pytest.mark.parametrize('sssd', [True, False]) ++def test_sssdupdate__no_change(monkeypatch, sssd: bool): ++ contents = [ ++ """ ++ XXXXX XXXXXXXXX XXXXXXXXXXXX XXXXXXXXX ++ YYYYYYYY YYYYYY YYYYYYY YYYYYYYYYY ++ ZZZZZZ ZZZZZZZ ZZZZZZZ ZZZZZZZ ZZZZZZZ ++ AAAAAAA AAAAAAAAAA AAAAA ++ BBBBBBBB BBBBBBBBBB BBBBBBB BBBBB ++ CCCCCCCC CCCCCCC CCCCCCCC CCCCCCC ++ """, ++ """ ++ xxxxxx xxxxxxxxx xxxxxxxxx ++ yyyyyyyyy yyyyyyyyy yyyyyyyyyyyyyy ++ zzzzz zzzzzzz zzzzzzzzzzzz zzz ++ aaaaaa aaaaaaaa aaaaaaaaa aaaaaaaa ++ bbbbbbbb bbbbbbbbbb bbbbbbbb ++ """ ++ ] ++ ++ files = make_files(contents) ++ if sssd: ++ config = KnownHostsProxyConfig(sssd_config_files=files) ++ else: ++ config = KnownHostsProxyConfig(ssh_config_files=files) ++ ++ sssdupdate.update_config(config) ++ ++ for i in range(len(contents)): ++ check_file(files[i], contents[i]) ++ ++ ++def test_sssdupdate__sssd_change(monkeypatch): ++ contents = [ ++ """ ++ [sssd] ++ services = pam, nss ++ domains = test ++ """, ++ """ ++ [sssd] ++ # services = pam,nss ++ domains = test ++ """, ++ """ ++ [sssd] ++ services = pam,ssh,nss ++ domains = test ++ """ ++ ] ++ expected = [ ++ """ ++ [sssd] ++ services = pam, nss,ssh ++ domains = test ++ """, ++ """ ++ [sssd] ++ # services = pam,nss,ssh ++ domains = test ++ """, ++ """ ++ [sssd] ++ services = pam,ssh,nss ++ domains = test ++ """ ++ ] ++ # A failure here indicates an error in the test ++ assert len(contents) == len(expected) ++ ++ sssd_files = make_files(contents) ++ ssh_files = make_files(['']) ++ config = KnownHostsProxyConfig(sssd_config_files=sssd_files, ssh_config_files=ssh_files) ++ ++ sssdupdate.update_config(config) ++ ++ for i in range(len(expected)): ++ check_file(sssd_files[i], expected[i]) ++ ++ ++def test_sssdupdate__ssh_change(monkeypatch): ++ contents = [ ++ """ ++ First line ++ ProxyCommand /usr/bin/sss_ssh_knownhostsproxy -p %p -d domain %h ++ 3rd line ++ """, ++ """ ++ #\tProxyCommand /usr/bin/sss_ssh_knownhostsproxy --port=%p %h ++ # Another comment ++ """ ++ ] ++ expected = [ ++ """ ++ First line ++ KnownHostsCommand /usr/bin/sss_ssh_knownhosts -d domain %H ++ 3rd line ++ """, ++ """ ++ #\tKnownHostsCommand /usr/bin/sss_ssh_knownhosts %H ++ # Another comment ++ """ ++ ] ++ # A failure here indicates an error in the test ++ assert len(contents) == len(expected) ++ ++ files = make_files(contents) ++ config = KnownHostsProxyConfig(ssh_config_files=files) ++ ++ sssdupdate.update_config(config) ++ ++ for i in range(len(expected)): ++ check_file(files[i], expected[i]) +diff --git a/repos/system_upgrade/el9toel10/models/sssd_knownhostsproxy.py b/repos/system_upgrade/el9toel10/models/sssd_knownhostsproxy.py +new file mode 100644 +index 00000000..4d2198fd +--- /dev/null ++++ b/repos/system_upgrade/el9toel10/models/sssd_knownhostsproxy.py +@@ -0,0 +1,21 @@ ++from leapp.models import fields, Model ++from leapp.topics import SystemInfoTopic ++ ++ ++class KnownHostsProxyConfig(Model): ++ """ ++ SSSD and SSH configuration that is related to the sss_ssh_knownhostsproxy tool. ++ """ ++ topic = SystemInfoTopic ++ ++ sssd_config_files = fields.List(fields.String(), default=[]) ++ """ ++ List of files in the sssd configuration that include `service` ++ and that may need to be updated. ++ """ ++ ++ ssh_config_files = fields.List(fields.String(), default=[]) ++ """ ++ List of files in the ssh configuration that include `sss_ssh_knownhostsproxy` ++ and that need to be updated. ++ """ diff --git a/SPECS/leapp-repository.spec b/SPECS/leapp-repository.spec index 3c43544..32979c1 100644 --- a/SPECS/leapp-repository.spec +++ b/SPECS/leapp-repository.spec @@ -53,7 +53,7 @@ py2_byte_compile "%1" "%2"} Epoch: 1 Name: leapp-repository Version: 0.22.0 -Release: 5%{?dist}.elevate.3 +Release: 5%{?dist}.elevate.4 Summary: Repositories for leapp License: ASL 2.0 @@ -471,6 +471,10 @@ ln -s 10.0 %{next_major_ver} # no files here %changelog +* Thu Sep 11 2025 Yuriy Kohut - 0.22.0-5.elevate.4 +- Update ELevate patch: + - rebase to upstream 0.22.0-5 (hash 7a3e2f6c78fa827b18854dfe5b486a40453d2b05) + * Thu Aug 07 2025 Yuriy Kohut - 0.22.0-5.elevate.3 - Update ELevate patch: - rebase to upstream 0.22.0-5 (hash 5d1ea992c2bee09b7c452e0a5107667ea12963ff)