From 9779a76d0f11a24b8fd8ba7ba5848ab35747a467 Mon Sep 17 00:00:00 2001 From: Yuriy Kohut Date: Tue, 8 Jul 2025 16:55:41 +0300 Subject: [PATCH] Add Vendors patch created against upstream hash c3bef4d9e89d109aee226f64e54e2eed088e591b Bump release to 0.22.0-4.elevate.2 --- SOURCES/leapp-repository-0.22.0-elevate.patch | 1518 ++++++++++++++++- SPECS/leapp-repository.spec | 6 +- 2 files changed, 1520 insertions(+), 4 deletions(-) diff --git a/SOURCES/leapp-repository-0.22.0-elevate.patch b/SOURCES/leapp-repository-0.22.0-elevate.patch index c60629d..21c40d2 100644 --- a/SOURCES/leapp-repository-0.22.0-elevate.patch +++ b/SOURCES/leapp-repository-0.22.0-elevate.patch @@ -1,3 +1,24 @@ +diff --git a/.github/workflows/pr-welcome-msg.yml b/.github/workflows/pr-welcome-msg.yml +index 0102c41f..f056fb79 100644 +--- a/.github/workflows/pr-welcome-msg.yml ++++ b/.github/workflows/pr-welcome-msg.yml +@@ -19,7 +19,7 @@ jobs: + issue-number: ${{ github.event.pull_request.number }} + body: | + ## **Thank you for contributing to the Leapp project!** +- Please note that every PR needs to comply with the [Leapp Guidelines](https://leapp.readthedocs.io/en/latest/contributing.html#) and must pass all tests in order to be mergeable. ++ Please note that every PR needs to comply with the [leapp-repository contribution and development guidelines](https://leapp-repository.readthedocs.io/latest/contrib-and-devel-guidelines.html) and must pass all tests in order to be mergeable. + If you want to request a review or rebuild a package in copr, you can use following commands as a comment: + - **`review please @oamg/developers`** to notify leapp developers of the review request + - **`/packit copr-build`** to submit a public copr build using packit +@@ -39,6 +39,6 @@ jobs: + + See other labels for particular jobs defined in the `.packit.yaml` file. + +- Please [open ticket](https://url.corp.redhat.com/oamg-ci-issue) in case you experience technical problem with the CI. (RH internal only) ++ Please [open ticket](https://red.ht/rhel-upgrades-ci-issue) in case you experience technical problem with the CI. (RH internal only) + + **Note:** In case there are problems with tests not being triggered automatically on new PR/commit or pending for a long time, please contact leapp-infra. diff --git a/.gitignore b/.gitignore index 0bb92d3d..a04c7ded 100644 --- a/.gitignore @@ -10,6 +31,35 @@ index 0bb92d3d..a04c7ded 100644 # pycharm .idea +diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md +index d4cb2046..7315b693 100644 +--- a/CONTRIBUTING.md ++++ b/CONTRIBUTING.md +@@ -1 +1 @@ +-See the [Contribution guidelines](https://leapp.readthedocs.io/en/latest/contributing.html) ++See the [contribution guidelines](https://leapp-repository.readthedocs.io/latest/contrib-and-devel-guidelines.html). +diff --git a/README.md b/README.md +index 6b45b4b7..43da589e 100644 +--- a/README.md ++++ b/README.md +@@ -1,6 +1,6 @@ +-**Before doing anything, please read the upstream [documentation](https://leapp-repository.readthedocs.io/).** ++**Before doing anything, please read the [leapp-repository documentation](https://leapp-repository.readthedocs.io/).** + +-Also, you could find useufl to read [Leapp framework documentation](https://leapp.readthedocs.io/). ++Also, you could find the [Leapp framework documentation](https://leapp.readthedocs.io/) useful to read. + + --- + +@@ -17,7 +17,7 @@ Also, you could find useufl to read [Leapp framework documentation](https://leap + - *All files in /var/log/leapp* + - */var/lib/leapp/leapp.db* + - *journalctl* +- - If you want, you can optionally send anything else would you like to provide (e.g. storage info) ++ - If you want, you can optionally send any other relevant information (e.g. storage, network) + + **For your convenience you can pack all logs with this command:** + diff --git a/ci/.gitignore b/ci/.gitignore new file mode 100644 index 00000000..e6f97f0f @@ -3421,6 +3471,19 @@ index 00000000..370758e6 + end + end +end +diff --git a/docs/source/contrib-and-devel-guidelines.md b/docs/source/contrib-and-devel-guidelines.md +index 66bef9b1..f2edf8b7 100644 +--- a/docs/source/contrib-and-devel-guidelines.md ++++ b/docs/source/contrib-and-devel-guidelines.md +@@ -1,7 +1,7 @@ + # Contribution and development guidelines + ## Code guidelines + +-Your code should follow the [Python Coding Guidelines](https://leapp.readthedocs.io/en/latest/python-coding-guidelines.html) used for the leapp project. On top of these rules follow instructions ++Your code should follow the [Python Coding Guidelines](https://leapp.readthedocs.io/en/latest/contributing.html#follow-python-coding-guidelines) used for the leapp project. On top of these rules follow instructions + below. + + ### Retrieving information about the source system should be separated from its use diff --git a/etc/leapp/transaction/to_reinstall b/etc/leapp/transaction/to_reinstall new file mode 100644 index 00000000..c6694a8e @@ -3515,6 +3578,781 @@ 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/cloud/checkgrubenvtofile/actor.py b/repos/system_upgrade/common/actors/cloud/checkgrubenvtofile/actor.py +new file mode 100644 +index 00000000..62ff7644 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/cloud/checkgrubenvtofile/actor.py +@@ -0,0 +1,34 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor import checkgrubenvtofile ++from leapp.models import ConvertGrubenvTask, FirmwareFacts, HybridImageAzure ++from leapp.reporting import Report ++from leapp.tags import ChecksPhaseTag, IPUWorkflowTag ++ ++ ++class CheckGrubenvToFile(Actor): ++ """ ++ Check whether grubenv is a symlink on Azure hybrid images using BIOS. ++ ++ Azure images provided by Red Hat aim for hybrid (BIOS/EFI) functionality, ++ however, currently GRUB is not able to see the "grubenv" file if it is a ++ symlink to a different partition (default on EFI with grub2-efi pkg ++ installed) and fails on BIOS systems. ++ ++ These images have a default relative symlink to EFI partition even when ++ booted using BIOS and in such cases GRUB is not able to find "grubenv" and ++ fails to get the kernel cmdline options resulting in system failing to boot ++ after upgrade. ++ ++ The symlink needs to be converted to a normal file with the content of ++ grubenv on the EFI partition in case the system is using BIOS and running ++ on the Azure cloud. This action is reported in the preupgrade phase. ++ ++ """ ++ ++ name = 'check_grubenv_to_file' ++ consumes = (FirmwareFacts, HybridImageAzure,) ++ produces = (ConvertGrubenvTask, Report) ++ tags = (ChecksPhaseTag, IPUWorkflowTag) ++ ++ def process(self): ++ checkgrubenvtofile.process() +diff --git a/repos/system_upgrade/common/actors/cloud/checkgrubenvtofile/libraries/checkgrubenvtofile.py b/repos/system_upgrade/common/actors/cloud/checkgrubenvtofile/libraries/checkgrubenvtofile.py +new file mode 100644 +index 00000000..a4c5ee1c +--- /dev/null ++++ b/repos/system_upgrade/common/actors/cloud/checkgrubenvtofile/libraries/checkgrubenvtofile.py +@@ -0,0 +1,44 @@ ++from leapp import reporting ++from leapp.libraries.stdlib import api ++from leapp.models import ConvertGrubenvTask, FirmwareFacts, HybridImageAzure ++ ++ ++def process(): ++ hybrid_image = next(api.consume(HybridImageAzure), None) ++ ++ if not hybrid_image: ++ return ++ ++ if not is_bios() or not hybrid_image.grubenv_is_symlink_to_efi: ++ return ++ ++ reporting.create_report([ ++ reporting.Title( ++ 'Azure hybrid (BIOS/EFI) image detected. "grubenv" symlink will be converted to a regular file' ++ ), ++ reporting.Summary( ++ 'Leapp detected the system is running on Azure cloud, booted using BIOS and ' ++ 'the "/boot/grub2/grubenv" file is a symlink to "../efi/EFI/redhat/grubenv". In case of such a ' ++ 'hybrid image scenario GRUB is not able to locate "grubenv" as it is a symlink to different ' ++ 'partition and fails to boot. If the system needs to be run in EFI mode later, please re-create ' ++ 'the relative symlink again.' ++ ), ++ reporting.Severity(reporting.Severity.HIGH), ++ reporting.Groups([ ++ reporting.Groups.PUBLIC_CLOUD, ++ reporting.Groups.BOOT ++ ]), ++ reporting.RelatedResource('file', '/boot/grub2/grubenv'), ++ reporting.RelatedResource('file', '/boot/efi/EFI/redhat/grubenv'), ++ ]) ++ ++ api.produce(ConvertGrubenvTask()) ++ ++ ++def is_bios(): ++ """ ++ Check whether system is booted into BIOS ++ """ ++ ++ ff = next(api.consume(FirmwareFacts), None) ++ return ff and ff.firmware == 'bios' +diff --git a/repos/system_upgrade/common/actors/cloud/checkgrubenvtofile/tests/test_checkgrubenvtofile.py b/repos/system_upgrade/common/actors/cloud/checkgrubenvtofile/tests/test_checkgrubenvtofile.py +new file mode 100644 +index 00000000..a5a203fd +--- /dev/null ++++ b/repos/system_upgrade/common/actors/cloud/checkgrubenvtofile/tests/test_checkgrubenvtofile.py +@@ -0,0 +1,35 @@ ++import pytest ++ ++from leapp import reporting ++from leapp.libraries.actor import checkgrubenvtofile ++from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked, produce_mocked ++from leapp.libraries.stdlib import api ++from leapp.models import FirmwareFacts, HybridImageAzure ++ ++BIOS_FIRMWARE = FirmwareFacts(firmware='bios') ++EFI_FIRMWARE = FirmwareFacts(firmware='efi') ++ ++ ++@pytest.mark.parametrize('is_hybrid', [True, False]) ++@pytest.mark.parametrize('is_bios', [True, False]) ++@pytest.mark.parametrize('is_symlink', [True, False]) ++def test_check_grubenv_to_file(monkeypatch, tmpdir, is_hybrid, is_bios, is_symlink): ++ ++ should_report = all([is_hybrid, is_bios, is_symlink]) ++ ++ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) ++ ++ firmware = BIOS_FIRMWARE if is_bios else EFI_FIRMWARE ++ msgs = [firmware] + ([HybridImageAzure(grubenv_is_symlink_to_efi=is_symlink)] if is_hybrid else []) ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(arch='x86_64', msgs=msgs)) ++ monkeypatch.setattr(api, "produce", produce_mocked()) ++ ++ checkgrubenvtofile.process() ++ ++ if should_report: ++ assert reporting.create_report.called == 1 ++ assert 'hybrid' in reporting.create_report.report_fields['title'] ++ assert api.produce.called == 1 ++ else: ++ assert reporting.create_report.called == 0 ++ assert api.produce.called == 0 +diff --git a/repos/system_upgrade/common/actors/cloud/checkhybridimage/actor.py b/repos/system_upgrade/common/actors/cloud/checkhybridimage/actor.py +deleted file mode 100644 +index 3cd2d864..00000000 +--- a/repos/system_upgrade/common/actors/cloud/checkhybridimage/actor.py ++++ /dev/null +@@ -1,24 +0,0 @@ +-from leapp.actors import Actor +-from leapp.libraries.actor.checkhybridimage import check_hybrid_image +-from leapp.models import FirmwareFacts, HybridImage, InstalledRPM +-from leapp.reporting import Report +-from leapp.tags import ChecksPhaseTag, IPUWorkflowTag +- +- +-class CheckHybridImage(Actor): +- """ +- Check if the system is using Azure hybrid image. +- +- These images have a default relative symlink to EFI +- partition even when booted using BIOS and in such cases +- GRUB is not able find "grubenv" to get the kernel cmdline +- options and fails to boot after upgrade`. +- """ +- +- name = 'checkhybridimage' +- consumes = (InstalledRPM, FirmwareFacts) +- produces = (HybridImage, Report) +- tags = (ChecksPhaseTag, IPUWorkflowTag) +- +- def process(self): +- check_hybrid_image() +diff --git a/repos/system_upgrade/common/actors/cloud/checkhybridimage/libraries/checkhybridimage.py b/repos/system_upgrade/common/actors/cloud/checkhybridimage/libraries/checkhybridimage.py +deleted file mode 100644 +index a4eb6fa1..00000000 +--- a/repos/system_upgrade/common/actors/cloud/checkhybridimage/libraries/checkhybridimage.py ++++ /dev/null +@@ -1,65 +0,0 @@ +-import os +- +-from leapp import reporting +-from leapp.libraries.common import rhui +-from leapp.libraries.common.config.version import get_source_major_version +-from leapp.libraries.common.rpms import has_package +-from leapp.libraries.stdlib import api +-from leapp.models import FirmwareFacts, HybridImage, InstalledRPM +- +-BIOS_PATH = '/boot/grub2/grubenv' +-EFI_PATH = '/boot/efi/EFI/redhat/grubenv' +- +- +-def is_grubenv_symlink_to_efi(): +- """ +- Check whether '/boot/grub2/grubenv' is a relative symlink to +- '/boot/efi/EFI/redhat/grubenv'. +- """ +- return os.path.islink(BIOS_PATH) and os.path.realpath(BIOS_PATH) == os.path.realpath(EFI_PATH) +- +- +-def is_azure_agent_installed(): +- """Check whether 'WALinuxAgent' package is installed.""" +- src_ver_major = get_source_major_version() +- +- family = rhui.RHUIFamily(rhui.RHUIProvider.AZURE) +- azure_setups = rhui.RHUI_SETUPS.get(family, []) +- +- agent_pkg = None +- for setup in azure_setups: +- setup_major_ver = str(setup.os_version[0]) +- if setup_major_ver == src_ver_major: +- agent_pkg = setup.extra_info.get('agent_pkg') +- break +- +- if not agent_pkg: +- return False +- +- return has_package(InstalledRPM, agent_pkg) +- +- +-def is_bios(): +- """Check whether system is booted into BIOS""" +- ff = next(api.consume(FirmwareFacts), None) +- return ff and ff.firmware == 'bios' +- +- +-def check_hybrid_image(): +- """Check whether the system is using Azure hybrid image.""" +- if all([is_grubenv_symlink_to_efi(), is_azure_agent_installed(), is_bios()]): +- api.produce(HybridImage(detected=True)) +- reporting.create_report([ +- reporting.Title( +- 'Azure hybrid (BIOS/EFI) image detected. "grubenv" symlink will be converted to a regular file' +- ), +- reporting.Summary( +- 'Leapp detected the system is running on Azure cloud, booted using BIOS and ' +- 'the "/boot/grub2/grubenv" file is a symlink to "../efi/EFI/redhat/grubenv". In case of such a ' +- 'hybrid image scenario GRUB is not able to locate "grubenv" as it is a symlink to different ' +- 'partition and fails to boot. If the system needs to be run in EFI mode later, please re-create ' +- 'the relative symlink again.' +- ), +- reporting.Severity(reporting.Severity.HIGH), +- reporting.Groups([reporting.Groups.PUBLIC_CLOUD]), +- ]) +diff --git a/repos/system_upgrade/common/actors/cloud/checkhybridimage/tests/test_checkhybridimage.py b/repos/system_upgrade/common/actors/cloud/checkhybridimage/tests/test_checkhybridimage.py +deleted file mode 100644 +index 16fbb44c..00000000 +--- a/repos/system_upgrade/common/actors/cloud/checkhybridimage/tests/test_checkhybridimage.py ++++ /dev/null +@@ -1,82 +0,0 @@ +-import pytest +- +-from leapp import reporting +-from leapp.libraries.actor import checkhybridimage +-from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked, produce_mocked +-from leapp.libraries.stdlib import api +-from leapp.models import FirmwareFacts, InstalledRPM, RPM +-from leapp.reporting import Report +- +-RH_PACKAGER = 'Red Hat, Inc. ' +-WA_AGENT_RPM = RPM( +- name='WALinuxAgent', 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' +-) +-NO_AGENT_RPM = RPM( +- name='NoAgent', 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' +-) +- +-INSTALLED_AGENT = InstalledRPM(items=[WA_AGENT_RPM]) +-NOT_INSTALLED_AGENT = InstalledRPM(items=[NO_AGENT_RPM]) +- +-BIOS_FIRMWARE = FirmwareFacts(firmware='bios') +-EFI_FIRMWARE = FirmwareFacts(firmware='efi') +- +-BIOS_PATH = '/boot/grub2/grubenv' +-EFI_PATH = '/boot/efi/EFI/redhat/grubenv' +- +- +-def test_hybrid_image(monkeypatch, tmpdir): +- grubenv_efi = tmpdir.join('grubenv_efi') +- grubenv_efi.write('grubenv') +- +- grubenv_boot = tmpdir.join('grubenv_boot') +- grubenv_boot.mksymlinkto('grubenv_efi') +- +- monkeypatch.setattr(checkhybridimage, 'BIOS_PATH', grubenv_boot.strpath) +- monkeypatch.setattr(checkhybridimage, 'EFI_PATH', grubenv_efi.strpath) +- monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) +- monkeypatch.setattr( +- api, 'current_actor', CurrentActorMocked(arch='x86_64', msgs=[BIOS_FIRMWARE, INSTALLED_AGENT]) +- ) +- monkeypatch.setattr(api, "produce", produce_mocked()) +- +- checkhybridimage.check_hybrid_image() +- assert reporting.create_report.called == 1 +- assert 'hybrid' in reporting.create_report.report_fields['title'] +- assert api.produce.called == 1 +- +- +-@pytest.mark.parametrize('is_symlink, realpath_match, is_bios, agent_installed', [ +- (False, True, True, True), +- (True, False, True, True), +- (True, True, False, True), +- (True, True, True, False), +-]) +-def test_no_hybrid_image(monkeypatch, is_symlink, realpath_match, is_bios, agent_installed, tmpdir): +- grubenv_efi = tmpdir.join('grubenv_efi') +- grubenv_efi.write('grubenv') +- grubenv_efi_false = tmpdir.join('grubenv_efi_false') +- grubenv_efi.write('nope') +- grubenv_boot = tmpdir.join('grubenv_boot') +- +- grubenv_target = grubenv_efi if realpath_match else grubenv_efi_false +- +- if is_symlink: +- grubenv_boot.mksymlinkto(grubenv_target) +- +- firmw = BIOS_FIRMWARE if is_bios else EFI_FIRMWARE +- inst_rpms = INSTALLED_AGENT if agent_installed else NOT_INSTALLED_AGENT +- +- monkeypatch.setattr(checkhybridimage, 'BIOS_PATH', grubenv_boot.strpath) +- monkeypatch.setattr(checkhybridimage, 'EFI_PATH', grubenv_efi.strpath) +- monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) +- monkeypatch.setattr( +- api, 'current_actor', CurrentActorMocked(arch='x86_64', msgs=[firmw, inst_rpms]) +- ) +- monkeypatch.setattr(api, "produce", produce_mocked()) +- +- checkhybridimage.check_hybrid_image() +- assert not reporting.create_report.called +- assert not api.produce.called +diff --git a/repos/system_upgrade/common/actors/cloud/convertgrubenvtofile/actor.py b/repos/system_upgrade/common/actors/cloud/convertgrubenvtofile/actor.py +new file mode 100644 +index 00000000..68ef54bb +--- /dev/null ++++ b/repos/system_upgrade/common/actors/cloud/convertgrubenvtofile/actor.py +@@ -0,0 +1,21 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor import convertgrubenvtofile ++from leapp.models import ConvertGrubenvTask ++from leapp.tags import FinalizationPhaseTag, IPUWorkflowTag ++ ++ ++class ConvertGrubenvToFile(Actor): ++ """ ++ Convert "grubenv" symlink to a regular file on Azure hybrid images using BIOS. ++ ++ For more information see CheckGrubenvToFile actor. ++ ++ """ ++ ++ name = 'convert_grubenv_to_file' ++ consumes = (ConvertGrubenvTask,) ++ produces = () ++ tags = (FinalizationPhaseTag, IPUWorkflowTag) ++ ++ def process(self): ++ convertgrubenvtofile.process() +diff --git a/repos/system_upgrade/common/actors/cloud/grubenvtofile/libraries/grubenvtofile.py b/repos/system_upgrade/common/actors/cloud/convertgrubenvtofile/libraries/convertgrubenvtofile.py +similarity index 79% +rename from repos/system_upgrade/common/actors/cloud/grubenvtofile/libraries/grubenvtofile.py +rename to repos/system_upgrade/common/actors/cloud/convertgrubenvtofile/libraries/convertgrubenvtofile.py +index 4d699ec3..1803c6c7 100644 +--- a/repos/system_upgrade/common/actors/cloud/grubenvtofile/libraries/grubenvtofile.py ++++ b/repos/system_upgrade/common/actors/cloud/convertgrubenvtofile/libraries/convertgrubenvtofile.py +@@ -1,9 +1,17 @@ + from leapp.libraries.stdlib import api, CalledProcessError, run ++from leapp.models import ConvertGrubenvTask + + BIOS_PATH = '/boot/grub2/grubenv' + EFI_PATH = '/boot/efi/EFI/redhat/grubenv' + + ++def process(): ++ convert_grubenv_task = next(api.consume(ConvertGrubenvTask), None) ++ ++ if convert_grubenv_task: ++ grubenv_to_file() ++ ++ + def grubenv_to_file(): + try: + run(['unlink', BIOS_PATH]) +diff --git a/repos/system_upgrade/common/actors/cloud/convertgrubenvtofile/tests/test_convertgrubenvtofile.py b/repos/system_upgrade/common/actors/cloud/convertgrubenvtofile/tests/test_convertgrubenvtofile.py +new file mode 100644 +index 00000000..c4534bd6 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/cloud/convertgrubenvtofile/tests/test_convertgrubenvtofile.py +@@ -0,0 +1,51 @@ ++import pytest ++ ++from leapp.libraries.actor import convertgrubenvtofile ++from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked ++from leapp.libraries.stdlib import api, CalledProcessError ++from leapp.models import ConvertGrubenvTask ++ ++ ++def raise_call_error(args=None): ++ raise CalledProcessError( ++ message='A Leapp Command Error occurred.', ++ command=args, ++ result={'signal': None, 'exit_code': 1, 'pid': 0, 'stdout': 'fake', 'stderr': 'fake'} ++ ) ++ ++ ++class run_mocked(object): ++ def __init__(self, raise_err=False): ++ self.called = 0 ++ self.args = [] ++ self.raise_err = raise_err ++ ++ def __call__(self, *args): ++ self.called += 1 ++ self.args.append(args) ++ if self.raise_err: ++ raise_call_error(args) ++ ++ ++def test_grubenv_to_file(monkeypatch): ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(arch='x86_64', msgs=[ConvertGrubenvTask()])) ++ monkeypatch.setattr(convertgrubenvtofile, 'run', run_mocked(raise_err=False)) ++ convertgrubenvtofile.process() ++ assert convertgrubenvtofile.run.called == 2 ++ ++ ++def test_no_grubenv_to_file(monkeypatch): ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(arch='x86_64', msgs=[])) ++ monkeypatch.setattr(convertgrubenvtofile, 'run', run_mocked(raise_err=False)) ++ convertgrubenvtofile.process() ++ assert convertgrubenvtofile.run.called == 0 ++ ++ ++def test_fail_grubenv_to_file(monkeypatch): ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(arch='x86_64', msgs=[ConvertGrubenvTask()])) ++ monkeypatch.setattr(convertgrubenvtofile, 'run', run_mocked(raise_err=True)) ++ monkeypatch.setattr(api, 'current_logger', logger_mocked()) ++ convertgrubenvtofile.grubenv_to_file() ++ ++ assert convertgrubenvtofile.run.called == 1 ++ assert api.current_logger.warnmsg[0].startswith('Could not unlink') +diff --git a/repos/system_upgrade/common/actors/cloud/grubenvtofile/actor.py b/repos/system_upgrade/common/actors/cloud/grubenvtofile/actor.py +deleted file mode 100644 +index fc94219c..00000000 +--- a/repos/system_upgrade/common/actors/cloud/grubenvtofile/actor.py ++++ /dev/null +@@ -1,28 +0,0 @@ +-from leapp.actors import Actor +-from leapp.libraries.actor.grubenvtofile import grubenv_to_file +-from leapp.models import HybridImage +-from leapp.tags import FinalizationPhaseTag, IPUWorkflowTag +- +- +-class GrubenvToFile(Actor): +- """ +- Convert "grubenv" symlink to a regular file on Azure hybrid images using BIOS. +- +- Azure images provided by Red Hat aim for hybrid (BIOS/EFI) functionality, +- however, currently GRUB is not able to see the "grubenv" file if it is a symlink +- to a different partition (default on EFI with grub2-efi pkg installed) and +- fails on BIOS systems. This actor converts the symlink to the normal file +- with the content of grubenv on the EFI partition in case the system is using BIOS +- and running on the Azure cloud. This action is reported in the preupgrade phase. +- """ +- +- name = 'grubenvtofile' +- consumes = (HybridImage,) +- produces = () +- tags = (FinalizationPhaseTag, IPUWorkflowTag) +- +- def process(self): +- grubenv_msg = next(self.consume(HybridImage), None) +- +- if grubenv_msg and grubenv_msg.detected: +- grubenv_to_file() +diff --git a/repos/system_upgrade/common/actors/cloud/grubenvtofile/tests/test_grubenvtofile.py b/repos/system_upgrade/common/actors/cloud/grubenvtofile/tests/test_grubenvtofile.py +deleted file mode 100644 +index 807f5efa..00000000 +--- a/repos/system_upgrade/common/actors/cloud/grubenvtofile/tests/test_grubenvtofile.py ++++ /dev/null +@@ -1,43 +0,0 @@ +-import pytest +- +-from leapp.libraries.actor import grubenvtofile +-from leapp.libraries.common.testutils import logger_mocked +-from leapp.libraries.stdlib import api, CalledProcessError +-from leapp.models import HybridImage +- +- +-def raise_call_error(args=None): +- raise CalledProcessError( +- message='A Leapp Command Error occurred.', +- command=args, +- result={'signal': None, 'exit_code': 1, 'pid': 0, 'stdout': 'fake', 'stderr': 'fake'} +- ) +- +- +-class run_mocked(object): +- def __init__(self, raise_err=False): +- self.called = 0 +- self.args = [] +- self.raise_err = raise_err +- +- def __call__(self, *args): +- self.called += 1 +- self.args.append(args) +- if self.raise_err: +- raise_call_error(args) +- +- +-def test_grubenv_to_file(monkeypatch): +- monkeypatch.setattr(api, 'consume', lambda x: iter([HybridImage()])) +- monkeypatch.setattr(grubenvtofile, 'run', run_mocked()) +- grubenvtofile.grubenv_to_file() +- assert grubenvtofile.run.called == 2 +- +- +-def test_fail_grubenv_to_file(monkeypatch): +- monkeypatch.setattr(api, 'consume', lambda x: iter([HybridImage()])) +- monkeypatch.setattr(grubenvtofile, 'run', run_mocked(raise_err=True)) +- monkeypatch.setattr(api, 'current_logger', logger_mocked()) +- grubenvtofile.grubenv_to_file() +- assert grubenvtofile.run.called == 1 +- assert api.current_logger.warnmsg[0].startswith('Could not unlink') +diff --git a/repos/system_upgrade/common/actors/cloud/scanhybridimage/actor.py b/repos/system_upgrade/common/actors/cloud/scanhybridimage/actor.py +new file mode 100644 +index 00000000..b1848141 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/cloud/scanhybridimage/actor.py +@@ -0,0 +1,19 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor.scanhybridimage import scan_hybrid_image ++from leapp.models import FirmwareFacts, HybridImageAzure, InstalledRPM ++from leapp.reporting import Report ++from leapp.tags import FactsPhaseTag, IPUWorkflowTag ++ ++ ++class ScanHybridImageAzure(Actor): ++ """ ++ Check if the system is using Azure hybrid image. ++ """ ++ ++ name = 'scan_hybrid_image_azure' ++ consumes = (InstalledRPM, FirmwareFacts) ++ produces = (HybridImageAzure, Report) ++ tags = (FactsPhaseTag, IPUWorkflowTag) ++ ++ def process(self): ++ scan_hybrid_image() +diff --git a/repos/system_upgrade/common/actors/cloud/scanhybridimage/libraries/scanhybridimage.py b/repos/system_upgrade/common/actors/cloud/scanhybridimage/libraries/scanhybridimage.py +new file mode 100644 +index 00000000..a37ab415 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/cloud/scanhybridimage/libraries/scanhybridimage.py +@@ -0,0 +1,102 @@ ++import os ++ ++from leapp.libraries.common import rhui ++from leapp.libraries.common.config.version import get_source_major_version ++from leapp.libraries.common.rpms import has_package ++from leapp.libraries.stdlib import api, CalledProcessError, run ++from leapp.models import FirmwareFacts, HybridImageAzure, InstalledRPM ++ ++EFI_MOUNTPOINT = '/boot/efi/' ++AZURE_HYPERVISOR_ID = 'microsoft' ++ ++GRUBENV_BIOS_PATH = '/boot/grub2/grubenv' ++GRUBENV_EFI_PATH = '/boot/efi/EFI/redhat/grubenv' ++ ++ ++def scan_hybrid_image(): ++ """ ++ Check whether the system is using Azure hybrid image. ++ """ ++ ++ hybrid_image_condition_1 = is_azure_agent_installed() and is_bios() ++ hybrid_image_condition_2 = has_efi_partition() and is_bios() and is_running_on_azure_hypervisor() ++ ++ if any([hybrid_image_condition_1, hybrid_image_condition_2]): ++ api.produce( ++ HybridImageAzure( ++ grubenv_is_symlink_to_efi=is_grubenv_symlink_to_efi() ++ ) ++ ) ++ ++ ++def is_azure_agent_installed(): ++ """ ++ Check whether 'WALinuxAgent' package is installed. ++ """ ++ ++ src_ver_major = get_source_major_version() ++ ++ family = rhui.RHUIFamily(rhui.RHUIProvider.AZURE) ++ azure_setups = rhui.RHUI_SETUPS.get(family, []) ++ ++ agent_pkg = None ++ for setup in azure_setups: ++ setup_major_ver = str(setup.os_version[0]) ++ if setup_major_ver == src_ver_major: ++ agent_pkg = setup.extra_info.get('agent_pkg') ++ break ++ ++ if not agent_pkg: ++ return False ++ ++ return has_package(InstalledRPM, agent_pkg) ++ ++ ++def has_efi_partition(): ++ """ ++ Check whether ESP partition exists and is mounted. ++ """ ++ ++ return os.path.exists(EFI_MOUNTPOINT) and os.path.ismount(EFI_MOUNTPOINT) ++ ++ ++def is_bios(): ++ """ ++ Check whether system is booted into BIOS ++ """ ++ ++ ff = next(api.consume(FirmwareFacts), None) ++ return ff and ff.firmware == 'bios' ++ ++ ++def is_running_on_azure_hypervisor(): ++ """ ++ Check if system is running on Azure hypervisor (Hyper-V) ++ """ ++ ++ return detect_virt() == AZURE_HYPERVISOR_ID ++ ++ ++def detect_virt(): ++ """ ++ Detect execution in a virtualized environment ++ """ ++ ++ try: ++ result = run(['systemd-detect-virt']) ++ except CalledProcessError as e: ++ api.current_logger().warning('Unable to detect virtualization environment! Error: {}'.format(e)) ++ return '' ++ ++ return result['stdout'] ++ ++ ++def is_grubenv_symlink_to_efi(): ++ """ ++ Check whether '/boot/grub2/grubenv' is a relative symlink to '/boot/efi/EFI/redhat/grubenv'. ++ """ ++ ++ is_symlink = os.path.islink(GRUBENV_BIOS_PATH) ++ realpaths_match = os.path.realpath(GRUBENV_BIOS_PATH) == os.path.realpath(GRUBENV_EFI_PATH) ++ ++ return is_symlink and realpaths_match +diff --git a/repos/system_upgrade/common/actors/cloud/scanhybridimage/tests/test_scanhybridimage.py b/repos/system_upgrade/common/actors/cloud/scanhybridimage/tests/test_scanhybridimage.py +new file mode 100644 +index 00000000..a0f6fd4c +--- /dev/null ++++ b/repos/system_upgrade/common/actors/cloud/scanhybridimage/tests/test_scanhybridimage.py +@@ -0,0 +1,124 @@ ++import os ++ ++import pytest ++ ++from leapp import reporting ++from leapp.libraries.actor import scanhybridimage ++from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked, logger_mocked, produce_mocked ++from leapp.libraries.stdlib import api, CalledProcessError ++from leapp.models import FirmwareFacts, HybridImageAzure, InstalledRPM, RPM ++ ++RH_PACKAGER = 'Red Hat, Inc. ' ++WA_AGENT_RPM = RPM( ++ name='WALinuxAgent', 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' ++) ++NO_AGENT_RPM = RPM( ++ name='NoAgent', 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' ++) ++ ++INSTALLED_AGENT = InstalledRPM(items=[WA_AGENT_RPM]) ++NOT_INSTALLED_AGENT = InstalledRPM(items=[NO_AGENT_RPM]) ++ ++BIOS_FIRMWARE = FirmwareFacts(firmware='bios') ++EFI_FIRMWARE = FirmwareFacts(firmware='efi') ++ ++BIOS_PATH = '/boot/grub2/grubenv' ++EFI_PATH = '/boot/efi/EFI/redhat/grubenv' ++ ++ ++def raise_call_error(args=None): ++ raise CalledProcessError( ++ message='A Leapp Command Error occurred.', ++ command=args, ++ result={'signal': None, 'exit_code': 1, 'pid': 0, 'stdout': 'fake', 'stderr': 'fake'} ++ ) ++ ++ ++class run_mocked(object): ++ def __init__(self, hypervisor='', raise_err=False): ++ self.hypervisor = hypervisor ++ self.called = 0 ++ self.args = [] ++ self.raise_err = raise_err ++ ++ def __call__(self, *args): # pylint: disable=inconsistent-return-statements ++ self.called += 1 ++ self.args.append(args) ++ ++ if self.raise_err: ++ raise_call_error(args) ++ ++ if args[0] == ['systemd-detect-virt']: ++ return {'stdout': self.hypervisor} ++ ++ raise AttributeError("Unexpected command supplied!") ++ ++ ++@pytest.mark.parametrize('hypervisor, expected', [('none', False), ('microsoft', True)]) ++def test_is_running_on_azure_hypervisor(monkeypatch, hypervisor, expected): ++ monkeypatch.setattr(scanhybridimage, 'run', run_mocked(hypervisor)) ++ ++ assert scanhybridimage.is_running_on_azure_hypervisor() == expected ++ ++ ++def test_is_running_on_azure_hypervisor_error(monkeypatch): ++ monkeypatch.setattr(scanhybridimage, 'run', run_mocked('microsoft', raise_err=True)) ++ monkeypatch.setattr(api, 'current_logger', logger_mocked()) ++ ++ result = scanhybridimage.is_running_on_azure_hypervisor() ++ ++ assert result is False ++ assert any('Unable to detect' in msg for msg in api.current_logger.warnmsg) ++ ++ ++@pytest.mark.parametrize('is_symlink', [True, False]) ++@pytest.mark.parametrize('realpath_match', [True, False]) ++def test_is_grubenv_symlink_to_efi(monkeypatch, is_symlink, realpath_match): ++ grubenv_efi_false = '/other/grub/grubenv' ++ ++ monkeypatch.setattr(scanhybridimage, 'GRUBENV_BIOS_PATH', BIOS_PATH) ++ monkeypatch.setattr(scanhybridimage, 'GRUBENV_EFI_PATH', EFI_PATH) ++ ++ monkeypatch.setattr(os.path, 'islink', lambda path: is_symlink) ++ ++ def mocked_realpath(path): ++ if realpath_match: ++ return EFI_PATH ++ ++ return grubenv_efi_false if path == EFI_PATH else EFI_PATH ++ ++ monkeypatch.setattr(os.path, 'realpath', mocked_realpath) ++ ++ result = scanhybridimage.is_grubenv_symlink_to_efi() ++ ++ assert result == (is_symlink and realpath_match) ++ ++ ++@pytest.mark.parametrize('is_bios', [True, False]) ++@pytest.mark.parametrize('has_efi_partition', [True, False]) ++@pytest.mark.parametrize('agent_installed', [True, False]) ++@pytest.mark.parametrize('is_microsoft', [True, False]) ++@pytest.mark.parametrize('is_symlink', [True, False]) ++def test_hybrid_image(monkeypatch, tmpdir, is_bios, has_efi_partition, agent_installed, is_microsoft, is_symlink): ++ should_produce = (is_microsoft and is_bios and has_efi_partition) or (agent_installed and is_bios) ++ ++ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) ++ msgs = [ ++ BIOS_FIRMWARE if is_bios else EFI_FIRMWARE, ++ INSTALLED_AGENT if agent_installed else NOT_INSTALLED_AGENT ++ ] ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(arch='x86_64', msgs=msgs)) ++ monkeypatch.setattr(api, 'produce', produce_mocked()) ++ monkeypatch.setattr(scanhybridimage, 'has_efi_partition', lambda: has_efi_partition) ++ monkeypatch.setattr(scanhybridimage, 'is_running_on_azure_hypervisor', lambda: is_microsoft) ++ monkeypatch.setattr(scanhybridimage, 'is_grubenv_symlink_to_efi', lambda: is_symlink) ++ ++ scanhybridimage.scan_hybrid_image() ++ ++ if should_produce: ++ assert api.produce.called == 1 ++ assert HybridImageAzure(grubenv_is_symlink_to_efi=is_symlink) in api.produce.model_instances ++ else: ++ assert not api.produce.called 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 @@ -4111,6 +4949,19 @@ index d4a64793..4ec1d6e0 100644 def _inhibit_upgrade(msg): local_path = os.path.join('/etc/leapp/file', REPOMAP_FILE) hint = ( +diff --git a/repos/system_upgrade/common/actors/rpmscanner/libraries/rpmscanner.py b/repos/system_upgrade/common/actors/rpmscanner/libraries/rpmscanner.py +index dbe56191..74c4b101 100644 +--- a/repos/system_upgrade/common/actors/rpmscanner/libraries/rpmscanner.py ++++ b/repos/system_upgrade/common/actors/rpmscanner/libraries/rpmscanner.py +@@ -25,6 +25,8 @@ except ImportError: + + def _get_package_repository_data_yum(): + yum_base = yum.YumBase() ++ # DNF configuration is not loaded here, since no impact for operations ++ # done by the actor is observed here + pkg_repos = {} + + try: diff --git a/repos/system_upgrade/common/actors/rpmtransactionconfigtaskscollector/libraries/rpmtransactionconfigtaskscollector.py b/repos/system_upgrade/common/actors/rpmtransactionconfigtaskscollector/libraries/rpmtransactionconfigtaskscollector.py index 43ac1fc4..62aefaf4 100644 --- a/repos/system_upgrade/common/actors/rpmtransactionconfigtaskscollector/libraries/rpmtransactionconfigtaskscollector.py @@ -4999,10 +5850,18 @@ index 00000000..df764b53 + } +} diff --git a/repos/system_upgrade/common/files/rhel_upgrade.py b/repos/system_upgrade/common/files/rhel_upgrade.py -index 34f7b8f9..acba532c 100644 +index 34f7b8f9..27824406 100644 --- a/repos/system_upgrade/common/files/rhel_upgrade.py +++ b/repos/system_upgrade/common/files/rhel_upgrade.py -@@ -184,6 +184,7 @@ class RhelUpgradeCommand(dnf.cli.Command): +@@ -116,6 +116,7 @@ class RhelUpgradeCommand(dnf.cli.Command): + self.base.conf.best = self.plugin_data['dnf_conf']['best'] + self.base.conf.assumeyes = True + self.base.conf.gpgcheck = self.plugin_data['dnf_conf']['gpgcheck'] ++ self.base.conf.localpkg_gpgcheck = False + self.base.conf.debug_solver = self.plugin_data['dnf_conf']['debugsolver'] + self.base.conf.module_platform_id = self.plugin_data['dnf_conf']['platform_id'] + installroot = self.plugin_data['dnf_conf'].get('installroot') +@@ -184,6 +185,7 @@ class RhelUpgradeCommand(dnf.cli.Command): to_install = self.plugin_data['pkgs_info']['to_install'] to_remove = self.plugin_data['pkgs_info']['to_remove'] to_upgrade = self.plugin_data['pkgs_info']['to_upgrade'] @@ -5010,7 +5869,7 @@ index 34f7b8f9..acba532c 100644 # Modules to enable self._process_entities(entities=[available_modules_to_enable], -@@ -196,6 +197,9 @@ class RhelUpgradeCommand(dnf.cli.Command): +@@ -196,6 +198,9 @@ class RhelUpgradeCommand(dnf.cli.Command): self._process_entities(entities=to_install, op=self.base.install, entity_name='Package') # Packages to be upgraded self._process_entities(entities=to_upgrade, op=self.base.upgrade, entity_name='Package') @@ -5180,6 +6039,18 @@ index c9c3f1fc..96907be0 100644 def is_nogpgcheck_set(): +diff --git a/repos/system_upgrade/common/libraries/module.py b/repos/system_upgrade/common/libraries/module.py +index 7d4e8aa4..db725e71 100644 +--- a/repos/system_upgrade/common/libraries/module.py ++++ b/repos/system_upgrade/common/libraries/module.py +@@ -38,6 +38,7 @@ def _create_or_get_dnf_base(base=None): + conf.substitutions.update_from_etc('/') + + base = dnf.Base(conf=conf) ++ base.conf.read() + base.init_plugins() + base.read_all_repos() + # configure plugins after the repositories are loaded diff --git a/repos/system_upgrade/common/libraries/repomaputils.py b/repos/system_upgrade/common/libraries/repomaputils.py new file mode 100644 index 00000000..39b7d662 @@ -5386,6 +6257,46 @@ index 00000000..de4056fb +class ActiveVendorList(Model): + topic = VendorTopic + data = fields.List(fields.String()) +diff --git a/repos/system_upgrade/common/models/grubenv.py b/repos/system_upgrade/common/models/grubenv.py +index be541131..c7f339f1 100644 +--- a/repos/system_upgrade/common/models/grubenv.py ++++ b/repos/system_upgrade/common/models/grubenv.py +@@ -1,12 +1,11 @@ +-from leapp.models import fields, Model ++from leapp.models import Model + from leapp.topics import SystemFactsTopic + + +-class HybridImage(Model): ++class ConvertGrubenvTask(Model): + """ +- Model used for instructing Leapp to convert "grubenv" symlink +- into a regular file in case of hybrid (BIOS/EFI) images using BIOS +- on Azure. ++ Model used for instructing Leapp to convert "grubenv" symlink into a ++ regular file. + """ ++ + topic = SystemFactsTopic +- detected = fields.Boolean(default=False) +diff --git a/repos/system_upgrade/common/models/hybridimage.py b/repos/system_upgrade/common/models/hybridimage.py +new file mode 100644 +index 00000000..6cf860ef +--- /dev/null ++++ b/repos/system_upgrade/common/models/hybridimage.py +@@ -0,0 +1,12 @@ ++from leapp.models import fields, Model ++from leapp.topics import SystemFactsTopic ++ ++ ++class HybridImageAzure(Model): ++ """ ++ Model used to signify that the system is using a hybrid (BIOS/EFI) images ++ using BIOS on Azure. ++ """ ++ ++ topic = SystemFactsTopic ++ grubenv_is_symlink_to_efi = fields.Boolean(default=False) 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 @@ -5503,6 +6414,607 @@ index c076fe6b..2455a2f6 100644 UPGRADE_BLS_DIR = '/boot/upgrade-loader' CONTAINER_DOWNLOAD_DIR = '/tmp_pkg_download_dir' +diff --git a/repos/system_upgrade/el8toel9/actors/cloud/checkvalidgrubcfghybrid/actor.py b/repos/system_upgrade/el8toel9/actors/cloud/checkvalidgrubcfghybrid/actor.py +new file mode 100644 +index 00000000..14668e42 +--- /dev/null ++++ b/repos/system_upgrade/el8toel9/actors/cloud/checkvalidgrubcfghybrid/actor.py +@@ -0,0 +1,32 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor import checkvalidgrubcfghybrid ++from leapp.models import FirmwareFacts, HybridImageAzure ++from leapp.reporting import Report ++from leapp.tags import ChecksPhaseTag, IPUWorkflowTag ++ ++ ++class CheckValidGrubConfigHybrid(Actor): ++ """ ++ Check potential for boot failures in Azure Gen1 VMs due to invalid grubcfg ++ ++ This actor addresses the issue where the `/boot/grub2/grub.cfg` file is ++ overwritten during the upgrade process by an old RHEL7 configuration ++ leftover on the system, causing the system to fail to boot. ++ ++ The problem occurs on hybrid Azure images, which support both UEFI and ++ Legacy systems. The issue is caused by one of the scriplets in `grub-efi` ++ which overwrites during the upgrade current configuration in ++ `/boot/grub2/grub.cfg` by an old configuration from ++ `/boot/efi/EFI/redhat/grub.cfg`. ++ ++ The issue is detected specifically to Azure hybrid cloud systems. ++ ++ """ ++ ++ name = 'check_valid_grubcfg_hybrid' ++ consumes = (FirmwareFacts, HybridImageAzure,) ++ produces = (Report,) ++ tags = (ChecksPhaseTag, IPUWorkflowTag) ++ ++ def process(self): ++ checkvalidgrubcfghybrid.process() +diff --git a/repos/system_upgrade/el8toel9/actors/cloud/checkvalidgrubcfghybrid/libraries/checkvalidgrubcfghybrid.py b/repos/system_upgrade/el8toel9/actors/cloud/checkvalidgrubcfghybrid/libraries/checkvalidgrubcfghybrid.py +new file mode 100644 +index 00000000..374772f5 +--- /dev/null ++++ b/repos/system_upgrade/el8toel9/actors/cloud/checkvalidgrubcfghybrid/libraries/checkvalidgrubcfghybrid.py +@@ -0,0 +1,30 @@ ++from leapp import reporting ++from leapp.libraries.stdlib import api ++from leapp.models import HybridImageAzure ++ ++ ++def process(): ++ hybrid_image = next(api.consume(HybridImageAzure), None) ++ ++ if hybrid_image: ++ reporting.create_report([ ++ reporting.Title( ++ 'Azure hybrid (BIOS/EFI) image detected. The GRUB configuration might be regenerated.' ++ ), ++ reporting.Summary( ++ 'Leapp detected that the system is running on Azure cloud and is booted using BIOS. ' ++ 'While upgrading from older systems (i.e. RHEL 7) on such systems' ++ 'it is possible that the system might end up with invalid GRUB configuration, ' ++ 'as `/boot/grub2/grub.cfg` might be overwritten by an old configuration from ' ++ '`/boot/efi/EFI/redhat/grub.cfg`, which might cause the system to fail to boot. ' ++ ++ 'Please ensure that the system is able to boot with both of these ' ++ 'configurations. If an invalid configuration is detected during upgrade, ' ++ 'it will be regenerated automatically using `grub2-mkconfig.`' ++ ), ++ reporting.Severity(reporting.Severity.HIGH), ++ reporting.Groups([ ++ reporting.Groups.PUBLIC_CLOUD, ++ reporting.Groups.BOOT ++ ]), ++ ]) +diff --git a/repos/system_upgrade/el8toel9/actors/cloud/checkvalidgrubcfghybrid/tests/test_checkvalidgrubcfghybrid.py b/repos/system_upgrade/el8toel9/actors/cloud/checkvalidgrubcfghybrid/tests/test_checkvalidgrubcfghybrid.py +new file mode 100644 +index 00000000..3fd9a53c +--- /dev/null ++++ b/repos/system_upgrade/el8toel9/actors/cloud/checkvalidgrubcfghybrid/tests/test_checkvalidgrubcfghybrid.py +@@ -0,0 +1,25 @@ ++import pytest ++ ++from leapp import reporting ++from leapp.libraries.actor import checkvalidgrubcfghybrid ++from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked, produce_mocked ++from leapp.libraries.stdlib import api ++from leapp.models import HybridImageAzure ++ ++ ++@pytest.mark.parametrize('is_hybrid', [True, False]) ++def test_check_invalid_grubcfg_hybrid(monkeypatch, is_hybrid): ++ ++ monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) ++ ++ msgs = [HybridImageAzure()] if is_hybrid else [] ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(arch='x86_64', msgs=msgs)) ++ monkeypatch.setattr(api, "produce", produce_mocked()) ++ ++ checkvalidgrubcfghybrid.process() ++ ++ if is_hybrid: ++ assert reporting.create_report.called == 1 ++ assert 'regenerated' in reporting.create_report.report_fields['title'] ++ else: ++ assert reporting.create_report.called == 0 +diff --git a/repos/system_upgrade/el8toel9/actors/cloud/ensurevalidgrubcfghybrid/actor.py b/repos/system_upgrade/el8toel9/actors/cloud/ensurevalidgrubcfghybrid/actor.py +new file mode 100644 +index 00000000..a350c7a0 +--- /dev/null ++++ b/repos/system_upgrade/el8toel9/actors/cloud/ensurevalidgrubcfghybrid/actor.py +@@ -0,0 +1,30 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor import ensurevalidgrubcfghybrid ++from leapp.models import HybridImageAzure ++from leapp.tags import ApplicationsPhaseTag, IPUWorkflowTag ++ ++ ++class EnsureValidGrubcfgHybrid(Actor): ++ """ ++ Resolve boot failures in Azure Gen1 VMs during upgrades from RHEL 7 to RHEL 8 to RHEL 9. ++ ++ If old configuration is detected, this actor regenerates the grub ++ configuration using `grub2-mkconfig -o /boot/grub2/grub.cfg` after ++ installing rpms to ensure the correct boot configuration is in place. ++ ++ Old configuration is detected by looking for a menuentry corresponding to a ++ kernel from RHEL 7 which should not be present on RHEL 8 systems. ++ ++ The fix is applied specifically to Azure hybrid cloud systems. ++ ++ See also CheckValidGrubConfigHybrid actor. ++ ++ """ ++ ++ name = 'ensure_valid_grubcfg_hybrid' ++ consumes = (HybridImageAzure,) ++ produces = () ++ tags = (ApplicationsPhaseTag, IPUWorkflowTag) ++ ++ def process(self): ++ ensurevalidgrubcfghybrid.process() +diff --git a/repos/system_upgrade/el8toel9/actors/cloud/ensurevalidgrubcfghybrid/libraries/ensurevalidgrubcfghybrid.py b/repos/system_upgrade/el8toel9/actors/cloud/ensurevalidgrubcfghybrid/libraries/ensurevalidgrubcfghybrid.py +new file mode 100644 +index 00000000..f94cf67b +--- /dev/null ++++ b/repos/system_upgrade/el8toel9/actors/cloud/ensurevalidgrubcfghybrid/libraries/ensurevalidgrubcfghybrid.py +@@ -0,0 +1,66 @@ ++import re ++ ++from leapp.exceptions import StopActorExecutionError ++from leapp.libraries.common.config.architecture import ARCH_ACCEPTED ++from leapp.libraries.stdlib import api, CalledProcessError, run ++from leapp.models import HybridImageAzure ++ ++GRUB_CFG_PATH = '/boot/grub2/grub.cfg' ++ ++MATCH_ARCH = r'({})'.format('|'.join(ARCH_ACCEPTED)) ++MATCH_RHEL7_KERNEL_VERSION = r"\d+\.\d+\.\d+-\d+(\.\d+)*\.el7\.{}".format(MATCH_ARCH) ++MATCH_RHEL7_KERNEL_DEFINITION = r"vmlinuz-{}".format(MATCH_RHEL7_KERNEL_VERSION) ++ ++ ++def process(): ++ if not _is_hybrid_image(): ++ api.current_logger().info('System is not a hybrid image. Skipping.') ++ return ++ ++ grubcfg = _read_grubcfg() ++ if _is_grubcfg_invalid(grubcfg): ++ _run_grub2_mkconfig() ++ ++ ++def _is_hybrid_image(): ++ return next(api.consume(HybridImageAzure), None) is not None ++ ++ ++def _read_grubcfg(): ++ api.current_logger().debug('Reading {}:'.format(GRUB_CFG_PATH)) ++ with open(GRUB_CFG_PATH, 'r') as fin: ++ grubcfg = fin.read() ++ ++ api.current_logger().debug(grubcfg) ++ return grubcfg ++ ++ ++def _is_grubcfg_invalid(grubcfg): ++ return _contains_rhel7_kernel_definition(grubcfg) ++ ++ ++def _contains_rhel7_kernel_definition(grubcfg): ++ api.current_logger().debug("Looking for RHEL7 kernel version ...") ++ ++ match = re.search(MATCH_RHEL7_KERNEL_DEFINITION, grubcfg) ++ ++ api.current_logger().debug( ++ "Matched: {}".format(match.group() if match else "[NO MATCH]") ++ ) ++ ++ return match is not None ++ ++ ++def _run_grub2_mkconfig(): ++ api.current_logger().info("Regenerating {}".format(GRUB_CFG_PATH)) ++ ++ try: ++ run([ ++ 'grub2-mkconfig', ++ '-o', ++ GRUB_CFG_PATH ++ ]) ++ except CalledProcessError as err: ++ msg = 'Could not regenerate {}: {}'.format(GRUB_CFG_PATH, str(err)) ++ api.current_logger().error(msg) ++ raise StopActorExecutionError(msg) +diff --git a/repos/system_upgrade/el8toel9/actors/cloud/ensurevalidgrubcfghybrid/tests/files/invalid_grub.cfg b/repos/system_upgrade/el8toel9/actors/cloud/ensurevalidgrubcfghybrid/tests/files/invalid_grub.cfg +new file mode 100644 +index 00000000..58f55c53 +--- /dev/null ++++ b/repos/system_upgrade/el8toel9/actors/cloud/ensurevalidgrubcfghybrid/tests/files/invalid_grub.cfg +@@ -0,0 +1,51 @@ ++ ++# Created by osbuild ++ ++set timeout=10 ++ ++# load the grubenv file ++load_env ++ ++# selection of the next boot entry ++if [ "${next_entry}" ] ; then ++ set default="${next_entry}" ++ set next_entry= ++ save_env next_entry ++ set boot_once=true ++else ++ set default="${saved_entry}" ++fi ++ ++if [ "${prev_saved_entry}" ]; then ++ set saved_entry="${prev_saved_entry}" ++ save_env saved_entry ++ set prev_saved_entry= ++ save_env prev_saved_entry ++ set boot_once=true ++fi ++ ++function savedefault { ++ if [ -z "${boot_once}" ]; then ++ saved_entry="${chosen}" ++ save_env saved_entry ++ fi ++} ++ ++serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1 ++terminal_input serial console ++terminal_output serial console ++ ++menuentry 'Red Hat Enterprise Linux Server (3.10.0-1160.119.1.el7.x86_64) 7.9 (Maipo)' --class red --class gnu-linux --class gnu --class os --unrestricted --id 'gnulinux-3.10.0-1160.99.1.el7.x86_64-advanced-76a22bf4-f153-4541-b6c7-0332c0dfaeac' { ++ insmod all_video ++ set gfxpayload=keep ++ search --no-floppy --set=root --fs-uuid 61779359-8d11-49ba-bc9d-8d038ee4b108 ++ linuxefi /vmlinuz-3.10.0-1160.119.1.el7.x86_64 root=UUID=d3c9a2bd-7ffb-4113-9b8f-234c13b18274 ro crashkernel=auto console=tty1 console=ttyS0 earlyprintk=ttyS0 rootdelay=300 scsi_mod.use_blk_mq=y LANG=en_US.UTF-8 ++ initrdefi /initramfs-3.10.0-1160.119.1.el7.x86_64.img ++} ++menuentry 'Red Hat Enterprise Linux (3.10.0-1160.99.1.el7.x86_64) 7.9 (Maipo)' --class red --class gnu-linux --class gnu --class os --unrestricted --id 'gnulinux-3.10.0-1160.99.1.el7.x86_64-advanced-76a22bf4-f153-4541-b6c7-0332c0dfaeac' { ++ insmod all_video ++ set gfxpayload=keep ++ search --no-floppy --set=root --fs-uuid 61779359-8d11-49ba-bc9d-8d038ee4b108 ++ linuxefi /vmlinuz-3.10.0-1160.99.1.el7.x86_64 root=UUID=d3c9a2bd-7ffb-4113-9b8f-234c13b18274 ro crashkernel=auto console=tty1 console=ttyS0 earlyprintk=ttyS0 rootdelay=300 scsi_mod.use_blk_mq=y ++ initrdefi /initramfs-3.10.0-1160.99.1.el7.x86_64.img ++} +diff --git a/repos/system_upgrade/el8toel9/actors/cloud/ensurevalidgrubcfghybrid/tests/files/valid_grub.cfg b/repos/system_upgrade/el8toel9/actors/cloud/ensurevalidgrubcfghybrid/tests/files/valid_grub.cfg +new file mode 100644 +index 00000000..8192665e +--- /dev/null ++++ b/repos/system_upgrade/el8toel9/actors/cloud/ensurevalidgrubcfghybrid/tests/files/valid_grub.cfg +@@ -0,0 +1,195 @@ ++# ++# DO NOT EDIT THIS FILE ++# ++# It is automatically generated by grub2-mkconfig using templates ++# from /etc/grub.d and settings from /etc/default/grub ++# ++ ++### BEGIN /etc/grub.d/00_header ### ++set pager=1 ++ ++if [ -f ${config_directory}/grubenv ]; then ++ load_env -f ${config_directory}/grubenv ++elif [ -s $prefix/grubenv ]; then ++ load_env ++fi ++if [ "${next_entry}" ] ; then ++ set default="${next_entry}" ++ set next_entry= ++ save_env next_entry ++ set boot_once=true ++else ++ set default="${saved_entry}" ++fi ++ ++if [ x"${feature_menuentry_id}" = xy ]; then ++ menuentry_id_option="--id" ++else ++ menuentry_id_option="" ++fi ++ ++export menuentry_id_option ++ ++if [ "${prev_saved_entry}" ]; then ++ set saved_entry="${prev_saved_entry}" ++ save_env saved_entry ++ set prev_saved_entry= ++ save_env prev_saved_entry ++ set boot_once=true ++fi ++ ++function savedefault { ++ if [ -z "${boot_once}" ]; then ++ saved_entry="${chosen}" ++ save_env saved_entry ++ fi ++} ++ ++function load_video { ++ if [ x$feature_all_video_module = xy ]; then ++ insmod all_video ++ else ++ insmod efi_gop ++ insmod efi_uga ++ insmod ieee1275_fb ++ insmod vbe ++ insmod vga ++ insmod video_bochs ++ insmod video_cirrus ++ fi ++} ++ ++serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1 ++terminal_input serial console ++terminal_output serial console ++if [ x$feature_timeout_style = xy ] ; then ++ set timeout_style=countdown ++ set timeout=10 ++# Fallback hidden-timeout code in case the timeout_style feature is ++# unavailable. ++elif sleep --interruptible 10 ; then ++ set timeout=0 ++fi ++### END /etc/grub.d/00_header ### ++ ++### BEGIN /etc/grub.d/00_tuned ### ++set tuned_params="" ++set tuned_initrd="" ++### END /etc/grub.d/00_tuned ### ++ ++### BEGIN /etc/grub.d/01_users ### ++if [ -f ${prefix}/user.cfg ]; then ++ source ${prefix}/user.cfg ++ if [ -n "${GRUB2_PASSWORD}" ]; then ++ set superusers="root" ++ export superusers ++ password_pbkdf2 root ${GRUB2_PASSWORD} ++ fi ++fi ++### END /etc/grub.d/01_users ### ++ ++### BEGIN /etc/grub.d/08_fallback_counting ### ++insmod increment ++# Check if boot_counter exists and boot_success=0 to activate this behaviour. ++if [ -n "${boot_counter}" -a "${boot_success}" = "0" ]; then ++ # if countdown has ended, choose to boot rollback deployment, ++ # i.e. default=1 on OSTree-based systems. ++ if [ "${boot_counter}" = "0" -o "${boot_counter}" = "-1" ]; then ++ set default=1 ++ set boot_counter=-1 ++ # otherwise decrement boot_counter ++ else ++ decrement boot_counter ++ fi ++ save_env boot_counter ++fi ++### END /etc/grub.d/08_fallback_counting ### ++ ++### BEGIN /etc/grub.d/10_linux ### ++insmod part_gpt ++insmod xfs ++set root='hd0,gpt2' ++if [ x$feature_platform_search_hint = xy ]; then ++ search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt2 --hint-efi=hd0,gpt2 --hint-baremetal=ahci0,gpt2 61779359-8d11-49ba-bc9d-8d038ee4b108 ++else ++ search --no-floppy --fs-uuid --set=root 61779359-8d11-49ba-bc9d-8d038ee4b108 ++fi ++insmod part_gpt ++insmod xfs ++set boot='hd0,gpt2' ++if [ x$feature_platform_search_hint = xy ]; then ++ search --no-floppy --fs-uuid --set=boot --hint-bios=hd0,gpt2 --hint-efi=hd0,gpt2 --hint-baremetal=ahci0,gpt2 61779359-8d11-49ba-bc9d-8d038ee4b108 ++else ++ search --no-floppy --fs-uuid --set=boot 61779359-8d11-49ba-bc9d-8d038ee4b108 ++fi ++ ++# This section was generated by a script. Do not modify the generated file - all changes ++# will be lost the next time file is regenerated. Instead edit the BootLoaderSpec files. ++# ++# The blscfg command parses the BootLoaderSpec files stored in /boot/loader/entries and ++# populates the boot menu. Please refer to the Boot Loader Specification documentation ++# for the files format: https://www.freedesktop.org/wiki/Specifications/BootLoaderSpec/. ++ ++# The kernelopts variable should be defined in the grubenv file. But to ensure that menu ++# entries populated from BootLoaderSpec files that use this variable work correctly even ++# without a grubenv file, define a fallback kernelopts variable if this has not been set. ++# ++# The kernelopts variable in the grubenv file can be modified using the grubby tool or by ++# executing the grub2-mkconfig tool. For the latter, the values of the GRUB_CMDLINE_LINUX ++# and GRUB_CMDLINE_LINUX_DEFAULT options from /etc/default/grub file are used to set both ++# the kernelopts variable in the grubenv file and the fallback kernelopts variable. ++if [ -z "${kernelopts}" ]; then ++ set kernelopts="root=/dev/mapper/rootvg-rootlv ro ro crashkernel=auto console=tty1 console=ttyS0 earlyprintk=ttyS0 rootdelay=300 scsi_mod.use_blk_mq=y " ++fi ++ ++insmod blscfg ++blscfg ++### END /etc/grub.d/10_linux ### ++ ++### BEGIN /etc/grub.d/10_reset_boot_success ### ++# Hiding the menu is ok if last boot was ok or if this is a first boot attempt to boot the entry ++if [ "${boot_success}" = "1" -o "${boot_indeterminate}" = "1" ]; then ++ set menu_hide_ok=1 ++else ++ set menu_hide_ok=0 ++fi ++# Reset boot_indeterminate after a successful boot ++if [ "${boot_success}" = "1" ] ; then ++ set boot_indeterminate=0 ++# Avoid boot_indeterminate causing the menu to be hidden more then once ++elif [ "${boot_indeterminate}" = "1" ]; then ++ set boot_indeterminate=2 ++fi ++# Reset boot_success for current boot ++set boot_success=0 ++save_env boot_success boot_indeterminate ++### END /etc/grub.d/10_reset_boot_success ### ++ ++### BEGIN /etc/grub.d/12_menu_auto_hide ### ++### END /etc/grub.d/12_menu_auto_hide ### ++ ++### BEGIN /etc/grub.d/20_linux_xen ### ++### END /etc/grub.d/20_linux_xen ### ++ ++### BEGIN /etc/grub.d/20_ppc_terminfo ### ++### END /etc/grub.d/20_ppc_terminfo ### ++ ++### BEGIN /etc/grub.d/30_os-prober ### ++### END /etc/grub.d/30_os-prober ### ++ ++### BEGIN /etc/grub.d/30_uefi-firmware ### ++### END /etc/grub.d/30_uefi-firmware ### ++ ++### BEGIN /etc/grub.d/40_custom ### ++# This file provides an easy way to add custom menu entries. Simply type the ++# menu entries you want to add after this comment. Be careful not to change ++# the 'exec tail' line above. ++### END /etc/grub.d/40_custom ### ++ ++### BEGIN /etc/grub.d/41_custom ### ++if [ -f ${config_directory}/custom.cfg ]; then ++ source ${config_directory}/custom.cfg ++elif [ -z "${config_directory}" -a -f $prefix/custom.cfg ]; then ++ source $prefix/custom.cfg; ++fi ++### END /etc/grub.d/41_custom ### +diff --git a/repos/system_upgrade/el8toel9/actors/cloud/ensurevalidgrubcfghybrid/tests/test_ensurevalidgrubcfghybrid.py b/repos/system_upgrade/el8toel9/actors/cloud/ensurevalidgrubcfghybrid/tests/test_ensurevalidgrubcfghybrid.py +new file mode 100644 +index 00000000..3ba46cb5 +--- /dev/null ++++ b/repos/system_upgrade/el8toel9/actors/cloud/ensurevalidgrubcfghybrid/tests/test_ensurevalidgrubcfghybrid.py +@@ -0,0 +1,124 @@ ++import os ++ ++import pytest ++ ++from leapp.exceptions import StopActorExecutionError ++from leapp.libraries.actor import ensurevalidgrubcfghybrid ++from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked ++from leapp.libraries.stdlib import api, CalledProcessError ++from leapp.models import HybridImageAzure ++ ++CUR_DIR = os.path.dirname(os.path.abspath(__file__)) ++ ++ ++def raise_call_error(args=None): ++ raise CalledProcessError( ++ message='A Leapp Command Error occurred.', ++ command=args, ++ result={'signal': None, 'exit_code': 1, 'pid': 0, 'stdout': 'fake', 'stderr': 'fake'} ++ ) ++ ++ ++class run_mocked(object): ++ def __init__(self, raise_err=False): ++ self.called = 0 ++ self.args = [] ++ self.raise_err = raise_err ++ ++ def __call__(self, *args): ++ self.called += 1 ++ self.args.append(args) ++ if self.raise_err: ++ raise_call_error(args) ++ ++ ++def test_not_hybrid_image(monkeypatch): ++ """ ++ Skip when system is not a hybrid. ++ """ ++ ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[])) ++ monkeypatch.setattr(api, 'current_logger', logger_mocked()) ++ monkeypatch.setattr(ensurevalidgrubcfghybrid, 'run', run_mocked(raise_err=False)) ++ ++ ensurevalidgrubcfghybrid.process() ++ ++ assert api.current_logger.infomsg[0].startswith('System is not a hybrid image') ++ assert ensurevalidgrubcfghybrid.run.called == 0 ++ ++ ++@pytest.mark.parametrize("is_invalid", [True, False]) ++def test_is_grubcfg_valid(monkeypatch, is_invalid): ++ ++ grubcfg_filename = ('invalid' if is_invalid else 'valid') + '_grub.cfg' ++ grubcfg_filepath = os.path.join(CUR_DIR, 'files', grubcfg_filename) ++ with open(grubcfg_filepath, 'r') as fin: ++ grubcfg = fin.read() ++ ++ assert ensurevalidgrubcfghybrid._is_grubcfg_invalid(grubcfg) == is_invalid ++ ++ ++def test_valid_grubcfg(monkeypatch): ++ """ ++ Test valid configuration does not trigger grub2-mkconfig ++ """ ++ ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[HybridImageAzure()])) ++ monkeypatch.setattr(api, 'current_logger', logger_mocked()) ++ monkeypatch.setattr(ensurevalidgrubcfghybrid, 'run', run_mocked(raise_err=False)) ++ ++ grubcfg_filepath = os.path.join(CUR_DIR, 'files', 'valid_grub.cfg') ++ with open(grubcfg_filepath, 'r') as fin: ++ grubcfg = fin.read() ++ ++ monkeypatch.setattr(ensurevalidgrubcfghybrid, '_read_grubcfg', lambda: grubcfg) ++ ++ ensurevalidgrubcfghybrid.process() ++ ++ assert ensurevalidgrubcfghybrid.run.called == 0 ++ ++ ++def test_invalid_grubcfg(monkeypatch): ++ """ ++ Test invalid configuration triggers grub2-mkconfig ++ """ ++ ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[HybridImageAzure()])) ++ monkeypatch.setattr(api, 'current_logger', logger_mocked()) ++ monkeypatch.setattr(ensurevalidgrubcfghybrid, 'run', run_mocked(raise_err=False)) ++ ++ grubcfg_filepath = os.path.join(CUR_DIR, 'files', 'invalid_grub.cfg') ++ with open(grubcfg_filepath, 'r') as fin: ++ grubcfg = fin.read() ++ ++ monkeypatch.setattr(ensurevalidgrubcfghybrid, '_read_grubcfg', lambda: grubcfg) ++ ++ ensurevalidgrubcfghybrid.process() ++ ++ assert ensurevalidgrubcfghybrid.run.called == 1 ++ assert any(msg.startswith('Regenerating') for msg in api.current_logger.infomsg) ++ ++ ++def test_run_error(monkeypatch): ++ """ ++ Test invalid configuration triggers grub2-mkconfig ++ """ ++ ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[HybridImageAzure()])) ++ monkeypatch.setattr(api, 'current_logger', logger_mocked()) ++ monkeypatch.setattr(ensurevalidgrubcfghybrid, 'run', run_mocked(raise_err=True)) ++ ++ grubcfg_filepath = os.path.join(CUR_DIR, 'files', 'invalid_grub.cfg') ++ with open(grubcfg_filepath, 'r') as fin: ++ grubcfg = fin.read() ++ ++ monkeypatch.setattr(ensurevalidgrubcfghybrid, '_read_grubcfg', lambda: grubcfg) ++ ++ with pytest.raises(StopActorExecutionError): ++ ensurevalidgrubcfghybrid.process() ++ ++ assert ensurevalidgrubcfghybrid.run.called == 1 ++ assert any( ++ msg.startswith('Could not regenerate') ++ for msg in api.current_logger.err ++ ) diff --git a/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/libraries/removeupgradeefientry.py b/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/libraries/removeupgradeefientry.py index daa7b2ca..dd604d8b 100644 --- a/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/libraries/removeupgradeefientry.py diff --git a/SPECS/leapp-repository.spec b/SPECS/leapp-repository.spec index 02fd95e..61a7c7c 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: 4%{?dist}.elevate.1 +Release: 4%{?dist}.elevate.2 Summary: Repositories for leapp License: ASL 2.0 @@ -422,6 +422,10 @@ ln -s 10.0 %{next_major_ver} # no files here %changelog +* Tue Jul 08 2025 Yuriy Kohut - 0.22.0-4.elevate.2 +- Update ELevate patch: + - rebase to upstream 0.22.0-4 (hash c3bef4d9e89d109aee226f64e54e2eed088e591b) + * Tue Jun 24 2025 Yuriy Kohut - 0.22.0-4.elevate.1 - Update ELevate patch: - rebase to upstream 0.22.0-4 (hash 7c6bb64e550ccfefe4001e79fef44b7d69446c36)