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

Extend LEAPP with actor configuration #870

Merged
merged 3 commits into from
Nov 13, 2024
Merged
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
12 changes: 12 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,27 @@ jobs:
- name: Run unit tests with python3.12 on el9
python: python3.12
container: ubi10
- name: Run python linters with python3.12 on el9
python: python3.12
container: ubi10-lint
- name: Run unit tests with python3.9 on el9
python: python3.9
container: ubi9
- name: Run python linters with python3.9 on el9
python: python3.9
container: ubi9-lint
- name: Run unit tests with python 3.6 on el8
python: python3.6
container: ubi8
- name: Run python linters with python 3.6 on el8
python: python3.6
container: ubi8-lint
- name: Run unit tests with python2.7 on el7
python: python2.7
container: ubi7
- name: Run python linters with python2.7 on el7
python: python2.7
container: ubi7-lint

steps:
- name: Checkout code
Expand Down
5 changes: 4 additions & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ disable=
too-few-public-methods,
too-many-ancestors,
too-many-branches,
too-many-lines,
too-many-locals,
too-many-positional-arguments,
too-many-public-methods,
too-many-statements,
print-statement,
Expand All @@ -42,7 +44,8 @@ disable=
no-absolute-import, # XXX FIXME nice to have one day
unspecified-encoding, # XXX FIXME May be a good thing to have one day though
deprecated-class, # We still have < 3.9 to support
use-dict-literal
use-dict-literal,
use-yield-from

[FORMAT]
# Maximum number of characters on a single line.
Expand Down
2 changes: 2 additions & 0 deletions etc/leapp/leapp.conf
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ repo_path=/etc/leapp/repos.d/
[database]
path=/var/lib/leapp/leapp.db

[actor_config]
path=/etc/leapp/actor_conf.d/
78 changes: 46 additions & 32 deletions leapp/actors/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import functools
import logging
import os
import sys

try:
# Python 3.3+
from collections.abc import Sequence
except ImportError:
# Python 2.6 through 3.2
from collections import Sequence

