Skip to content

Commit

Permalink
Initial work to demonstrate an actor config framework.
Browse files Browse the repository at this point in the history
  • Loading branch information
abadger committed Nov 7, 2023
1 parent 64ec2ec commit d29d9a3
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -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'


Expand All @@ -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))
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand All @@ -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'])
126 changes: 126 additions & 0 deletions repos/system_upgrade/common/configs/__init__.py
Original file line number Diff line number Diff line change
@@ -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))
Original file line number Diff line number Diff line change
@@ -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.
"""

0 comments on commit d29d9a3

Please sign in to comment.