From d29d9a36baedf1622501fdb4b56aad2312e2dff8 Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Tue, 7 Nov 2023 02:28:18 -0800 Subject: [PATCH] Initial work to demonstrate an actor config framework. --- .../actor.py | 6 +- .../rpmtransactionconfigtaskscollector.py | 8 +- .../system_upgrade/common/configs/__init__.py | 126 ++++++++++++++++++ .../rpmtransactionconfigtaskscollector.py | 44 ++++++ 4 files changed, 178 insertions(+), 6 deletions(-) create mode 100644 repos/system_upgrade/common/configs/__init__.py create mode 100644 repos/system_upgrade/common/configs/rpmtransactionconfigtaskscollector.py diff --git a/repos/system_upgrade/common/actors/rpmtransactionconfigtaskscollector/actor.py b/repos/system_upgrade/common/actors/rpmtransactionconfigtaskscollector/actor.py index 4ef726f5f4..232a8f7275 100644 --- a/repos/system_upgrade/common/actors/rpmtransactionconfigtaskscollector/actor.py +++ b/repos/system_upgrade/common/actors/rpmtransactionconfigtaskscollector/actor.py @@ -1,8 +1,10 @@ from leapp.actors import Actor +from leapp.configs import Transaction_ToInstall, Transaction_ToKeep, Transaction_ToRemove from leapp.libraries.actor.rpmtransactionconfigtaskscollector import load_tasks from leapp.models import InstalledRedHatSignedRPM, RpmTransactionTasks from leapp.tags import FactsPhaseTag, IPUWorkflowTag + CONFIGURATION_BASE_PATH = '/etc/leapp/transaction' @@ -13,11 +15,11 @@ class RpmTransactionConfigTasksCollector(Actor): After collecting task data from /etc/leapp/transaction directory, a message with relevant data will be produced. """ - + configs = (Transaction_ToInstall, Transaction_ToKeep, Transaction_ToRemove) name = 'rpm_transaction_config_tasks_collector' consumes = (InstalledRedHatSignedRPM,) produces = (RpmTransactionTasks,) tags = (FactsPhaseTag, IPUWorkflowTag) def process(self): - self.produce(load_tasks(CONFIGURATION_BASE_PATH, self.log)) + self.produce(load_tasks(self.config, self.log)) diff --git a/repos/system_upgrade/common/actors/rpmtransactionconfigtaskscollector/libraries/rpmtransactionconfigtaskscollector.py b/repos/system_upgrade/common/actors/rpmtransactionconfigtaskscollector/libraries/rpmtransactionconfigtaskscollector.py index fb6ae8ff44..4c37b70924 100644 --- a/repos/system_upgrade/common/actors/rpmtransactionconfigtaskscollector/libraries/rpmtransactionconfigtaskscollector.py +++ b/repos/system_upgrade/common/actors/rpmtransactionconfigtaskscollector/libraries/rpmtransactionconfigtaskscollector.py @@ -18,11 +18,11 @@ def load_tasks_file(path, logger): return [] -def load_tasks(base_dir, logger): +def load_tasks(config, logger): # Loads configuration files to_install, to_keep, and to_remove from the given base directory rpms = next(api.consume(InstalledRedHatSignedRPM)) rpm_names = [rpm.name for rpm in rpms.items] - to_install = load_tasks_file(os.path.join(base_dir, 'to_install'), logger) + to_install = config['transaction']['to_install'] # we do not want to put into rpm transaction what is already installed (it will go to "to_upgrade" bucket) to_install_filtered = [pkg for pkg in to_install if pkg not in rpm_names] @@ -34,5 +34,5 @@ def load_tasks(base_dir, logger): return RpmTransactionTasks( to_install=to_install_filtered, - to_keep=load_tasks_file(os.path.join(base_dir, 'to_keep'), logger), - to_remove=load_tasks_file(os.path.join(base_dir, 'to_remove'), logger)) + to_keep=config['transaction']['to_keep'], + to_remove=config['transaction']['to_remove']) diff --git a/repos/system_upgrade/common/configs/__init__.py b/repos/system_upgrade/common/configs/__init__.py new file mode 100644 index 0000000000..21edf6d330 --- /dev/null +++ b/repos/system_upgrade/common/configs/__init__.py @@ -0,0 +1,126 @@ +# This is code that needs to go in the leapp framework. +# Putting it here for now so that everything is in one git repo. +__metaclass__ = type + + +import abc +import six + +import yaml +try: + # Compiled versions if available, for speed + from yaml import CSafeLoader as SafeLoader, CSafeDumper as SafeDumper +except ImportError: + from yaml import SafeLoader, SafeDumper + + +@six.add_metaclass(abc.ABCMeta) +class Config: + @abc.abstractproperty + def section(): + pass + + @abc.abstractproperty + def name(): + pass + + @abc.abstractproperty + def type_(): + pass + + @abc.abstractproperty + def description(): + pass + + @classmethod + def to_dict(cls): + """ + Return a dictionary representation of the config item that would be suitable for putting + into a config file. + """ + representation = { + cls.section: { + '{0}_description__'.format(cls.name): cls.description + } + } + if cls.default is not None: + representation[cls.section][cls.name] = cls.default + + return representation + + +def parse_system_config_files(config_dir='/etc/leapp/config.d'): + """ + Read all configuration files from the config_dir and return a dict with their values. + """ + return {} + + +def all_repository_configs(): + """ + Return all the configuration items present in all repositories. + """ + return [] + + +def parse_repo_config_files(): + repo_config = {} + for config in all_repository_configs(): + section_name = config.section + + if section not in repo_config: + repo_config.update(config.to_dict()) + else: + if '{0}_description__'.format(config_item.name) in repo_config[config.section]: + raise Exception("Error: Two configuration items are declared with the same name Section: {0}, Key: {1}".format(config.section, config.name)) + repo_config[config.section].update(config.to_dict()[config.section]) + + return repo_config + + +def parse_config_files(config_dir): + """ + Parse all configuration and return a dict with those values. + """ + config = parse_repo_config_files() + system_config = parse_system_config_files(config_dir) + + for section, config_items in system_config.items(): + if section not in config: + print('WARNING: config file contains an unused section: Section: {0}'.format(section)) + config.update[section] = config_items + else: + for key, value in config_items: + if '{0}_description__'.format(key) not in config[section]: + print('WARNING: config file contains an unused config entry: Section: {0}, Key{1}'.format(section, key)) + + config[section][key] = value + + return config + + +def format_config(): + """ + Read the configuration definitions from all of the known repositories and return a string that + can be used as an example config file. + + Example config file: + transaction: + to_install_description__: | + List of packages to be added to the upgrade transaction. + Signed packages which are already installed will be skipped. + to_remove_description__: | + List of packages to be removed from the upgrade transaction + initial-setup should be removed to avoid it asking for EULA acceptance during upgrade + to_remove: + - initial-setup + to_keep_description__: | + List of packages to be kept in the upgrade transaction + to_keep: + - leapp + - python2-leapp + - python3-leapp + - leapp-repository + - snactor + """ + return SafeDumper(yaml.dump(parse_config_files(), dumper=SafeDumper)) diff --git a/repos/system_upgrade/common/configs/rpmtransactionconfigtaskscollector.py b/repos/system_upgrade/common/configs/rpmtransactionconfigtaskscollector.py new file mode 100644 index 0000000000..f58d0ce8e3 --- /dev/null +++ b/repos/system_upgrade/common/configs/rpmtransactionconfigtaskscollector.py @@ -0,0 +1,44 @@ +from leapp.models.fields import List +from leapp.models.configs import Config + + +### Nested containers? +### Duplication of default value in type_ and Config. If we eliminate that, we need to extract default from the type_ for the documentation. +### We probably want to allow dicts in Config. But IIRC, dicts were specifically excluded for model fields. Do we need something that restricts where fields are valid? +### Another thing we might want is must_be_string (no conversion). This can be helpful for things like file modes where 644 as a number would be wrong but "644" and "0644" can be inerpretted correctly +class Transaction_ToInstall(Config): + section = "transaction" + name = "to_install" + type_ = fields.List(fields.String(), default=[]) + default = [] + description = """ + List of packages to be added to the upgrade transaction. + Signed packages which are already installed will be skipped. + """ + + +class Transaction_ToKeep(Config): + section = "transaction" + name = "to_keep" + type_ = fields.List(fields.String(), default=[ + "leapp", + "python2-leapp", + "python3-leapp", + "leapp-repository", + "snactor", + ]) + description = """ + List of packages to be kept in the upgrade transaction. + """ + + +class Transaction_ToRemove(Config): + section = "transaction" + name = "to_remove" + type_ = fields.List(fields.String(), default=[ + "initial-setup", + ]) + description = """ + List of packages to be removed from the upgrade transaction. + initial-setup should be removed to avoid it asking for EULA acceptance during upgrade. + """