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)