from leapp.actors.config import Config, retrieve_config
from leapp.compat import string_types
from leapp.dialogs import Dialog
from leapp.exceptions import (MissingActorAttributeError, RequestStopAfterPhase, StopActorExecution,
Expand Down Expand Up @@ -41,6 +50,11 @@ class Actor(object):
Write the actor's description as a docstring.
"""

config_schemas = ()
"""
Defines the structure of the configuration that the actor uses.
"""

consumes = ()
"""
Tuple of :py:class:`leapp.models.Model` derived classes defined in the :ref:`repositories <terminology:repository>`
Expand Down Expand Up @@ -86,6 +100,7 @@ def serialize(self):
'path': os.path.dirname(sys.modules[type(self).__module__].__file__),
'class_name': type(self).__name__,
'description': self.description or type(self).__doc__,
'config_schemas': [c.__name__ for c in self.config_schemas],
MichalHe marked this conversation as resolved.
Show resolved Hide resolved
'consumes': [c.__name__ for c in self.consumes],
'produces': [p.__name__ for p in self.produces],
'tags': [t.__name__ for t in self.tags],
Expand All @@ -100,15 +115,20 @@ def __init__(self, messaging=None, logger=None, config_model=None, skip_dialogs=
This depends on the definition of such a configuration model being defined by the workflow
and an actor that provides such a message.
"""

Actor.current_instance = self
install_translation_for_actor(type(self))
self._messaging = messaging
self.log = (logger or logging.getLogger('leapp.actors')).getChild(self.name)
self.skip_dialogs = skip_dialogs
""" A configured logger instance for the current actor. """

# self._configuration is the workflow configuration.
# self.config_schemas is the actor defined configuration.
# self.config is the actual actor configuration
if config_model:
self._configuration = next(self.consume(config_model), None)
self.config = retrieve_config(self.config_schemas)

self._path = path

Expand Down Expand Up @@ -359,6 +379,15 @@ def report_error(self, message, severity=ErrorSeverity.ERROR, details=None):
actor=self,
details=details)

def retrieve_config(self):
"""
Retrieve the configuration described by self.config_schema.

:return: Dictionary containing requested configuration.
:rtype: dict
"""
return retrieve_config(self.config_schema)


def _is_type(value_type):
def validate(actor, name, value):
Expand Down Expand Up @@ -390,17 +419,23 @@ def _lint_warn(actor, name, type_name):
logging.getLogger("leapp.linter").warning("Actor %s field %s should be a tuple of %s", actor, name, type_name)


def _is_model_tuple(actor, name, value):
if isinstance(value, type) and issubclass(value, Model):
_lint_warn(actor, name, "Models")
def _is_foo_sequence(cls, cls_name, actor, name, value):
if isinstance(value, type) and issubclass(value, cls):
_lint_warn(actor, name, cls_name)
value = (value,)
_is_type(tuple)(actor, name, value)
if not all([True] + [isinstance(item, type) and issubclass(item, Model) for item in value]):
_is_type(Sequence)(actor, name, value)
if not all(isinstance(item, type) and issubclass(item, cls) for item in value):
raise WrongAttributeTypeError(
'Actor {} attribute {} should contain only Models'.format(actor, name))
'Actor {} attribute {} should contain only {}'.format(actor, name, cls_name))
return value


_is_config_sequence = functools.partial(_is_foo_sequence, Config, "Configs")
_is_model_sequence = functools.partial(_is_foo_sequence, Model, "Models")
_is_tag_sequence = functools.partial(_is_foo_sequence, Tag, "Tags")
_is_api_sequence = functools.partial(_is_foo_sequence, WorkflowAPI, "WorkflowAPIs")


def _is_dialog_tuple(actor, name, value):
if isinstance(value, Dialog):
_lint_warn(actor, name, "Dialogs")
Expand All @@ -412,28 +447,6 @@ def _is_dialog_tuple(actor, name, value):
return value


def _is_tag_tuple(actor, name, value):
if isinstance(value, type) and issubclass(value, Tag):
_lint_warn(actor, name, "Tags")
value = (value,)
_is_type(tuple)(actor, name, value)
if not all([True] + [isinstance(item, type) and issubclass(item, Tag) for item in value]):
raise WrongAttributeTypeError(
'Actor {} attribute {} should contain only Tags'.format(actor, name))
return value


def _is_api_tuple(actor, name, value):
if isinstance(value, type) and issubclass(value, WorkflowAPI):
_lint_warn(actor, name, "Apis")
value = (value,)
_is_type(tuple)(actor, name, value)
if not all([True] + [isinstance(item, type) and issubclass(item, WorkflowAPI) for item in value]):
raise WrongAttributeTypeError(
'Actor {} attribute {} should contain only WorkflowAPIs'.format(actor, name))
return value


def _get_attribute(actor, name, validator, required=False, default_value=None, additional_info='', resolve=None):
if resolve:
value = resolve(actor, name)
Expand Down Expand Up @@ -464,13 +477,14 @@ def get_actor_metadata(actor):
# # if path is not transformed into the realpath.
('path', os.path.dirname(os.path.realpath(sys.modules[actor.__module__].__file__))),
_get_attribute(actor, 'name', _is_type(string_types), required=True),
_get_attribute(actor, 'tags', _is_tag_tuple, required=True, additional_info=additional_tag_info),
_get_attribute(actor, 'consumes', _is_model_tuple, required=False, default_value=(), resolve=get_api_models),
_get_attribute(actor, 'produces', _is_model_tuple, required=False, default_value=(), resolve=get_api_models),
_get_attribute(actor, 'tags', _is_tag_sequence, required=True, additional_info=additional_tag_info),
_get_attribute(actor, 'consumes', _is_model_sequence, required=False, default_value=(), resolve=get_api_models),
_get_attribute(actor, 'produces', _is_model_sequence, required=False, default_value=(), resolve=get_api_models),
_get_attribute(actor, 'dialogs', _is_dialog_tuple, required=False, default_value=()),
_get_attribute(actor, 'description', _is_type(string_types), required=False,
default_value=actor.__doc__ or 'There has been no description provided for this actor.'),
_get_attribute(actor, 'apis', _is_api_tuple, required=False, default_value=())
_get_attribute(actor, 'config_schemas', _is_config_sequence, required=False, default_value=()),
_get_attribute(actor, 'apis', _is_api_sequence, required=False, default_value=())
])


Expand Down
Loading