diff --git a/repos/system_upgrade/common/libraries/rhsm.py b/repos/system_upgrade/common/libraries/rhsm.py index 74f6aeb1f3..061a6b7a80 100644 --- a/repos/system_upgrade/common/libraries/rhsm.py +++ b/repos/system_upgrade/common/libraries/rhsm.py @@ -20,6 +20,15 @@ SCA_TEXT = "Content Access Mode is set to Simple Content Access" +_DEFAULT_EXCEPTION_HINT = ( + 'Please ensure you have a valid RHEL subscription and your network is up.' + ' If you are using proxy for Red Hat subscription-manager, please make sure' + ' it is specified inside the /etc/rhsm/rhsm.conf file.' + ' Or use the --no-rhsm option when running leapp, if you do not want to' + ' use subscription-manager for the in-place upgrade and you want to' + ' deliver all target repositories by yourself or using RHUI on public cloud.' +) + def _rhsm_retry(max_attempts, sleep=None): """ @@ -72,20 +81,12 @@ def _handle_rhsm_exceptions(hint=None): } ) except CalledProcessError as e: - _def_hint = ( - 'Please ensure you have a valid RHEL subscription and your network is up.' - ' If you are using proxy for Red Hat subscription-manager, please make sure' - ' it is specified inside the /etc/rhsm/rhsm.conf file.' - ' Or use the --no-rhsm option when running leapp, if you do not want to' - ' use subscription-manager for the in-place upgrade and you want to' - ' deliver all target repositories by yourself or using RHUI on public cloud.' - ) raise StopActorExecutionError( message='A subscription-manager command failed to execute', details={ 'details': str(e), 'stderr': e.stderr, - 'hint': hint or _def_hint, + 'hint': hint or _DEFAULT_EXCEPTION_HINT, 'link': 'https://access.redhat.com/solutions/6138372' } ) @@ -383,6 +384,44 @@ def switch_certificate(context, rhsm_info, cert_path): context.copy_to(cert_path, os.path.join(path, os.path.basename(cert_path))) +def is_rhsm_registered(context): + """ + Check whether the system is registered with Red Hat Subscription Manager + + Note that this doesn't differentiate between SCA and SKU access + + :param context: An instance of a mounting.IsolatedActions class + :type context: mounting.IsolatedActions class + :return: True if it is registered, false otherwise + :rtype: bool + """ + try: + result = context.call(['subscription-manager', 'identity', '--no-progress-messages'], checked=False) + except OSError as e: + api.current_logger().error('Failed to execute subscription-manager executable') + raise StopActorExecutionError( + message='Unable to execute subscription-manager executable: {}'.format(str(e)), + details={ + 'hint': 'Please ensure subscription-manager is installed and executable.' + } + ) + + if result['exit_code'] == 0: + return True + if result['exit_code'] == 1: + return False + raise StopActorExecutionError( + message='A subscription-manager command failed to execute', + details={ + 'details': 'Command \'subscription-manager identity\' exited with exit code: {}'.format( + result["exit_code"] + ), + 'hint': _DEFAULT_EXCEPTION_HINT, + 'link': 'https://access.redhat.com/solutions/6138372' # TODO check link + } + ) + + @with_rhsm def scan_rhsm_info(context): """ @@ -402,4 +441,5 @@ def scan_rhsm_info(context): info.release = get_release(context) info.existing_product_certificates.extend(get_existing_product_certificates(context)) info.sca_detected = is_manifest_sca(context) + info.is_registered = is_rhsm_registered(context) return info diff --git a/repos/system_upgrade/common/libraries/tests/test_rhsm.py b/repos/system_upgrade/common/libraries/tests/test_rhsm.py index 957616f458..980c6bd171 100644 --- a/repos/system_upgrade/common/libraries/tests/test_rhsm.py +++ b/repos/system_upgrade/common/libraries/tests/test_rhsm.py @@ -19,6 +19,7 @@ CMD_RHSM_STATUS = ('subscription-manager', 'status') CMD_RHSM_RELEASE = ('subscription-manager', 'release') CMD_RHSM_LIST_ENABLED_REPOS = ('subscription-manager', 'repos', '--list-enabled') +CMD_RHSM_IDENTITY = ('subscription-manager', 'identity', '--no-progress-messages') RHSM_STATUS_OUTPUT_NOSCA = ''' +-------------------------------------------+ @@ -79,11 +80,13 @@ def call(self, cmd, *args, **dummy_kwargs): tuple(cmd), # Cast to tuple, as list is not hashable self.call_return) - def add_mocked_command_call_with_stdout(self, cmd, stdout): + def add_mocked_command_call(self, cmd, stdout=None, stderr=None, exit_code=0): # We cast `cmd` from list to tuple, as a list cannot be hashed self.mocked_command_call_outputs[tuple(cmd)] = { 'stdout': stdout, - 'stderr': None} + 'stderr': stderr, + 'exit_code': exit_code + } def full_path(self, path): return path @@ -203,7 +206,7 @@ def test_inhibit_on_duplicate_repos_no_dups(monkeypatch): def test_sku_listing(monkeypatch, actor_mocked, context_mocked): """Tests whether the rhsm library can obtain used SKUs correctly.""" - context_mocked.add_mocked_command_call_with_stdout(CMD_RHSM_LIST_CONSUMED, 'SKU: 598339696910') + context_mocked.add_mocked_command_call(CMD_RHSM_LIST_CONSUMED, 'SKU: 598339696910') attached_skus = rhsm.get_attached_skus(context_mocked) @@ -234,7 +237,7 @@ def test_scanrhsminfo_with_skip_rhsm(monkeypatch, context_mocked): def test_get_release(monkeypatch, actor_mocked, context_mocked): """Tests whether the library correctly retrieves release from RHSM.""" - context_mocked.add_mocked_command_call_with_stdout(CMD_RHSM_RELEASE, 'Release: 7.9') + context_mocked.add_mocked_command_call(CMD_RHSM_RELEASE, 'Release: 7.9') release = rhsm.get_release(context_mocked) @@ -245,7 +248,7 @@ def test_get_release(monkeypatch, actor_mocked, context_mocked): def test_get_release_with_release_not_set(monkeypatch, actor_mocked, context_mocked): """Tests whether the library does not retrieve release information when the release is not set.""" # Test whether no release is detected correctly too - context_mocked.add_mocked_command_call_with_stdout(CMD_RHSM_RELEASE, 'Release not set') + context_mocked.add_mocked_command_call(CMD_RHSM_RELEASE, 'Release not set') release = rhsm.get_release(context_mocked) @@ -255,7 +258,7 @@ def test_get_release_with_release_not_set(monkeypatch, actor_mocked, context_moc def test_is_manifest_sca_on_nonsca_system(monkeypatch, actor_mocked, context_mocked): """Tests whether the library obtains the SCA information correctly from a non-SCA system.""" - context_mocked.add_mocked_command_call_with_stdout(CMD_RHSM_STATUS, RHSM_STATUS_OUTPUT_NOSCA) + context_mocked.add_mocked_command_call(CMD_RHSM_STATUS, RHSM_STATUS_OUTPUT_NOSCA) is_sca = rhsm.is_manifest_sca(context_mocked) assert not is_sca, 'SCA was detected on a non-SCA system.' @@ -263,7 +266,7 @@ def test_is_manifest_sca_on_nonsca_system(monkeypatch, actor_mocked, context_moc def test_is_manifest_sca_on_sca_system(monkeypatch, actor_mocked, context_mocked): """Tests whether the library obtains the SCA information from SCA system correctly.""" - context_mocked.add_mocked_command_call_with_stdout(CMD_RHSM_STATUS, RHSM_STATUS_OUTPUT_SCA) + context_mocked.add_mocked_command_call(CMD_RHSM_STATUS, RHSM_STATUS_OUTPUT_SCA) is_sca = rhsm.is_manifest_sca(context_mocked) assert is_sca, 'Failed to detected SCA on a SCA system.' @@ -286,7 +289,7 @@ def test_get_enabled_repo_ids(monkeypatch, actor_mocked, context_mocked): rhsm_output_fragment += '\n' rhsm_list_enabled_output += rhsm_output_fragment - context_mocked.add_mocked_command_call_with_stdout(CMD_RHSM_LIST_ENABLED_REPOS, rhsm_list_enabled_output) + context_mocked.add_mocked_command_call(CMD_RHSM_LIST_ENABLED_REPOS, rhsm_list_enabled_output) enabled_repo_ids = rhsm.get_enabled_repo_ids(context_mocked) @@ -385,3 +388,24 @@ def mocked_listdir(path): assert len(existing_product_certificates) == 1, fail_description fail_description = 'Library failed to identify certificate from mocked outputs.' assert existing_product_certificates[0] == '/etc/pki/product-default/cert', fail_description + + +def test_is_registered_on_registered_system(context_mocked): + """Tests whether the library obtains the registraton status correctly from a registered system.""" + context_mocked.add_mocked_command_call(CMD_RHSM_IDENTITY, exit_code=0) + assert rhsm.is_rhsm_registered(context_mocked) + + +def test_is_registered_on_unregistered_system(context_mocked): + """Tests whether the library obtains the registraton status correctly from an unregistered system.""" + context_mocked.add_mocked_command_call(CMD_RHSM_IDENTITY, exit_code=1) + assert not rhsm.is_rhsm_registered(context_mocked) + + +def test_is_registered_error(context_mocked): + """Tests whether the is_rhsm_registered function correctly handles command errors""" + context_mocked.add_mocked_command_call(CMD_RHSM_IDENTITY, exit_code=2) + with pytest.raises(StopActorExecutionError) as err: + rhsm.is_rhsm_registered(context_mocked) + + assert 'A subscription-manager command failed to execute' in str(err) diff --git a/repos/system_upgrade/common/models/rhsminfo.py b/repos/system_upgrade/common/models/rhsminfo.py index 985b833a1a..6046ee4d3b 100644 --- a/repos/system_upgrade/common/models/rhsminfo.py +++ b/repos/system_upgrade/common/models/rhsminfo.py @@ -20,3 +20,10 @@ class RHSMInfo(Model): """ Product certificates that are currently installed on the system. """ sca_detected = fields.Boolean(default=False) """ Info about whether SCA manifest was used or not. """ + is_registered = fields.Boolean(default=False) + """ + Whether the system is registered through subscription-manager + + Note that this doesn't differentiate between a registration to an SKU or + SCA organization. + """ diff --git a/repos/system_upgrade/el9toel10/actors/nonscarhsm/actor.py b/repos/system_upgrade/el9toel10/actors/nonscarhsm/actor.py new file mode 100644 index 0000000000..d9df6f3e33 --- /dev/null +++ b/repos/system_upgrade/el9toel10/actors/nonscarhsm/actor.py @@ -0,0 +1,24 @@ +from leapp.actors import Actor +from leapp.libraries.actor import nonscarhsm +from leapp.models import Report, RHSMInfo +from leapp.tags import ChecksPhaseTag, IPUWorkflowTag + + +class CheckRedHatSubscriptionManagerSCA(Actor): + """ + Ensure that a registered system is in SCA (Simple Content Access) + + This actor verifies that in case the system is subscribed to the Red Hat + Subscription Manager it is registered to an SCA organization. The actor + will inhibit the upgrade if the system is registered to an entitlements + organization. This actor will run regardless of whether the --skip-rhsm + command line parameter is specified. + """ + + name = 'check_rhsmsca' + consumes = (RHSMInfo,) + produces = (Report,) + tags = (IPUWorkflowTag, ChecksPhaseTag) + + def process(self): + nonscarhsm.process() diff --git a/repos/system_upgrade/el9toel10/actors/nonscarhsm/libraries/nonscarhsm.py b/repos/system_upgrade/el9toel10/actors/nonscarhsm/libraries/nonscarhsm.py new file mode 100644 index 0000000000..d18299151a --- /dev/null +++ b/repos/system_upgrade/el9toel10/actors/nonscarhsm/libraries/nonscarhsm.py @@ -0,0 +1,32 @@ +from leapp import reporting +from leapp.libraries.stdlib import api +from leapp.models import RHSMInfo +from leapp.reporting import create_report + + +def process(): + for info in api.consume(RHSMInfo): + if info.is_registered and not info.sca_detected: + # TODO summary, remediation hint, external link + create_report( + [ + reporting.Title( + "The system is not registered to an SCA organization" + ), + reporting.Summary( + "Leapp detected that the system is registered to an SKU organization." + " On RHEL 10, Red Hat Subscription Manager cannot be registered to an" + " SKU organization." + ), + reporting.Severity(reporting.Severity.HIGH), + reporting.Groups([reporting.Groups.SANITY]), + reporting.Groups([reporting.Groups.INHIBITOR]), + reporting.Remediation( + hint="Register your system with the subscription-manager tool and attach" + " proper SKUs to be able to proceed the upgrade or use the --no-rhsm" + " leapp option if you want to provide target repositories by yourself." + ), + reporting.RelatedResource("package", "subscription-manager"), + reporting.ExternalLink(url="", title=""), + ] + )