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

Upstream subscription code sync #5989

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion anaconda.spec.in
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Source0: https://github.com/rhinstaller/%{name}/releases/download/%{name}-%{vers
%define pythonblivetver 1:3.9.0-1
%define rpmver 4.15.0
%define simplelinever 1.9.0-1
%define subscriptionmanagerver 1.26
%define subscriptionmanagerver 1.29.31
%define utillinuxver 2.15.1
%define rpmostreever 2023.2
%define s390utilscorever 2.31.0
Expand Down
27 changes: 27 additions & 0 deletions pyanaconda/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
InsightsClientMissingError, InsightsConnectError
from pyanaconda.modules.common.errors.payload import SourceSetupError
from pyanaconda.modules.common.errors.storage import UnusableStorageError
from pyanaconda.modules.common.errors.subscription import SatelliteProvisioningError

log = get_module_logger(__name__)

Expand Down Expand Up @@ -111,6 +112,10 @@ def _get_default_mapping(self):
InsightsClientMissingError.__name__: self._insightsErrorHandler,
InsightsConnectError.__name__: self._insightsErrorHandler,
"KickstartRegistrationError": self._kickstartRegistrationErrorHandler,
"SubscriptionTokenTransferError": self._subscriptionTokenTransferErrorHandler,

# Satellite
SatelliteProvisioningError.__name__: self._target_satellite_provisioning_error_handler,

# General installation errors.
NonCriticalInstallationError.__name__: self._non_critical_error_handler,
Expand Down Expand Up @@ -176,6 +181,13 @@ def _bootloader_error_handler(self, exn):
else:
return ERROR_RAISE

def _target_satellite_provisioning_error_handler(self, exn):
message = _("Failed to provision the target system for Satellite.")
details = str(exn)

self.ui.showDetailedError(message, details)
return ERROR_RAISE

