From c1bb512e5a046dfc3bd36a2b8e82f49b2ea43cf2 Mon Sep 17 00:00:00 2001 From: Vinzenz Feenstra Date: Tue, 12 May 2020 16:31:38 +0200 Subject: [PATCH] Add --actor-config to snactor run This patch introduces support for specifying a model which the actor should consume for configuration data. Using this feature requires to run an actor before which produces this model and saves the output. Additionally to facilitate the testing needs for this PR `snactor messages add` has been added, which allows injecting messages from the commandline. Jira-Task: OAMG-1092 Signed-off-by: Vinzenz Feenstra --- Makefile | 2 +- leapp/messaging/__init__.py | 4 +- leapp/snactor/commands/messages/__init__.py | 55 +++++++++++++++++++-- leapp/snactor/commands/run.py | 20 ++++---- leapp/utils/workarounds/mp.py | 2 +- tests/scripts/test_snactor.py | 40 +++++++++++++++ 6 files changed, 106 insertions(+), 17 deletions(-) diff --git a/Makefile b/Makefile index 24e710649..f746b1b98 100644 --- a/Makefile +++ b/Makefile @@ -122,7 +122,7 @@ container-test: docker run --rm -ti -v ${PWD}:/payload leapp-tests test: lint - pytest --cov-report term-missing --cov=leapp tests/scripts + pytest -vv --cov-report term-missing --cov=leapp tests/scripts lint: pytest --cache-clear --pylint -m pylint leapp tests/scripts/*.py diff --git a/leapp/messaging/__init__.py b/leapp/messaging/__init__.py index 98a7b57b8..f25226483 100644 --- a/leapp/messaging/__init__.py +++ b/leapp/messaging/__init__.py @@ -9,10 +9,10 @@ from leapp.dialogs import RawMessageDialog from leapp.dialogs.renderer import CommandlineRenderer -from leapp.messaging.answerstore import AnswerStore from leapp.exceptions import CannotConsumeErrorMessages -from leapp.models import DialogModel, ErrorModel +from leapp.messaging.answerstore import AnswerStore from leapp.messaging.commands import WorkflowCommand +from leapp.models import DialogModel, ErrorModel from leapp.utils import get_api_models diff --git a/leapp/snactor/commands/messages/__init__.py b/leapp/snactor/commands/messages/__init__.py index dd5d00475..6a1040e77 100644 --- a/leapp/snactor/commands/messages/__init__.py +++ b/leapp/snactor/commands/messages/__init__.py @@ -1,9 +1,17 @@ +import json import os +import sys +from importlib import import_module -from leapp.utils.clicmd import command -from leapp.utils.repository import requires_repository +from leapp.exceptions import CommandError, LeappError, ModelDefinitionError +from leapp.logger import configure_logger +from leapp.messaging.inprocess import InProcessMessaging +from leapp.models.fields import ModelViolationError +from leapp.repository.scan import find_and_scan_repositories from leapp.snactor.context import with_snactor_context -from leapp.utils.audit import get_connection, get_config +from leapp.utils.audit import get_config, get_connection +from leapp.utils.clicmd import command, command_arg, command_opt +from leapp.utils.repository import find_repository_basedir, requires_repository _MAIN_LONG_DESCRIPTION = ''' This group of commands are around managing messages stored in the @@ -29,8 +37,18 @@ def messages(args): # noqa; pylint: disable=unused-argument ''' +_ADD_LONG_DESCRIPTION = ''' +With this command messages in the current repository scope can be added. +This gives the developer control over the input data available to actors +run and developed in this repository. + +For more information please consider reading the documentation at: +https://red.ht/leapp-docs +''' + + @messages.command('clear', help='Deletes all messages from the current repository scope', - description=_CLEAR_LONG_DESCRIPTION) + description=_ADD_LONG_DESCRIPTION) @requires_repository @with_snactor_context def clear(args): # noqa; pylint: disable=unused-argument @@ -40,3 +58,32 @@ def clear(args): # noqa; pylint: disable=unused-argument )) with get_connection(None) as con: con.execute("DELETE FROM message WHERE context = ?", (os.environ["LEAPP_EXECUTION_ID"],)) + + +class Injector(object): + name = 'injector' + + +@messages.command('add', help='Adds a message to the current repository scope', + description=_CLEAR_LONG_DESCRIPTION) +@command_opt('model', short_name='m', help='Model to add') +@command_arg('data', help='Data to add in JSON format') +@requires_repository +@with_snactor_context +def add(args): # noqa; pylint: disable=unused-argument + log = configure_logger() + basedir = find_repository_basedir('.') + repository = find_and_scan_repositories(basedir, include_locals=True) + try: + repository.load() + except LeappError as exc: + sys.stderr.write(exc.message) + sys.stderr.write('\n') + sys.exit(1) + try: + model = getattr(import_module('leapp.models'), args.model, None) + InProcessMessaging().produce(model.create(json.loads(args.data)), Injector()) + except ModelDefinitionError: + raise CommandError('No such model: {}'.format(args.model)) + except ModelViolationError as e: + raise CommandError('Invalid data format for model: {}\n => {}'.format(args.model, e.message)) diff --git a/leapp/snactor/commands/run.py b/leapp/snactor/commands/run.py index e47b80cfd..143d12b78 100644 --- a/leapp/snactor/commands/run.py +++ b/leapp/snactor/commands/run.py @@ -1,15 +1,15 @@ +from importlib import import_module import json import sys -from leapp.exceptions import LeappError, CommandError -from leapp.utils.clicmd import command, command_opt, command_arg -from leapp.utils.repository import requires_repository, find_repository_basedir +from leapp.exceptions import CommandError, LeappError from leapp.logger import configure_logger from leapp.messaging.inprocess import InProcessMessaging -from leapp.utils.output import report_errors, beautify_actor_exception from leapp.repository.scan import find_and_scan_repositories from leapp.snactor.context import with_snactor_context - +from leapp.utils.clicmd import command, command_arg, command_opt +from leapp.utils.output import beautify_actor_exception, report_errors +from leapp.utils.repository import find_repository_basedir, requires_repository _LONG_DESCRIPTION = ''' Runs the given actor as specified as `actor_name` in a testing environment. @@ -21,8 +21,9 @@ @command('run', help='Execute the given actor', description=_LONG_DESCRIPTION) @command_arg('actor-name') -@command_opt('--save-output', is_flag=True) -@command_opt('--print-output', is_flag=True) +@command_opt('--actor-config', help='Name of the workflow config model to use') +@command_opt('--save-output', is_flag=True, help='Save the produced messages by this actor.') +@command_opt('--print-output', is_flag=True, help='Print the produced messages by this actor.') @requires_repository @with_snactor_context def cli(args): @@ -39,13 +40,14 @@ def cli(args): actor = repository.lookup_actor(args.actor_name) if not actor: raise CommandError('Actor "{}" not found!'.format(args.actor_name)) - messaging = InProcessMessaging(stored=args.save_output) + config_model = getattr(import_module('leapp.models'), args.actor_config) if args.actor_config else None + messaging = InProcessMessaging(stored=args.save_output, config_model=config_model) messaging.load(actor.consumes) failure = False with beautify_actor_exception(): try: - actor(messaging=messaging, logger=actor_logger).run() + actor(messaging=messaging, logger=actor_logger, config_model=config_model).run() except BaseException: failure = True raise diff --git a/leapp/utils/workarounds/mp.py b/leapp/utils/workarounds/mp.py index 434b308c8..339323929 100644 --- a/leapp/utils/workarounds/mp.py +++ b/leapp/utils/workarounds/mp.py @@ -16,7 +16,7 @@ def __init__(self, *args, **kwargs): super(FixedFinalize, self).__init__(*args, **kwargs) self._pid = os.getpid() - def __call__(self, *args, **kwargs): # pylint: disable=signature-differs + def __call__(self, *args, **kwargs): # pylint: disable=signature-differs if self._pid != os.getpid(): return None return super(FixedFinalize, self).__call__(*args, **kwargs) diff --git a/tests/scripts/test_snactor.py b/tests/scripts/test_snactor.py index b241581a7..718ae2a95 100644 --- a/tests/scripts/test_snactor.py +++ b/tests/scripts/test_snactor.py @@ -131,6 +131,7 @@ def test_new_actor(repository_dir): with pytest.raises(CalledProcessError): check_call(['snactor', 'discover']) repository_dir.join('actors/test/actor.py').write(''' +from __future__ import print_function from leapp.actors import Actor from leapp.models import TestModel from leapp.tags import TestTag @@ -144,6 +145,8 @@ class Test(Actor): def process(self): self.produce(TestModel(value='Some testing value')) + if self._configuration: + print(self.configuration.value) ''') check_call(['snactor', 'discover']) @@ -211,6 +214,43 @@ def test_run_actor(repository_dir): check_call(['snactor', '--debug', 'run', '--print-output', '--save-output', 'Test']) +def test_actor_config(repository_dir): + with repository_dir.as_cwd(): + repository_dir.join('models/testconfig.py').write(''' +from leapp.models import Model, fields +from leapp.topics import TestTopic + + +class TestConfig(Model): + topic = TestTopic + value = fields.String(default='Test config value') +''') + check_call(['snactor', 'new-actor', 'ConfigTest']) + repository_dir.join('actors/configtest/actor.py').write(''' +from __future__ import print_function +from leapp.actors import Actor +from leapp.models import TestConfig +from leapp.tags import TestTag + +class ConfigTest(Actor): + name = 'test' + description = 'No description has been provided for the test actor.' + consumes = (TestConfig,) + produces = () + tags = (TestTag,) + + def process(self): + print(self.configuration.value) +''') + with pytest.raises(CalledProcessError): + # Raises if actor-config is not specified + check_call(['snactor', 'run', 'ConfigTest']) + # Inject message + check_call(['snactor', 'messages', 'add', '-m', 'TestConfig', '{"value": "ConfigTest calls"}']) + # Ensure the injected data is now available to the actor + check_call(['snactor', 'run', '--actor-config', 'TestConfig', 'Test']) + + def test_clear_messages(repository_dir): with repository_dir.as_cwd(): check_call(['snactor', 'messages', 'clear'])