591 lines
25 KiB
Python
591 lines
25 KiB
Python
|
#
|
||
|
# Copyright (C) 2020 Red Hat, Inc.
|
||
|
#
|
||
|
# This copyrighted material is made available to anyone wishing to use,
|
||
|
# modify, copy, or redistribute it subject to the terms and conditions of
|
||
|
# the GNU General Public License v.2, or (at your option) any later version.
|
||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||
|
# ANY WARRANTY expressed or implied, including the implied warranties of
|
||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||
|
# Public License for more details. You should have received a copy of the
|
||
|
# GNU General Public License along with this program; if not, write to the
|
||
|
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||
|
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
|
||
|
# source code or documentation are not subject to the GNU General Public
|
||
|
# License and may only be used or replicated with the express permission of
|
||
|
# Red Hat, Inc.
|
||
|
#
|
||
|
import os
|
||
|
import json
|
||
|
import datetime
|
||
|
from collections import namedtuple
|
||
|
|
||
|
from dasbus.typing import get_variant, Str
|
||
|
from dasbus.connection import MessageBus
|
||
|
from dasbus.error import DBusError
|
||
|
|
||
|
from pyanaconda.core.i18n import _
|
||
|
from pyanaconda.modules.common.task import Task
|
||
|
from pyanaconda.modules.common.constants.services import RHSM
|
||
|
from pyanaconda.modules.common.constants.objects import RHSM_REGISTER
|
||
|
from pyanaconda.modules.common.errors.subscription import RegistrationError, \
|
||
|
UnregistrationError, SubscriptionError
|
||
|
from pyanaconda.modules.common.structures.subscription import AttachedSubscription, \
|
||
|
SystemPurposeData
|
||
|
from pyanaconda.modules.subscription import system_purpose
|
||
|
from pyanaconda.modules.subscription.constants import SERVER_HOSTNAME_NOT_SATELLITE_PREFIX
|
||
|
from pyanaconda.anaconda_loggers import get_module_logger
|
||
|
|
||
|
import gi
|
||
|
gi.require_version("Gio", "2.0")
|
||
|
from gi.repository import Gio
|
||
|
|
||
|
log = get_module_logger(__name__)
|
||
|
|
||
|
|
||
|
class RHSMPrivateBus(MessageBus):
|
||
|
"""Representation of RHSM private bus connection that can be used as a context manager."""
|
||
|
|
||
|
def __init__(self, rhsm_register_server_proxy, *args, **kwargs):
|
||
|
"""Representation of RHSM private bus connection that can be used as a context manager.
|
||
|
|
||
|
:param rhsm_register_server_proxy: DBus proxy for the RHSM RegisterServer object
|
||
|
"""
|
||
|
super().__init__(*args, **kwargs)
|
||
|
self._rhsm_register_server_proxy = rhsm_register_server_proxy
|
||
|
self._private_bus_address = None
|
||
|
|
||
|
def __enter__(self):
|
||
|
log.debug("subscription: starting RHSM private DBus session")
|
||
|
locale = os.environ.get("LANG", "")
|
||
|
self._private_bus_address = self._rhsm_register_server_proxy.Start(locale)
|
||
|
log.debug("subscription: RHSM private DBus session has been started")
|
||
|
return self
|
||
|
|
||
|
def __exit__(self, exc_type, exc_value, exc_traceback):
|
||
|
log.debug("subscription: shutting down the RHSM private DBus session")
|
||
|
self.disconnect()
|
||
|
locale = os.environ.get("LANG", "")
|
||
|
self._rhsm_register_server_proxy.Stop(locale)
|
||
|
log.debug("subscription: RHSM private DBus session has been shutdown")
|
||
|
|
||
|
def _get_connection(self):
|
||
|
"""Get a connection to RHSM private DBus session."""
|
||
|
# the RHSM private bus address is potentially sensitive
|
||
|
# so we will not log it
|
||
|
log.info("Connecting to the RHSM private DBus session.")
|
||
|
return self._provider.get_addressed_bus_connection(
|
||
|
bus_address=self._private_bus_address,
|
||
|
flags=Gio.DBusConnectionFlags.AUTHENTICATION_CLIENT
|
||
|
)
|
||
|
|
||
|
|
||
|
SystemSubscriptionData = namedtuple("SystemSubscriptionData",
|
||
|
["attached_subscriptions", "system_purpose_data"])
|
||
|
|
||
|
|
||
|
class SystemPurposeConfigurationTask(Task):
|
||
|
"""Runtime task for setting system purpose."""
|
||
|
|
||
|
def __init__(self, rhsm_syspurpose_proxy, system_purpose_data):
|
||
|
"""Create a new system purpose configuration task.
|
||
|
|
||
|
:param rhsm_syspurpose_proxy: DBus proxy for the RHSM Syspurpose object
|
||
|
:param system_purpose_data: system purpose data DBus structure
|
||
|
:type system_purpose_data: DBusData instance
|
||
|
"""
|
||
|
super().__init__()
|
||
|
self._rhsm_syspurpose_proxy = rhsm_syspurpose_proxy
|
||
|
self._system_purpose_data = system_purpose_data
|
||
|
|
||
|
@property
|
||
|
def name(self):
|
||
|
return "Set system purpose"
|
||
|
|
||
|
def run(self):
|
||
|
# the task is always expected to run in the installation environment
|
||
|
# - if existing data is present, it will be cleared and then
|
||
|
# replaced by new data
|
||
|
return system_purpose.give_the_system_purpose(
|
||
|
sysroot="/",
|
||
|
rhsm_syspurpose_proxy=self._rhsm_syspurpose_proxy,
|
||
|
role=self._system_purpose_data.role,
|
||
|
sla=self._system_purpose_data.sla,
|
||
|
usage=self._system_purpose_data.usage,
|
||
|
addons=self._system_purpose_data.addons
|
||
|
)
|
||
|
|
||
|
|
||
|
class SetRHSMConfigurationTask(Task):
|
||
|
"""Task for setting configuration to the RHSM service.
|
||
|
|
||
|
Set configuration options of the RHSM service via it's
|
||
|
DBus interface, based on the provided SubscriptionRequest
|
||
|
structure.
|
||
|
|
||
|
Also in case one of the configuration options was unset,
|
||
|
restore the key to its original value. This way for example
|
||
|
a user decides at runtime to use the default server hostname
|
||
|
or RHSM baseurl, they can just delete the value in the UI,
|
||
|
triggering the original value to be restored when we encounter
|
||
|
the empty value for a key that originally was set to
|
||
|
a non empty value.
|
||
|
"""
|
||
|
|
||
|
# Keys in the RHSM config key/value store we care about and
|
||
|
# should be able to restore to original value.
|
||
|
#
|
||
|
# NOTE: These keys map 1:1 to rhsm.conf. To see what they do the
|
||
|
# best bet is to check the /etc/rhsm/rhsm.conf file on a system
|
||
|
# with the subscription-manager package installed. The file
|
||
|
# is heavily documented with comment's explaining what
|
||
|
# the different keys do.
|
||
|
CONFIG_KEY_SERVER_HOSTNAME = "server.hostname"
|
||
|
CONFIG_KEY_SERVER_PROXY_HOSTNAME = "server.proxy_hostname"
|
||
|
CONFIG_KEY_SERVER_PROXY_PORT = "server.proxy_port"
|
||
|
CONFIG_KEY_SERVER_PROXY_USER = "server.proxy_user"
|
||
|
CONFIG_KEY_SERVER_PROXY_PASSWORD = "server.proxy_password"
|
||
|
CONFIG_KEY_RHSM_BASEURL = "rhsm.baseurl"
|
||
|
|
||
|
def __init__(self, rhsm_config_proxy, rhsm_config_defaults, subscription_request):
|
||
|
"""Create a new task for setting RHSM configuration.
|
||
|
|
||
|
:param rhsm_config_proxy: DBus proxy for the RHSM Config object
|
||
|
:param dict rhsm_config_defaults: a dictionary of original RHSM configuration values
|
||
|
:param subscription_request: subscription request DBus Structure
|
||
|
:type subscription_request: SubscriptionRequest instance
|
||
|
"""
|
||
|
super().__init__()
|
||
|
self._rhsm_config_proxy = rhsm_config_proxy
|
||
|
self._request = subscription_request
|
||
|
self._rhsm_config_defaults = rhsm_config_defaults
|
||
|
|
||
|
@property
|
||
|
def name(self):
|
||
|
return "Set RHSM configuration."
|
||
|
|
||
|
def run(self):
|
||
|
log.debug("subscription: setting RHSM config values")
|
||
|
# We will use the SetAll() dbus method and we need to
|
||
|
# assemble a dictionary that we will feed to it.
|
||
|
# Start by preparing a SubscriptionData property mapping
|
||
|
# to the RHSM config keys.
|
||
|
#
|
||
|
# A note about constructing the dict:
|
||
|
# - DBus API needs all values to be strings, so we need to convert the
|
||
|
# port number to string
|
||
|
# - all values need to be string variants
|
||
|
# - proxy password is stored in SecretData instance and we need to retrieve
|
||
|
# its value
|
||
|
# - server host name might have a prefix indicating the given URL is not
|
||
|
# a Satellite URL, drop that prefix before setting the value to RHSM
|
||
|
|
||
|
# drop the not-satellite prefix, if any
|
||
|
server_hostname = self._request.server_hostname.removeprefix(
|
||
|
SERVER_HOSTNAME_NOT_SATELLITE_PREFIX
|
||
|
)
|
||
|
property_key_map = {
|
||
|
self.CONFIG_KEY_SERVER_HOSTNAME: server_hostname,
|
||
|
self.CONFIG_KEY_SERVER_PROXY_HOSTNAME: self._request.server_proxy_hostname,
|
||
|
self.CONFIG_KEY_SERVER_PROXY_PORT: str(self._request.server_proxy_port),
|
||
|
self.CONFIG_KEY_SERVER_PROXY_USER: self._request.server_proxy_user,
|
||
|
self.CONFIG_KEY_SERVER_PROXY_PASSWORD: self._request.server_proxy_password.value,
|
||
|
self.CONFIG_KEY_RHSM_BASEURL: self._request.rhsm_baseurl
|
||
|
}
|
||
|
|
||
|
# Then process the mapping into the final dict we will set to RHSM. This includes
|
||
|
# checking if some values have been cleared by the user and should be restored to
|
||
|
# the original values that have been in the RHSM config before we started
|
||
|
# manipulating it.
|
||
|
#
|
||
|
# Also the RHSM DBus API requires a dict of variants, so we need to provide
|
||
|
# that as well.
|
||
|
config_dict = {}
|
||
|
for key, value in property_key_map.items():
|
||
|
if value:
|
||
|
# if value is present in request, use it
|
||
|
config_dict[key] = get_variant(Str, value)
|
||
|
else:
|
||
|
# if no value is present in request, use
|
||
|
# value from the original RHSM config state
|
||
|
# (if any)
|
||
|
log.debug("subscription: restoring original value for RHSM config key %s", key)
|
||
|
config_dict[key] = get_variant(Str, self._rhsm_config_defaults.get(key, ""))
|
||
|
|
||
|
# and finally set the dict to RHSM via the DBus API
|
||
|
self._rhsm_config_proxy.SetAll(config_dict, "")
|
||
|
|
||
|
|
||
|
class RegisterWithUsernamePasswordTask(Task):
|
||
|
"""Register the system via username + password."""
|
||
|
|
||
|
def __init__(self, rhsm_register_server_proxy, username, password):
|
||
|
"""Create a new registration task.
|
||
|
|
||
|
It is assumed the username and password have been
|
||
|
validated before this task has been started.
|
||
|
|
||
|
:param rhsm_register_server_proxy: DBus proxy for the RHSM RegisterServer object
|
||
|
:param str username: Red Hat account username
|
||
|
:param str password: Red Hat account password
|
||
|
"""
|
||
|
super().__init__()
|
||
|
self._rhsm_register_server_proxy = rhsm_register_server_proxy
|
||
|
self._username = username
|
||
|
self._password = password
|
||
|
|
||
|
@property
|
||
|
def name(self):
|
||
|
return "Register with Red Hat account username and password"
|
||
|
|
||
|
def run(self):
|
||
|
"""Register the system with Red Hat account username and password.
|
||
|
|
||
|
:raises: RegistrationError if calling the RHSM DBus API returns an error
|
||
|
"""
|
||
|
log.debug("subscription: registering with username and password")
|
||
|
with RHSMPrivateBus(self._rhsm_register_server_proxy) as private_bus:
|
||
|
try:
|
||
|
locale = os.environ.get("LANG", "")
|
||
|
private_register_proxy = private_bus.get_proxy(RHSM.service_name,
|
||
|
RHSM_REGISTER.object_path)
|
||
|
# We do not yet support setting organization for username & password
|
||
|
# registration, so organization is blank for now.
|
||
|
organization = ""
|
||
|
private_register_proxy.Register(organization,
|
||
|
self._username,
|
||
|
self._password,
|
||
|
{},
|
||
|
{},
|
||
|
locale)
|
||
|
log.debug("subscription: registered with username and password")
|
||
|
except DBusError as e:
|
||
|
log.debug("subscription: failed to register with username and password: %s",
|
||
|
str(e))
|
||
|
# RHSM exception contain details as JSON due to DBus exception handling limitations
|
||
|
exception_dict = json.loads(str(e))
|
||
|
# return a generic error message in case the RHSM provided error message is missing
|
||
|
message = exception_dict.get("message", _("Registration failed."))
|
||
|
raise RegistrationError(message) from None
|
||
|
|
||
|
|
||
|
class RegisterWithOrganizationKeyTask(Task):
|
||
|
"""Register the system via organization and one or more activation keys."""
|
||
|
|
||
|
def __init__(self, rhsm_register_server_proxy, organization, activation_keys):
|
||
|
"""Create a new registration task.
|
||
|
|
||
|
:param rhsm_register_server_proxy: DBus proxy for the RHSM RegisterServer object
|
||
|
:param str organization: organization name for subscription purposes
|
||
|
:param activation keys: activation keys
|
||
|
:type activation_keys: list of str
|
||
|
"""
|
||
|
super().__init__()
|
||
|
self._rhsm_register_server_proxy = rhsm_register_server_proxy
|
||
|
self._organization = organization
|
||
|
self._activation_keys = activation_keys
|
||
|
|
||
|
@property
|
||
|
def name(self):
|
||
|
return "Register with organization name and activation key"
|
||
|
|
||
|
def run(self):
|
||
|
"""Register the system with organization name and activation key.
|
||
|
|
||
|
:raises: RegistrationError if calling the RHSM DBus API returns an error
|
||
|
"""
|
||
|
log.debug("subscription: registering with organization and activation key")
|
||
|
with RHSMPrivateBus(self._rhsm_register_server_proxy) as private_bus:
|
||
|
try:
|
||
|
locale = os.environ.get("LANG", "")
|
||
|
private_register_proxy = private_bus.get_proxy(RHSM.service_name,
|
||
|
RHSM_REGISTER.object_path)
|
||
|
private_register_proxy.RegisterWithActivationKeys(self._organization,
|
||
|
self._activation_keys,
|
||
|
{},
|
||
|
{},
|
||
|
locale)
|
||
|
log.debug("subscription: registered with organization and activation key")
|
||
|
except DBusError as e:
|
||
|
log.debug("subscription: failed to register with organization & key: %s", str(e))
|
||
|
# RHSM exception contain details as JSON due to DBus exception handling limitations
|
||
|
exception_dict = json.loads(str(e))
|
||
|
# return a generic error message in case the RHSM provided error message is missing
|
||
|
message = exception_dict.get("message", _("Registration failed."))
|
||
|
raise RegistrationError(message) from None
|
||
|
|
||
|
|
||
|
class UnregisterTask(Task):
|
||
|
"""Unregister the system."""
|
||
|
|
||
|
def __init__(self, rhsm_unregister_proxy):
|
||
|
"""Create a new unregistration task.
|
||
|
|
||
|
:param rhsm_unregister_proxy: DBus proxy for the RHSM Unregister object
|
||
|
"""
|
||
|
super().__init__()
|
||
|
self._rhsm_unregister_proxy = rhsm_unregister_proxy
|
||
|
|
||
|
@property
|
||
|
def name(self):
|
||
|
return "Unregister the system"
|
||
|
|
||
|
def run(self):
|
||
|
"""Unregister the system."""
|
||
|
log.debug("subscription: unregistering the system")
|
||
|
try:
|
||
|
locale = os.environ.get("LANG", "")
|
||
|
self._rhsm_unregister_proxy.Unregister({}, locale)
|
||
|
log.debug("subscription: the system has been unregistered")
|
||
|
except DBusError as e:
|
||
|
log.exception("subscription: failed to unregister: %s", str(e))
|
||
|
exception_dict = json.loads(str(e))
|
||
|
# return a generic error message in case the RHSM provided error message
|
||
|
# is missing
|
||
|
message = exception_dict.get("message", _("Unregistration failed."))
|
||
|
raise UnregistrationError(message) from None
|
||
|
|
||
|
|
||
|
class AttachSubscriptionTask(Task):
|
||
|
"""Attach a subscription."""
|
||
|
|
||
|
def __init__(self, rhsm_attach_proxy, sla):
|
||
|
"""Create a new subscription task.
|
||
|
|
||
|
:param rhsm_attach_proxy: DBus proxy for the RHSM Attach object
|
||
|
:param str sla: organization name for subscription purposes
|
||
|
"""
|
||
|
super().__init__()
|
||
|
self._rhsm_attach_proxy = rhsm_attach_proxy
|
||
|
self._sla = sla
|
||
|
|
||
|
@property
|
||
|
def name(self):
|
||
|
return "Attach a subscription"
|
||
|
|
||
|
def run(self):
|
||
|
"""Attach a subscription to the installation environment.
|
||
|
|
||
|
This subscription will be used for CDN access during the
|
||
|
installation and then transferred to the target system
|
||
|
via separate DBus task.
|
||
|
|
||
|
:raises: SubscriptionError if RHSM API DBus call fails
|
||
|
"""
|
||
|
log.debug("subscription: auto-attaching a subscription")
|
||
|
try:
|
||
|
locale = os.environ.get("LANG", "")
|
||
|
self._rhsm_attach_proxy.AutoAttach(self._sla, {}, locale)
|
||
|
log.debug("subscription: auto-attached a subscription")
|
||
|
except DBusError as e:
|
||
|
log.debug("subscription: auto-attach failed: %s", str(e))
|
||
|
exception_dict = json.loads(str(e))
|
||
|
# return a generic error message in case the RHSM provided error message
|
||
|
# is missing
|
||
|
message = exception_dict.get("message", _("Failed to attach subscription."))
|
||
|
raise SubscriptionError(message) from None
|
||
|
|
||
|
|
||
|
class ParseAttachedSubscriptionsTask(Task):
|
||
|
"""Parse data about subscriptions attached to the installation environment."""
|
||
|
|
||
|
def __init__(self, rhsm_entitlement_proxy, rhsm_syspurpose_proxy):
|
||
|
"""Create a new attached subscriptions parsing task.
|
||
|
|
||
|
:param rhsm_entitlement_proxy: DBus proxy for the RHSM Entitlement object
|
||
|
:param rhsm_syspurpose_proxy: DBus proxy for the RHSM Syspurpose object
|
||
|
"""
|
||
|
super().__init__()
|
||
|
self._rhsm_entitlement_proxy = rhsm_entitlement_proxy
|
||
|
self._rhsm_syspurpose_proxy = rhsm_syspurpose_proxy
|
||
|
|
||
|
@property
|
||
|
def name(self):
|
||
|
return "Parse attached subscription data"
|
||
|
|
||
|
@staticmethod
|
||
|
def _pretty_date(date_from_json):
|
||
|
"""Return pretty human readable date based on date from the input JSON."""
|
||
|
# fallback in case of the parsing fails
|
||
|
date_string = date_from_json
|
||
|
# try to parse the date as ISO 8601 first
|
||
|
try:
|
||
|
date = datetime.datetime.strptime(date_from_json, "%Y-%m-%d")
|
||
|
# get a nice human readable date
|
||
|
return date.strftime("%b %d, %Y")
|
||
|
except ValueError:
|
||
|
pass
|
||
|
try:
|
||
|
# The start/end date in GetPools() output seems to be formatted as
|
||
|
# "Locale's appropriate date representation.".
|
||
|
# See bug 1793501 for possible issues with RHSM provided date parsing.
|
||
|
date = datetime.datetime.strptime(date_from_json, "%m/%d/%y")
|
||
|
# get a nice human readable date
|
||
|
date_string = date.strftime("%b %d, %Y")
|
||
|
except ValueError:
|
||
|
log.warning("subscription: date parsing failed: %s", date_from_json)
|
||
|
return date_string
|
||
|
|
||
|
@classmethod
|
||
|
def _parse_subscription_json(cls, subscription_json):
|
||
|
"""Parse the JSON into list of AttachedSubscription instances.
|
||
|
|
||
|
The expected JSON is at top level a list of rather complex dictionaries,
|
||
|
with each dictionary describing a single subscription that has been attached
|
||
|
to the system.
|
||
|
|
||
|
:param str subscription_json: JSON describing what subscriptions have been attached
|
||
|
:return: list of attached subscriptions
|
||
|
:rtype: list of AttachedSubscription instances
|
||
|
"""
|
||
|
attached_subscriptions = []
|
||
|
try:
|
||
|
subscriptions = json.loads(subscription_json)
|
||
|
except json.decoder.JSONDecodeError:
|
||
|
log.warning("subscription: failed to parse GetPools() JSON output")
|
||
|
# empty attached subscription list is better than an installation
|
||
|
# ending crash
|
||
|
return []
|
||
|
# find the list of subscriptions
|
||
|
consumed_subscriptions = subscriptions.get("consumed", [])
|
||
|
log.debug("subscription: parsing %d attached subscriptions",
|
||
|
len(consumed_subscriptions))
|
||
|
# split the list of subscriptions into separate subscription dictionaries
|
||
|
for subscription_info in consumed_subscriptions:
|
||
|
attached_subscription = AttachedSubscription()
|
||
|
# user visible product name
|
||
|
attached_subscription.name = subscription_info.get(
|
||
|
"subscription_name",
|
||
|
_("product name unknown")
|
||
|
)
|
||
|
|
||
|
# subscription support level
|
||
|
# - this does *not* seem to directly correlate to system purpose SLA attribute
|
||
|
attached_subscription.service_level = subscription_info.get(
|
||
|
"service_level",
|
||
|
_("unknown")
|
||
|
)
|
||
|
|
||
|
# SKU
|
||
|
# - looks like productId == SKU in this JSON output
|
||
|
attached_subscription.sku = subscription_info.get(
|
||
|
"sku",
|
||
|
_("unknown")
|
||
|
)
|
||
|
|
||
|
# contract number
|
||
|
attached_subscription.contract = subscription_info.get(
|
||
|
"contract",
|
||
|
_("not available")
|
||
|
)
|
||
|
|
||
|
# subscription start date
|
||
|
# - convert the raw date data from JSON to something more readable
|
||
|
start_date = subscription_info.get(
|
||
|
"starts",
|
||
|
_("unknown")
|
||
|
)
|
||
|
attached_subscription.start_date = cls._pretty_date(start_date)
|
||
|
|
||
|
# subscription end date
|
||
|
# - convert the raw date data from JSON to something more readable
|
||
|
end_date = subscription_info.get(
|
||
|
"ends",
|
||
|
_("unknown")
|
||
|
)
|
||
|
attached_subscription.end_date = cls._pretty_date(end_date)
|
||
|
|
||
|
# consumed entitlements
|
||
|
# - this seems to correspond to the toplevel "quantity" key,
|
||
|
# not to the pool-level "consumed" key for some reason
|
||
|
# *or* the pool-level "quantity" key
|
||
|
quantity_string = int(subscription_info.get("quantity_used", 1))
|
||
|
attached_subscription.consumed_entitlement_count = quantity_string
|
||
|
# add attached subscription to the list
|
||
|
attached_subscriptions.append(attached_subscription)
|
||
|
# return the list of attached subscriptions
|
||
|
return attached_subscriptions
|
||
|
|
||
|
@staticmethod
|
||
|
def _parse_system_purpose_json(final_syspurpose_json):
|
||
|
"""Parse the JSON into a SystemPurposeData instance.
|
||
|
|
||
|
The expected JSON is a simple three key dictionary listing the final
|
||
|
System Purpose state after subscription/subscriptions have been attached.
|
||
|
|
||
|
:param str final_syspurpose_json: JSON describing final syspurpose state
|
||
|
:return: final system purpose data
|
||
|
:rtype: SystemPurposeData instance
|
||
|
"""
|
||
|
system_purpose_data = SystemPurposeData()
|
||
|
|
||
|
try:
|
||
|
syspurpose_json = json.loads(final_syspurpose_json)
|
||
|
except json.decoder.JSONDecodeError:
|
||
|
log.warning("subscription: failed to parse GetSyspurpose() JSON output")
|
||
|
# empty system purpose data is better than an installation ending crash
|
||
|
return system_purpose_data
|
||
|
|
||
|
system_purpose_data.role = syspurpose_json.get(
|
||
|
"role",
|
||
|
""
|
||
|
)
|
||
|
system_purpose_data.sla = syspurpose_json.get(
|
||
|
"service_level_agreement",
|
||
|
""
|
||
|
)
|
||
|
system_purpose_data.usage = syspurpose_json.get(
|
||
|
"usage",
|
||
|
""
|
||
|
)
|
||
|
system_purpose_data.addons = syspurpose_json.get(
|
||
|
"addons",
|
||
|
[]
|
||
|
)
|
||
|
return system_purpose_data
|
||
|
|
||
|
def run(self):
|
||
|
"""Get data from RHSM describing what subscriptions have been attached to the system.
|
||
|
|
||
|
Calling the AutoAttach() over RHSM DBus API also generally returns such data,
|
||
|
but due to bug 1790924, we can't depend on it always being the case.
|
||
|
|
||
|
Therefore, we query subscription state separately using the GetPools() method.
|
||
|
|
||
|
We also retrieve system purpose data from the system, as registration that
|
||
|
uses an activation key with custom system purpose value attached, can result
|
||
|
in system purpose data being different after registration.
|
||
|
"""
|
||
|
locale = os.environ.get("LANG", "")
|
||
|
# fetch subscription status data
|
||
|
subscription_json = self._rhsm_entitlement_proxy.GetPools(
|
||
|
{"pool_subsets": get_variant(Str, "consumed")},
|
||
|
{},
|
||
|
locale
|
||
|
)
|
||
|
subscription_data_length = 0
|
||
|
# Log how much subscription data we got for debugging purposes.
|
||
|
# By only logging length, we should be able to debug cases of no
|
||
|
# or incomplete data being logged, without logging potentially
|
||
|
# sensitive subscription status detail into the installation logs
|
||
|
# stored on the target system.
|
||
|
if subscription_json:
|
||
|
subscription_data_length = len(subscription_json)
|
||
|
log.debug("subscription: fetched subscription status data: %d characters",
|
||
|
subscription_data_length)
|
||
|
else:
|
||
|
log.warning("subscription: fetched empty subscription status data")
|
||
|
|
||
|
# fetch final system purpose data
|
||
|
log.debug("subscription: fetching final syspurpose data")
|
||
|
final_syspurpose_json = self._rhsm_syspurpose_proxy.GetSyspurpose(locale)
|
||
|
log.debug("subscription: final syspurpose data: %s", final_syspurpose_json)
|
||
|
|
||
|
# parse the JSON strings
|
||
|
attached_subscriptions = self._parse_subscription_json(subscription_json)
|
||
|
system_purpose_data = self._parse_system_purpose_json(final_syspurpose_json)
|
||
|
|
||
|
# return the DBus structures as a named tuple
|
||
|
return SystemSubscriptionData(attached_subscriptions=attached_subscriptions,
|
||
|
system_purpose_data=system_purpose_data)
|