def _non_critical_error_handler(self, exn):
message = _("The following error occurred during the installation:"
"\n\n{details}\n\nWould you like to ignore this and "
Expand Down Expand Up @@ -211,6 +223,21 @@ def _kickstartRegistrationErrorHandler(self, exn):
else:
return ERROR_RAISE

def _subscriptionTokenTransferErrorHandler(self, exn):
message = _("Failed to enable Red Hat subscription on the "
"installed system."
"\n\n"
"Your Red Hat subscription might be invalid "
"(such as due to an expired developer subscription)."
"\n\n"
"Would you like to ignore this and continue with "
"installation?")

if self.ui.showYesNoQuestion(message):
return ERROR_CONTINUE
else:
return ERROR_RAISE

def cb(self, exn):
"""This method is the callback that all error handling should pass
through. The return value is one of the ERROR_* constants defined
Expand Down
10 changes: 0 additions & 10 deletions pyanaconda/modules/common/constants/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,16 +145,6 @@
basename="Unregister"
)

RHSM_ATTACH = DBusObjectIdentifier(
namespace=RHSM_NAMESPACE,
basename="Attach"
)

RHSM_ENTITLEMENT = DBusObjectIdentifier(
namespace=RHSM_NAMESPACE,
basename="Entitlement"
)

RHSM_SYSPURPOSE = DBusObjectIdentifier(
namespace=RHSM_NAMESPACE,
basename="Syspurpose"
Expand Down
6 changes: 6 additions & 0 deletions pyanaconda/modules/common/errors/installation.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,9 @@ class InsightsConnectError(InstallationError):
class SubscriptionTokenTransferError(InstallationError):
"""Exception for errors during subscription token transfer."""
pass


@dbus_error("TargetSatelliteProvisioningError", namespace=ANACONDA_NAMESPACE)
class TargetSatelliteProvisioningError(InstallationError):
"""Exception for errors when provisioning target system for Satellite."""
pass
12 changes: 9 additions & 3 deletions pyanaconda/modules/common/errors/subscription.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,13 @@ class UnregistrationError(AnacondaError):
pass


@dbus_error("SubscriptionError", namespace=ANACONDA_NAMESPACE)
class SubscriptionError(AnacondaError):
"""Subscription attempt failed."""
@dbus_error("SatelliteProvisioningError", namespace=ANACONDA_NAMESPACE)
class SatelliteProvisioningError(AnacondaError):
"""Failed to provision the installation environment for Satellite."""
pass


@dbus_error("MultipleOrganizationsError", namespace=ANACONDA_NAMESPACE)
class MultipleOrganizationsError(AnacondaError):
"""Account is member of more than one organization."""
pass
173 changes: 46 additions & 127 deletions pyanaconda/modules/common/structures/subscription.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@

from pyanaconda.modules.common.structures.secret import SecretData, SecretDataList

__all__ = ["SystemPurposeData", "SubscriptionRequest", "AttachedSubscription"]

__all__ = ["SystemPurposeData", "SubscriptionRequest"]

class SystemPurposeData(DBusData):
"""System purpose data."""
Expand Down Expand Up @@ -141,6 +140,7 @@ def __init__(self):
# need to be set
self._organization = ""
self._redhat_account_username = ""
self._redhat_account_organization = ""
# Candlepin instance
self._server_hostname = ""
# CDN base url
Expand Down Expand Up @@ -228,6 +228,27 @@ def account_username(self) -> Str:
def account_username(self, account_username: Str):
self._redhat_account_username = account_username

@property
def account_organization(self) -> Str:
"""Red Hat account organization for subscription purposes.

In case the account for the given username is member
of multiple organizations, organization id needs to
be specified as well or else the registration attempt
will not be successful. This account dependent organization
id is deliberately separate from the org + key org id
to avoid collisions and issues in the GUI when switching
between authentication types.

:return: Red Hat account organization id
:rtype: str
"""
return self._redhat_account_organization

@account_organization.setter
def account_organization(self, account_organization: Str):
self._redhat_account_organization = account_organization

@property
def server_hostname(self) -> Str:
"""Subscription server hostname.
Expand Down Expand Up @@ -392,145 +413,43 @@ def server_proxy_password(self, password: SecretData):
self._server_proxy_password = password


class AttachedSubscription(DBusData):
"""Data for a single attached subscription."""
class OrganizationData(DBusData):
"""Data about a single organization in the Red Hat account system.

A Red Hat account is expected to be member of an organization,
with some accounts being members of more than one organization.
"""

def __init__(self):
self._id = ""
self._name = ""
self._service_level = ""
self._sku = ""
self._contract = ""
self._start_date = ""
self._end_date = ""
# we can expect at least one entitlement
# to be consumed per attached subscription
self._consumed_entitlement_count = 1

@property
def name(self) -> Str:
"""Name of the attached subscription.

Example: "Red Hat Beta Access"

:return: subscription name
:rtype: str
"""
return self._name

@name.setter
def name(self, name: Str):
self._name = name

@property
def service_level(self) -> Str:
"""Service level of the attached subscription.

Example: "Premium"

:return: service level
:rtype: str
"""
return self._service_level

@service_level.setter
def service_level(self, service_level: Str):
self._service_level = service_level

@property
def sku(self) -> Str:
"""SKU id of the attached subscription.
def id(self) -> Str:
"""Id of the organization.

Example: "MBT8547"
Example: "abc123efg456"

:return: SKU id
:rtype: str
"""
return self._sku

@sku.setter
def sku(self, sku: Str):
self._sku = sku

@property
def contract(self) -> Str:
"""Contract identifier.

Example: "32754658"

:return: contract identifier
:rtype: str
"""
return self._contract

@contract.setter
def contract(self, contract: Str):
self._contract = contract

@property
def start_date(self) -> Str:
"""Subscription start date.

We do not guarantee fixed date format,
but we aim for the date to look good
when displayed in a GUI and be human
readable.

For context see the following bug, that
illustrates the issues we are having with
the source date for this property, that
prevent us from providing a consistent
date format:
https://bugzilla.redhat.com/show_bug.cgi?id=1793501

Example: "Nov 04, 2019"

:return: start date of the subscription
:return: organization id
:rtype: str
"""
return self._start_date
return self._id

@start_date.setter
def start_date(self, start_date: Str):
self._start_date = start_date
@id.setter
def id(self, organization_id: Str):
self._id = organization_id

@property
def end_date(self) -> Str:
"""Subscription end date.

We do not guarantee fixed date format,
but we aim for the date to look good
when displayed in a GUI and be human
readable.

For context see the following bug, that
illustrates the issues we are having with
the source date for this property, that
prevent us from providing a consistent
date format:
https://bugzilla.redhat.com/show_bug.cgi?id=1793501
def name(self) -> Str:
"""Name of the organization.

Example: "Nov 04, 2020"
Example: "Foo Organization"

:return: end date of the subscription
:return: organization name
:rtype: str
"""
return self._end_date

@end_date.setter
def end_date(self, end_date: Str):
self._end_date = end_date

@property
def consumed_entitlement_count(self) -> Int:
"""Number of consumed entitlements for this subscription.

Example: "1"

:return: consumed entitlement number
:rtype: int
"""
return self._consumed_entitlement_count
return self._name

@consumed_entitlement_count.setter
def consumed_entitlement_count(self, consumed_entitlement_count: Int):
self._consumed_entitlement_count = consumed_entitlement_count
@name.setter
def name(self, organization_name: Str):
self._name = organization_name
2 changes: 2 additions & 0 deletions pyanaconda/modules/subscription/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@

# name of the RHSM systemd unit
RHSM_SERVICE_NAME = "rhsm.service"
# server hostname prefix marking the URL as not Satellite
SERVER_HOSTNAME_NOT_SATELLITE_PREFIX = "not-satellite:"
57 changes: 57 additions & 0 deletions pyanaconda/modules/subscription/installation.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
from pyanaconda.modules.common.task import Task
from pyanaconda.modules.common.errors.installation import InsightsConnectError, \
InsightsClientMissingError, SubscriptionTokenTransferError
from pyanaconda.modules.common.errors.subscription import SatelliteProvisioningError
from pyanaconda.modules.subscription import satellite

from pyanaconda.anaconda_loggers import get_module_logger
log = get_module_logger(__name__)
Expand Down Expand Up @@ -245,3 +247,58 @@ def run(self):

# transfer the RHSM config file
self._transfer_file(self.RHSM_CONFIG_FILE_PATH, "RHSM config file")


class ProvisionTargetSystemForSatelliteTask(Task):
"""Provision target system for communication with Satellite.

If the System gets registered to Satellite at installation time,
the provisioning is applied only to the installation environment.
This task makes sure it is applied also on the target system.

Run the appropriate Satellite provisioning script on the target system.

This should assure the target system has all the needed certificates
installed and rhsm.conf tweaks applied.
"""

def __init__(self, provisioning_script):
"""Create a new task.

:param str provisioning_script: Satellite provisioning script in string form
"""
super().__init__()
self._provisioning_script = provisioning_script

@property
def name(self):
return "Provisioning target system for Satellite"

def run(self):
"""Provision target system for Satellite.

First check if we are actually registered to a Satellite instance
by checking if we got a provisioning script.

If not, do nothing.

If we are registered to a Satellite instance, run the Satellite
provisioning script that has been downloaded from the instance previously.

"""
if self._provisioning_script:
log.debug("subscription: provisioning target system for Satellite")
provisioning_success = satellite.run_satellite_provisioning_script(
provisioning_script=self._provisioning_script,
run_on_target_system=True

)
if provisioning_success:
log.debug("subscription: target system successfully provisioned for Satellite")
else:
raise SatelliteProvisioningError("Satellite provisioning script failed.")
else:
# lets assume here that no provisioning script == not registered to Satellite
log.debug(
"subscription: not registered to Satellite, skipping Satellite provisioning."
)
Loading