From b522b3d27d35d7e8497e74d81d4aaa3be84c0dca Mon Sep 17 00:00:00 2001 From: Nicholas Amorim Date: Mon, 30 Sep 2019 11:45:54 +0300 Subject: [PATCH] Added support for on action aliases. These allow for default parameters to be supplied on every execution and values can be Jinja expressions. --- CHANGELOG.rst | 1 + .../st2api/controllers/v1/aliasexecution.py | 6 +++ .../controllers/v1/test_alias_execution.py | 12 +++++- st2common/st2common/models/api/action.py | 6 +++ st2common/st2common/models/db/actionalias.py | 2 + .../models/utils/action_alias_utils.py | 38 +++++++++++++++++++ .../tests/unit/test_action_alias_utils.py | 29 ++++++++++++++ .../fixtures/aliases/aliases/alias5.yaml | 10 +++++ 8 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 st2tests/st2tests/fixtures/aliases/aliases/alias5.yaml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6c50ca5499..edce010f5a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,7 @@ Added #4757 * Add ``user`` parameter to ``re_run`` method of st2client. #4785 * Install pack dependencies automatically. #4769 +* Add support for `immutable_parameters` on Action Aliases. This feature allows default parameters to be supplied to the action on every execution of the alias. #4786 Changed ~~~~~~~ diff --git a/st2api/st2api/controllers/v1/aliasexecution.py b/st2api/st2api/controllers/v1/aliasexecution.py index b14750630f..085370c6f6 100644 --- a/st2api/st2api/controllers/v1/aliasexecution.py +++ b/st2api/st2api/controllers/v1/aliasexecution.py @@ -30,6 +30,7 @@ from st2common.models.db.notification import NotificationSchema, NotificationSubSchema from st2common.models.utils import action_param_utils from st2common.models.utils.action_alias_utils import extract_parameters_for_action_alias_db +from st2common.models.utils.action_alias_utils import inject_immutable_parameters from st2common.persistence.actionalias import ActionAlias from st2common.services import action as action_service from st2common.util import action_db as action_utils @@ -146,6 +147,11 @@ def _post(self, payload, requester_user, show_secrets=False, match_multiple=Fals 'source_channel': payload.source_channel, } + inject_immutable_parameters( + action_alias_db=action_alias_db, + multiple_execution_parameters=multiple_execution_parameters, + action_context=context) + results = [] for execution_parameters in multiple_execution_parameters: execution = self._schedule_execution(action_alias_db=action_alias_db, diff --git a/st2api/tests/unit/controllers/v1/test_alias_execution.py b/st2api/tests/unit/controllers/v1/test_alias_execution.py index f89f998d50..e224b8d93a 100644 --- a/st2api/tests/unit/controllers/v1/test_alias_execution.py +++ b/st2api/tests/unit/controllers/v1/test_alias_execution.py @@ -27,7 +27,7 @@ TEST_MODELS = { 'aliases': ['alias1.yaml', 'alias2.yaml', 'alias_with_undefined_jinja_in_ack_format.yaml', - 'alias4.yaml', 'alias_fixes1.yaml', 'alias_fixes2.yaml', + 'alias4.yaml', 'alias5.yaml', 'alias_fixes1.yaml', 'alias_fixes2.yaml', 'alias_match_multiple.yaml'], 'actions': ['action1.yaml', 'action2.yaml'], 'runners': ['runner1.yaml'] @@ -64,6 +64,7 @@ def setUpClass(cls): cls.alias1 = cls.models['aliases']['alias1.yaml'] cls.alias2 = cls.models['aliases']['alias2.yaml'] cls.alias4 = cls.models['aliases']['alias4.yaml'] + cls.alias5 = cls.models['aliases']['alias5.yaml'] cls.alias_with_undefined_jinja_in_ack_format = \ cls.models['aliases']['alias_with_undefined_jinja_in_ack_format.yaml'] @@ -76,6 +77,15 @@ def test_basic_execution(self, request): expected_parameters = {'param1': 'value1', 'param2': 'value2 value3'} self.assertEquals(request.call_args[0][0].parameters, expected_parameters) + @mock.patch.object(action_service, 'request', + return_value=(None, EXECUTION)) + def test_basic_execution_with_immutable_parameters(self, request): + command = 'lorem ipsum' + post_resp = self._do_post(alias_execution=self.alias5, command=command) + self.assertEqual(post_resp.status_int, 201) + expected_parameters = {'param1': 'value1', 'param2': 'value2'} + self.assertEquals(request.call_args[0][0].parameters, expected_parameters) + @mock.patch.object(action_service, 'request', return_value=(None, EXECUTION)) def test_invalid_format_string_referenced_in_request(self, request): diff --git a/st2common/st2common/models/api/action.py b/st2common/st2common/models/api/action.py index e3609f3d5b..8d564e5b2f 100644 --- a/st2common/st2common/models/api/action.py +++ b/st2common/st2common/models/api/action.py @@ -624,6 +624,10 @@ class ActionAliasAPI(BaseAPI, APIUIDMixin): "type": "object", "description": "Extra parameters, usually adapter-specific." }, + "immutable_parameters": { + "type": "object", + "description": "Parameters to be passed to the action on every execution." + }, "metadata_file": { "description": "Path to the metadata file relative to the pack directory.", "type": "string", @@ -645,11 +649,13 @@ def to_model(cls, alias): ack = getattr(alias, 'ack', None) result = getattr(alias, 'result', None) extra = getattr(alias, 'extra', None) + immutable_parameters = getattr(alias, 'immutable_parameters', None) metadata_file = getattr(alias, 'metadata_file', None) model = cls.model(name=name, description=description, pack=pack, ref=ref, enabled=enabled, action_ref=action_ref, formats=formats, ack=ack, result=result, extra=extra, + immutable_parameters=immutable_parameters, metadata_file=metadata_file) return model diff --git a/st2common/st2common/models/db/actionalias.py b/st2common/st2common/models/db/actionalias.py index ee57ab27b9..2f35c6bdfd 100644 --- a/st2common/st2common/models/db/actionalias.py +++ b/st2common/st2common/models/db/actionalias.py @@ -71,6 +71,8 @@ class ActionAliasDB(stormbase.StormFoundationDB, stormbase.ContentPackResourceMi extra = me.DictField( help_text='Additional parameters (usually adapter-specific) not covered in the schema.' ) + immutable_parameters = me.DictField( + help_text='Parameters to be passed to the action on every execution.') meta = { 'indexes': [ diff --git a/st2common/st2common/models/utils/action_alias_utils.py b/st2common/st2common/models/utils/action_alias_utils.py index 9a34f9fcdf..376b57c1c1 100644 --- a/st2common/st2common/models/utils/action_alias_utils.py +++ b/st2common/st2common/models/utils/action_alias_utils.py @@ -20,6 +20,9 @@ BRANCH, SUBPATTERN, ) +from st2common.util.jinja import render_values +from st2common.constants import keyvalue as kv_constants +from st2common.services import keyvalues as kv_service from st2common.exceptions.content import ParseException from st2common import log @@ -220,6 +223,41 @@ def extract_parameters(format_str, param_stream, match_multiple=False): return parser.get_extracted_param_value() +def inject_immutable_parameters(action_alias_db, multiple_execution_parameters, action_context): + """ + Inject immutable parameters from the alias definiton on the execution parameters. + Jinja expressions will be resolved. + """ + immutable_parameters = action_alias_db.immutable_parameters or {} + if not immutable_parameters: + return multiple_execution_parameters + + user = action_context.get('user', None) + + context = {} + context.update({ + kv_constants.DATASTORE_PARENT_SCOPE: { + kv_constants.SYSTEM_SCOPE: kv_service.KeyValueLookup( + scope=kv_constants.FULL_SYSTEM_SCOPE), + kv_constants.USER_SCOPE: kv_service.UserKeyValueLookup( + scope=kv_constants.FULL_USER_SCOPE, user=user) + } + }) + context.update(action_context) + rendered_params = render_values(immutable_parameters, context) + + for exec_params in multiple_execution_parameters: + overriden = [param for param in immutable_parameters.keys() if param in exec_params] + if overriden: + raise ValueError( + "Immutable arguments cannot be overriden: {}".format( + ','.join(overriden))) + + exec_params.update(rendered_params) + + return multiple_execution_parameters + + def search_regex_tokens(needle_tokens, haystack_tokens, backwards=False): """ Search a tokenized regex for any tokens in needle_tokens. Returns True if diff --git a/st2common/tests/unit/test_action_alias_utils.py b/st2common/tests/unit/test_action_alias_utils.py index 748bfe5d32..8bb1f6032a 100644 --- a/st2common/tests/unit/test_action_alias_utils.py +++ b/st2common/tests/unit/test_action_alias_utils.py @@ -14,10 +14,12 @@ from __future__ import absolute_import from sre_parse import (parse, AT, AT_BEGINNING, AT_BEGINNING_STRING, AT_END, AT_END_STRING) +from mock import Mock from unittest2 import TestCase from st2common.exceptions.content import ParseException from st2common.models.utils.action_alias_utils import ( ActionAliasFormatParser, search_regex_tokens, + inject_immutable_parameters ) @@ -320,3 +322,30 @@ def test_branches(self): def test_subpatterns(self): tokens = parse("^(?:asdf|fdsa$)") self.assertTrue(search_regex_tokens(self.end_tokens, tokens)) + + +class TestInjectImmutableParameters(TestCase): + def test_immutable_parameters_are_injected(self): + action_alias_db = Mock() + action_alias_db.immutable_parameters = {"env": "dev"} + exec_params = [{"param1": "value1", "param2": "value2"}] + inject_immutable_parameters(action_alias_db, exec_params, {}) + self.assertEqual( + exec_params, + [{"param1": "value1", "param2": "value2", "env": "dev"}]) + + def test_immutable_parameters_with_jinja(self): + action_alias_db = Mock() + action_alias_db.immutable_parameters = {"env": '{{ "dev" + "1" }}'} + exec_params = [{"param1": "value1", "param2": "value2"}] + inject_immutable_parameters(action_alias_db, exec_params, {}) + self.assertEqual( + exec_params, + [{"param1": "value1", "param2": "value2", "env": "dev1"}]) + + def test_override_raises_error(self): + action_alias_db = Mock() + action_alias_db.immutable_parameters = {"env": "dev"} + exec_params = [{"param1": "value1", "env": "prod"}] + with self.assertRaises(ValueError): + inject_immutable_parameters(action_alias_db, exec_params, {}) diff --git a/st2tests/st2tests/fixtures/aliases/aliases/alias5.yaml b/st2tests/st2tests/fixtures/aliases/aliases/alias5.yaml new file mode 100644 index 0000000000..ee87393653 --- /dev/null +++ b/st2tests/st2tests/fixtures/aliases/aliases/alias5.yaml @@ -0,0 +1,10 @@ +--- + name: "alias5" + pack: "aliases" + description: "Static test" + action_ref: "wolfpack.action1" + formats: + - "lorem ipsum" + immutable_parameters: + param1: value1 + param2: value2