Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Inhibit upgrades with non-SCA registration #1333

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 49 additions & 9 deletions repos/system_upgrade/common/libraries/rhsm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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'
}
)
Expand Down Expand Up @@ -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):
"""
Expand All @@ -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
40 changes: 32 additions & 8 deletions repos/system_upgrade/common/libraries/tests/test_rhsm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '''
+-------------------------------------------+
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand All @@ -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)

Expand All @@ -255,15 +258,15 @@ 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.'


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.'
Expand All @@ -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)

Expand Down Expand Up @@ -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)
7 changes: 7 additions & 0 deletions repos/system_upgrade/common/models/rhsminfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
24 changes: 24 additions & 0 deletions repos/system_upgrade/el9toel10/actors/nonscarhsm/actor.py
Original file line number Diff line number Diff line change
@@ -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()
Original file line number Diff line number Diff line change
@@ -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=""),
]
)
Loading