diff --git a/repos/system_upgrade/common/actors/checkleftoverpackages/libraries/checkleftoverpackages.py b/repos/system_upgrade/common/actors/checkleftoverpackages/libraries/checkleftoverpackages.py index 348e23ffab..8b93bd102c 100644 --- a/repos/system_upgrade/common/actors/checkleftoverpackages/libraries/checkleftoverpackages.py +++ b/repos/system_upgrade/common/actors/checkleftoverpackages/libraries/checkleftoverpackages.py @@ -1,4 +1,6 @@ +import re from leapp.libraries.common.rpms import get_installed_rpms +from leapp.libraries.common.config.version import get_source_major_version from leapp.models import InstalledUnsignedRPM, LeftoverPackages, RPM from leapp.libraries.stdlib import api @@ -19,15 +21,20 @@ def process(): continue name, version, release, epoch, packager, arch, pgpsig = rpm.split('|') - if 'el7' in release and name not in set(unsigned + LEAPP_PACKAGES): - to_remove.items.append(RPM( - name=name, - version=version, - epoch=epoch, - packager=packager, - arch=arch, - release=release, - pgpsig=pgpsig - )) + version_pattern = r'el(\d+)' + match = re.search(version_pattern, release) + + if match: + RHEL_version = match.group(1) + if int(RHEL_version) <= int(get_source_major_version()) and name not in LEAPP_PACKAGES + unsigned: + to_remove.items.append(RPM( + name=name, + version=version, + epoch=epoch, + packager=packager, + arch=arch, + release=release, + pgpsig=pgpsig + )) api.produce(to_remove) 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 905947c34e..e0d781e904 100644 --- a/repos/system_upgrade/common/actors/checkleftoverpackages/tests/test_checkleftoverpackages.py +++ b/repos/system_upgrade/common/actors/checkleftoverpackages/tests/test_checkleftoverpackages.py @@ -6,38 +6,45 @@ @pytest.mark.parametrize( - ('rpm_name', 'release', 'expected_to_be_removed'), + ('source_major_version', 'rpm_name', 'release', 'expected_to_be_removed'), ( - # el7 - ('sed', '7.el7', True), - ('leapp', '1.el7', False), - ('unsigned', '1.el7', False), + (7, 'sed', '7.el7', True), + (8, 'sed', '7.el7', True), + (8, 'sed', '8.el7', True), - # el8 - ('leapp-repository', '1.el8', False), - ('gnutls', '8.el8_9.1', False), + (7, 'leapp', '1.el7', False), + (7, 'unsigned', '1.el7', False), - # other - ('whois-nls', '1.fc39', False), + (7, 'leapp-repository', '1.el8', False), + (7, 'gnutls', '8.el8_9.1', False), ) ) -def test_package_to_be_removed(monkeypatch, rpm_name, release, expected_to_be_removed): +def test_package_to_be_removed(monkeypatch, source_major_version, rpm_name, release, expected_to_be_removed): + rpm_details = { + 'version': '0.1', + 'epoch': '0', + 'packager': 'packager', + 'arch': 'noarch', + 'pgpsig': 'OTHER_SIG' + } + def get_installed_rpms_mocked(): - return [f'{rpm_name}|0.1|{release}|0|packager|noarch|OTHER_SIG'] - - UnsignedRPM = RPM(name='unsigned', version='0.1', release=f'{release}', epoch='0', packager='packager', arch='noarch', - pgpsig='OTHER_SIG') + return ['{}|{}|{}|{}|{}|{}|{}'.format(rpm_name, rpm_details['version'], release, rpm_details['epoch'], + rpm_details['packager'], rpm_details['arch'], rpm_details['pgpsig'])] + + 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(checkleftoverpackages, 'get_installed_rpms', get_installed_rpms_mocked) + monkeypatch.setattr(checkleftoverpackages, 'get_source_major_version', lambda: source_major_version) monkeypatch.setattr(api, 'produce', produce_mocked()) checkleftoverpackages.process() expected_output = LeftoverPackages() if expected_to_be_removed: - expected_output.items.append(RPM(name=f'{rpm_name}', version='0.1', release=f'{release}', epoch='0', - packager='packager', arch='noarch', pgpsig='OTHER_SIG')) + expected_output.items.append(RPM(name=rpm_name, release=release, **rpm_details)) assert api.produce.called == 1 assert api.produce.model_instances[0] == expected_output diff --git a/repos/system_upgrade/common/actors/removeleftoverpackages/actor.py b/repos/system_upgrade/common/actors/removeleftoverpackages/actor.py index 063e07d57a..4d4ef39c04 100644 --- a/repos/system_upgrade/common/actors/removeleftoverpackages/actor.py +++ b/repos/system_upgrade/common/actors/removeleftoverpackages/actor.py @@ -1,17 +1,15 @@ from leapp.actors import Actor -from leapp.libraries import stdlib -from leapp.libraries.common import rhsm -from leapp.libraries.common.rpms import get_installed_rpms -from leapp.models import LeftoverPackages, RemovedPackages, RPM +from leapp.libraries.actor import removeleftoverpackages +from leapp.models import LeftoverPackages, RemovedPackages from leapp.reporting import Report from leapp.tags import ExperimentalTag, IPUWorkflowTag, RPMUpgradePhaseTag class RemoveLeftoverPackages(Actor): """ - Remove el7 packages left on the system after the upgrade to RHEL 8. + Remove packages left on the system after the upgrade to higher major version of RHEL. - Removal of el7 packages is necessary in order to keep the machine in supported state. + Removal of packages is necessary in order to keep the machine in supported state. Actor generates report telling users what packages have been removed. """ @@ -21,36 +19,4 @@ class RemoveLeftoverPackages(Actor): tags = (RPMUpgradePhaseTag, IPUWorkflowTag, ExperimentalTag) def process(self): - leftover_packages = next(self.consume(LeftoverPackages), LeftoverPackages()) - if not leftover_packages.items: - self.log.info('No leftover packages, skipping...') - return - - installed_rpms = get_installed_rpms() - - to_remove = ['-'.join([pkg.name, pkg.version, pkg.release]) for pkg in leftover_packages.items] - cmd = ['dnf', 'remove', '-y', '--noautoremove'] + to_remove - if rhsm.skip_rhsm(): - # ensure we don't use suscription-manager when it should be skipped - cmd += ['--disableplugin', 'subscription-manager'] - try: - stdlib.run(cmd) - except stdlib.CalledProcessError: - error = 'Failed to remove packages: {}'.format(', '.join(to_remove)) - self.log.error(error) - return - - removed_packages = RemovedPackages() - removed = list(set(installed_rpms) - set(get_installed_rpms())) - for pkg in removed: - name, version, release, epoch, packager, arch, pgpsig = pkg.split('|') - removed_packages.items.append(RPM( - name=name, - version=version, - epoch=epoch, - packager=packager, - arch=arch, - release=release, - pgpsig=pgpsig - )) - self.produce(removed_packages) + removeleftoverpackages.process() diff --git a/repos/system_upgrade/common/actors/removeleftoverpackages/libraries/removeleftoverpackages.py b/repos/system_upgrade/common/actors/removeleftoverpackages/libraries/removeleftoverpackages.py new file mode 100644 index 0000000000..681965bf39 --- /dev/null +++ b/repos/system_upgrade/common/actors/removeleftoverpackages/libraries/removeleftoverpackages.py @@ -0,0 +1,56 @@ +from leapp.libraries.stdlib import api, run, CalledProcessError +from leapp.libraries.common.rpms import get_installed_rpms +from leapp.libraries.common.rhsm import skip_rhsm +from leapp.models import LeftoverPackages, RemovedPackages, RPM + + +def get_leftover_packages(): + leftover_packages = next(api.consume(LeftoverPackages), LeftoverPackages()) + if not leftover_packages.items: + api.current_logger().info('No leftover packages, skipping...') + return None + return leftover_packages + + +def _get_removed_packages(installed_rpms): + return list(set(installed_rpms) - set(get_installed_rpms())) + + +def get_removed_packages(installed_rpms): + removed_packages = RemovedPackages() + removed = _get_removed_packages(installed_rpms) + + for pkg in removed: + name, version, release, epoch, packager, arch, pgpsig = pkg.split('|') + removed_packages.items.append(RPM( + name=name, + version=version, + epoch=epoch, + packager=packager, + arch=arch, + release=release, + pgpsig=pgpsig + )) + return removed_packages + + +def process(): + leftover_packages = get_leftover_packages() + if not leftover_packages: + return + + installed_rpms = get_installed_rpms() + + to_remove = ['-'.join([pkg.name, pkg.version, pkg.release]) for pkg in leftover_packages.items] + cmd = ['dnf', 'remove', '-y', '--noautoremove'] + to_remove + if skip_rhsm(): + # ensure we don't use suscription-manager when it should be skipped + cmd += ['--disableplugin', 'subscription-manager'] + try: + run(cmd) + except (CalledProcessError, OSError): + error = 'Failed to remove packages: {}'.format(', '.join(to_remove)) + api.current_logger().error(error) + return + + api.produce(get_removed_packages(installed_rpms)) diff --git a/repos/system_upgrade/common/actors/removeleftoverpackages/tests/test_removeleftoverpackages.py b/repos/system_upgrade/common/actors/removeleftoverpackages/tests/test_removeleftoverpackages.py new file mode 100644 index 0000000000..12125eafe7 --- /dev/null +++ b/repos/system_upgrade/common/actors/removeleftoverpackages/tests/test_removeleftoverpackages.py @@ -0,0 +1,116 @@ +import pytest + +from leapp.libraries.common.testutils import produce_mocked, CurrentActorMocked, logger_mocked +from leapp.libraries.stdlib import api, CalledProcessError +from leapp.libraries.actor import removeleftoverpackages +from leapp.models import LeftoverPackages, RemovedPackages, RPM + + +def test_get_leftover_packages(monkeypatch): + rpm = RPM(name='rpm', version='1.0', release='1.el7', epoch='0', packager='foo', arch='noarch', pgpsig='SIG') + monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[LeftoverPackages(items=[rpm])])) + monkeypatch.setattr(api, 'current_logger', logger_mocked()) + + assert removeleftoverpackages.get_leftover_packages() == LeftoverPackages(items=[rpm]) + + +def test_no_leftover_packages(monkeypatch): + monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[LeftoverPackages()])) + monkeypatch.setattr(api, 'current_logger', logger_mocked()) + + removeleftoverpackages.get_leftover_packages() + + assert api.current_logger.infomsg == ['No leftover packages, skipping...'] + + +def test_remove_lefover_packages_error(monkeypatch): + def get_leftover_pkgs(): + return LeftoverPackages(items=[RPM(name='rpm', version='1.0', release='1.el7', epoch='0', + packager='packager', arch='noarch', pgpsig='SIG')]) + + def mocked_run(cmd): + raise CalledProcessError(command=cmd, + message='mocked error', + result={'stdout': 'out', 'stderr': 'err', 'exit_code': 1, 'signal': 0}) + + monkeypatch.setattr(api, 'current_logger', logger_mocked()) + monkeypatch.setattr(api, 'produce', produce_mocked()) + monkeypatch.setattr(removeleftoverpackages, 'get_leftover_packages', get_leftover_pkgs) + monkeypatch.setattr(removeleftoverpackages, 'get_installed_rpms', lambda: []) + monkeypatch.setattr(removeleftoverpackages, 'skip_rhsm', lambda: False) + monkeypatch.setattr(removeleftoverpackages, 'run', mocked_run) + + removeleftoverpackages.process() + + assert api.produce.called == 0 + assert api.current_logger.errmsg == ['Failed to remove packages: rpm-1.0-1.el7'] + + +def test__get_removed_packages(monkeypatch): + pkg1 = 'rpm|1.0|1.el7|0|packager|noarch|SIG' + pkg2 = 'rpm2|1.0|1.el7|0|packager|noarch|SIG' + installed_packages = [pkg1, pkg2] + monkeypatch.setattr(removeleftoverpackages, 'get_installed_rpms', lambda: [pkg1]) + + assert removeleftoverpackages._get_removed_packages(installed_packages) == [pkg2] + + +@pytest.mark.parametrize( + ('installed_rpms'), + ( + ([]), + (['rpm1']), + (['rpm1', 'rpm2']), + ) +) +def test_get_removed_packages(monkeypatch, installed_rpms): + rpm_details = { + 'version': '1.0', + 'release': '1.el7', + 'epoch': '0', + 'packager': 'packager', + 'arch': 'noarch', + 'pgpsig': 'SIG' + } + rpm_details_composed = '|'.join([rpm_details[key] for key in ['version', 'release', 'epoch', + 'packager', 'arch', 'pgpsig']]) + mocked_installed_rpms = ['{}|{}'.format(rpm, rpm_details_composed) for rpm in installed_rpms] + + monkeypatch.setattr(removeleftoverpackages, '_get_removed_packages', lambda _: mocked_installed_rpms) + + removed_packages = removeleftoverpackages.get_removed_packages(mocked_installed_rpms) + expected_output = [RPM(name=rpm, **rpm_details) for rpm in installed_rpms] + + assert removed_packages == RemovedPackages(items=expected_output) + + +@pytest.mark.parametrize( + ('removed_packages'), + ( + ([]), + (['rpm1']), + (['rpm1', 'rpm2']), + ) +) +def test_process(monkeypatch, removed_packages): + def get_leftover_pkgs(): + return LeftoverPackages() + + removed_pkgs = RemovedPackages(items=[RPM(name=pkg, version='1.0', release='1.el7', epoch='0', + packager='packager', arch='noarch', pgpsig='SIG') + for pkg in removed_packages]) + + def mocked_get_removed_packages(installed_rpms): + return removed_pkgs + + monkeypatch.setattr(api, 'produce', produce_mocked()) + monkeypatch.setattr(removeleftoverpackages, 'get_leftover_packages', get_leftover_pkgs) + monkeypatch.setattr(removeleftoverpackages, 'get_installed_rpms', lambda: []) + monkeypatch.setattr(removeleftoverpackages, 'run', lambda _: None) + monkeypatch.setattr(removeleftoverpackages, 'skip_rhsm', lambda: False) + monkeypatch.setattr(removeleftoverpackages, 'get_removed_packages', mocked_get_removed_packages) + + removeleftoverpackages.process() + + assert api.produce.called == 1 + assert api.produce.model_instances == [removed_pkgs] diff --git a/repos/system_upgrade/common/actors/reportleftoverpackages/reportleftoverpackages/actor.py b/repos/system_upgrade/common/actors/reportleftoverpackages/reportleftoverpackages/actor.py index 171eab9392..d9d987c2f6 100644 --- a/repos/system_upgrade/common/actors/reportleftoverpackages/reportleftoverpackages/actor.py +++ b/repos/system_upgrade/common/actors/reportleftoverpackages/reportleftoverpackages/actor.py @@ -1,15 +1,16 @@ -from leapp import reporting from leapp.actors import Actor from leapp.models import LeftoverPackages, RemovedPackages -from leapp.reporting import create_report, Report +from leapp.reporting import Report from leapp.tags import IPUWorkflowTag, RPMUpgradePhaseTag +from leapp.libraries.actor import reportleftoverpackages class ReportLeftoverPackages(Actor): """ - Collect messages about leftover el7 packages and generate report for users. + Collect messages about leftover RHEL packages from older major versions and generate report for users. - Depending on execution of previous actors, generated report contains information that there are still el7 packages + Depending on execution of previous actors, + generated report contains information that there are still old RHEL packages present on the system, which makes it unsupported or lists packages that have been removed. """ @@ -19,44 +20,4 @@ class ReportLeftoverPackages(Actor): tags = (RPMUpgradePhaseTag, IPUWorkflowTag) def process(self): - removed_packages = next(self.consume(RemovedPackages), None) - leftover_packages = next(self.consume(LeftoverPackages), LeftoverPackages()) - to_remove = ['-'.join([pkg.name, pkg.version, pkg.release]) for pkg in leftover_packages.items] - - if removed_packages: - title = 'Leftover RHEL 7 packages have been removed' - - if removed_packages.items: - removed = ['-'.join([pkg.name, pkg.version, pkg.release]) for pkg in removed_packages.items] - create_report([ - reporting.Title(title), - reporting.Summary('Following packages have been removed:\n{}'.format('\n'.join(removed))), - reporting.Severity(reporting.Severity.HIGH), - reporting.Groups([reporting.Groups.SANITY]), - ] + [reporting.RelatedResource('package', pkg.name) for pkg in removed_packages.items]) - else: - summary = ('Following packages have been removed:\n' - '{}\n' - 'Dependent packages may have been removed as well, please check that you are not missing ' - 'any packages.\n'.format('\n'.join(to_remove))) - - create_report([ - reporting.Title(title), - reporting.Summary(summary), - reporting.Severity(reporting.Severity.HIGH), - reporting.Groups([reporting.Groups.SANITY]), - ] + [reporting.RelatedResource('package', pkg.name) for pkg in leftover_packages.items]) - return - - if not leftover_packages.items: - self.log.info('No leftover packages, skipping...') - return - - summary = 'Following RHEL 7 packages have not been upgraded:\n{}\n'.format('\n'.join(to_remove)) - summary += 'Please remove these packages to keep your system in supported state.\n' - create_report([ - reporting.Title('Some RHEL 7 packages have not been upgraded'), - reporting.Summary(summary), - reporting.Severity(reporting.Severity.HIGH), - reporting.Groups([reporting.Groups.SANITY]), - ] + [reporting.RelatedResource('package', pkg.name) for pkg in leftover_packages.items]) + reportleftoverpackages.process() diff --git a/repos/system_upgrade/common/actors/reportleftoverpackages/reportleftoverpackages/libraries/reportleftoverpackages.py b/repos/system_upgrade/common/actors/reportleftoverpackages/reportleftoverpackages/libraries/reportleftoverpackages.py new file mode 100644 index 0000000000..666092d5d7 --- /dev/null +++ b/repos/system_upgrade/common/actors/reportleftoverpackages/reportleftoverpackages/libraries/reportleftoverpackages.py @@ -0,0 +1,47 @@ +from leapp import reporting +from leapp.models import LeftoverPackages, RemovedPackages +from leapp.libraries.stdlib import api + + +def process(): + removed_packages = next(api.consume(RemovedPackages), None) + leftover_packages = next(api.consume(LeftoverPackages), LeftoverPackages()) + to_remove = ['-'.join([pkg.name, pkg.version, pkg.release]) for pkg in leftover_packages.items] + + if removed_packages: + title = 'Leftover RHEL packages have been removed' + + if removed_packages.items: + removed = ['-'.join([pkg.name, pkg.version, pkg.release]) for pkg in removed_packages.items] + reporting.create_report([ + reporting.Title(title), + reporting.Summary('Following packages have been removed:\n{}'.format('\n'.join(removed))), + reporting.Severity(reporting.Severity.HIGH), + reporting.Groups([reporting.Groups.SANITY]), + ] + [reporting.RelatedResource('package', pkg.name) for pkg in removed_packages.items]) + else: + summary = ('Following packages have been removed:\n' + '{}\n' + 'Dependent packages may have been removed as well, please check that you are not missing ' + 'any packages.\n'.format('\n'.join(to_remove))) + + reporting.create_report([ + reporting.Title(title), + reporting.Summary(summary), + reporting.Severity(reporting.Severity.HIGH), + reporting.Groups([reporting.Groups.SANITY]), + ] + [reporting.RelatedResource('package', pkg.name) for pkg in leftover_packages.items]) + return + + if not leftover_packages.items: + api.current_logger().info('No leftover packages, skipping...') + return + + summary = 'Following RHEL packages have not been upgraded:\n{}\n'.format('\n'.join(to_remove)) + summary += 'Please remove these packages to keep your system in supported state.\n' + reporting.create_report([ + reporting.Title('Some RHEL packages have not been upgraded'), + reporting.Summary(summary), + reporting.Severity(reporting.Severity.HIGH), + reporting.Groups([reporting.Groups.SANITY]), + ] + [reporting.RelatedResource('package', pkg.name) for pkg in leftover_packages.items]) diff --git a/repos/system_upgrade/common/actors/reportleftoverpackages/reportleftoverpackages/tests/test_reportleftoverpackages.py b/repos/system_upgrade/common/actors/reportleftoverpackages/reportleftoverpackages/tests/test_reportleftoverpackages.py new file mode 100644 index 0000000000..d411692632 --- /dev/null +++ b/repos/system_upgrade/common/actors/reportleftoverpackages/reportleftoverpackages/tests/test_reportleftoverpackages.py @@ -0,0 +1,58 @@ +from leapp.libraries.actor import reportleftoverpackages +from leapp import reporting +from leapp.models import LeftoverPackages, RemovedPackages, RPM +from leapp.libraries.stdlib import api +from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked, logger_mocked + + +def test_no_leftover_and_no_removed_packages(monkeypatch): + monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[])) + monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) + monkeypatch.setattr(api, 'current_logger', logger_mocked()) + reportleftoverpackages.process() + + assert reporting.create_report.called == 0 + assert api.current_logger.infomsg == ['No leftover packages, skipping...'] + + +def test_no_removed_packages(monkeypatch): + leftover_packages = LeftoverPackages(items=[RPM(name='rpm', version='1.0', release='1.el7', epoch='0', + packager='foo', arch='noarch', pgpsig='SIG')]) + monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[leftover_packages])) + # monkeypatch.setattr(reportleftoverpackages, 'create_report', create_report_mocked()) + monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) + monkeypatch.setattr(api, 'current_logger', logger_mocked()) + reportleftoverpackages.process() + + assert reporting.create_report.called == 1 + assert 'Some RHEL packages have not been upgraded' in reporting.create_report.report_fields['title'] + assert 'Following RHEL packages have not been upgraded' in reporting.create_report.report_fields['summary'] + summary = 'Please remove these packages to keep your system in supported state.' + assert summary in reporting.create_report.report_fields['summary'] + + +def test_removed_packages(monkeypatch): + removed_packages = RemovedPackages(items=[RPM(name='rpm', version='1.0', release='1.el7', epoch='0', + packager='foo', arch='noarch', pgpsig='SIG')]) + + monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[removed_packages])) + monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) + reportleftoverpackages.process() + + assert reporting.create_report.called == 1 + assert 'Leftover RHEL packages have been removed' in reporting.create_report.report_fields['title'] + assert 'Following packages have been removed' in reporting.create_report.report_fields['summary'] + assert 'rpm-1.0-1.el7' in reporting.create_report.report_fields['summary'] + + +def test_removed_packages_no_items(monkeypatch): + removed_packages = RemovedPackages() + monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[removed_packages])) + monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) + reportleftoverpackages.process() + + assert reporting.create_report.called == 1 + assert 'Leftover RHEL packages have been removed' in reporting.create_report.report_fields['title'] + assert 'Following packages have been removed' in reporting.create_report.report_fields['summary'] + summary = 'Dependent packages may have been removed as well, please check that you are not missing any packages.' + assert summary in reporting.create_report.report_fields['summary']