ActionAliasExecutionController._post()   F
last analyzed

Complexity

Conditions 14

Size

Total Lines 92

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 14
dl 0
loc 92
rs 2
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like ActionAliasExecutionController._post() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
2
# contributor license agreements.  See the NOTICE file distributed with
3
# this work for additional information regarding copyright ownership.
4
# The ASF licenses this file to You under the Apache License, Version 2.0
5
# (the "License"); you may not use this file except in compliance with
6
# the License.  You may obtain a copy of the License at
7
#
8
#     http://www.apache.org/licenses/LICENSE-2.0
9
#
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS,
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
# See the License for the specific language governing permissions and
14
# limitations under the License.
15
16
import jsonschema
17
from jinja2.exceptions import UndefinedError
18
from oslo_config import cfg
19
import six
20
21
from st2api.controllers.base import BaseRestControllerMixin
22
from st2common import log as logging
23
from st2common.exceptions.actionalias import ActionAliasAmbiguityException
24
from st2common.exceptions.db import StackStormDBObjectNotFoundError
25
from st2common.models.api.action import ActionAliasAPI
26
from st2common.models.api.action import AliasMatchAndExecuteInputAPI
27
from st2common.models.api.auth import get_system_username
28
from st2common.models.api.execution import ActionExecutionAPI
29
from st2common.models.db.auth import UserDB
30
from st2common.models.db.liveaction import LiveActionDB
31
from st2common.models.db.notification import NotificationSchema, NotificationSubSchema
32
from st2common.models.utils import action_param_utils
33
from st2common.models.utils.action_alias_utils import extract_parameters_for_action_alias_db
34
from st2common.persistence.actionalias import ActionAlias
35
from st2common.services import action as action_service
36
from st2common.util import action_db as action_utils
37
from st2common.util import reference
38
from st2common.util.actionalias_matching import get_matching_alias
39
from st2common.util.jinja import render_values as render
40
from st2common.rbac.types import PermissionType
41
from st2common.rbac.utils import assert_user_has_resource_db_permission
42
from st2common.router import abort
43
from st2common.router import Response
44
45
http_client = six.moves.http_client
46
47
LOG = logging.getLogger(__name__)
48
49
CAST_OVERRIDES = {
50
    'array': (lambda cs_x: [v.strip() for v in cs_x.split(',')])
51
}
52
53
54
class ActionAliasExecutionController(BaseRestControllerMixin):
55
    def match_and_execute(self, input_api, requester_user, show_secrets=False):
56
        """
57
            Try to find a matching alias and if one is found, schedule a new
58
            execution by parsing parameters from the provided command against
59
            the matched alias.
60
61
            Handles requests:
62
                POST /aliasexecution/match_and_execute
63
        """
64
        command = input_api.command
65
66
        try:
67
            format_ = get_matching_alias(command=command)
68
        except ActionAliasAmbiguityException as e:
69
            LOG.exception('Command "%s" matched (%s) patterns.', e.command, len(e.matches))
70
            return abort(http_client.BAD_REQUEST, str(e))
71
72
        action_alias_db = format_['alias']
73
        representation = format_['representation']
74
75
        params = {
76
            'name': action_alias_db.name,
77
            'format': representation,
78
            'command': command,
79
            'user': input_api.user,
80
            'source_channel': input_api.source_channel
81
        }
82
83
        # Add in any additional parameters provided by the user
84
        if input_api.notification_channel:
85
            params['notification_channel'] = input_api.notification_channel
86
87
        if input_api.notification_route:
88
            params['notification_route'] = input_api.notification_route
89
90
        alias_execution_api = AliasMatchAndExecuteInputAPI(**params)
91
        results = self._post(
92
            payload=alias_execution_api,
93
            requester_user=requester_user,
94
            show_secrets=show_secrets,
95
            match_multiple=format_['match_multiple'])
96
        return Response(json={'results': results}, status=http_client.CREATED)
97
98
    def _post(self, payload, requester_user, show_secrets=False, match_multiple=False):
99
        action_alias_name = payload.name if payload else None
100
101
        if not action_alias_name:
102
            abort(http_client.BAD_REQUEST, 'Alias execution "name" is required')
103
            return
104
105
        if not requester_user:
106
            requester_user = UserDB(cfg.CONF.system_user.user)
107
108
        format_str = payload.format or ''
109
        command = payload.command or ''
110
111
        try:
112
            action_alias_db = ActionAlias.get_by_name(action_alias_name)
113
        except ValueError:
114
            action_alias_db = None
115
116
        if not action_alias_db:
117
            msg = 'Unable to identify action alias with name "%s".' % (action_alias_name)
118
            abort(http_client.NOT_FOUND, msg)
119
            return
120
121
        if not action_alias_db.enabled:
122
            msg = 'Action alias with name "%s" is disabled.' % (action_alias_name)
123
            abort(http_client.BAD_REQUEST, msg)
124
            return
125
126
        if match_multiple:
127
            multiple_execution_parameters = extract_parameters_for_action_alias_db(
128
                action_alias_db=action_alias_db,
129
                format_str=format_str,
130
                param_stream=command,
131
                match_multiple=match_multiple)
132
        else:
133
            multiple_execution_parameters = [
134
                extract_parameters_for_action_alias_db(
135
                    action_alias_db=action_alias_db,
136
                    format_str=format_str,
137
                    param_stream=command,
138
                    match_multiple=match_multiple)
139
            ]
140
141
        notify = self._get_notify_field(payload)
142
143
        context = {
144
            'action_alias_ref': reference.get_ref_from_model(action_alias_db),
145
            'api_user': payload.user,
146
            'user': requester_user.name,
147
            'source_channel': payload.source_channel
148
        }
149
150
        results = []
151
        for execution_parameters in multiple_execution_parameters:
152
            execution = self._schedule_execution(action_alias_db=action_alias_db,
153
                                                 params=execution_parameters,
154
                                                 notify=notify,
155
                                                 context=context,
156
                                                 show_secrets=show_secrets,
157
                                                 requester_user=requester_user)
158
159
            result = {
160
                'execution': execution,
161
                'actionalias': ActionAliasAPI.from_model(action_alias_db)
162
            }
163
164
            if action_alias_db.ack:
165
                try:
166
                    if 'format' in action_alias_db.ack:
167
                        message = render({'alias': action_alias_db.ack['format']}, result)['alias']
168
169
                        result.update({
170
                            'message': message
171
                        })
172
                except UndefinedError as e:
173
                    result.update({
174
                        'message': 'Cannot render "format" in field "ack" for alias. ' + e.message
175
                    })
176
177
                try:
178
                    if 'extra' in action_alias_db.ack:
179
                        result.update({
180
                            'extra': render(action_alias_db.ack['extra'], result)
181
                        })
182
                except UndefinedError as e:
183
                    result.update({
184
                        'extra': 'Cannot render "extra" in field "ack" for alias. ' + e.message
185
                    })
186
187
            results.append(result)
188
189
        return results
190
191
    def post(self, payload, requester_user, show_secrets=False):
192
        results = self._post(payload, requester_user, show_secrets, match_multiple=False)
193
        return Response(json=results[0], status=http_client.CREATED)
194
195
    def _tokenize_alias_execution(self, alias_execution):
196
        tokens = alias_execution.strip().split(' ', 1)
197
        return (tokens[0], tokens[1] if len(tokens) > 1 else None)
198
199
    def _get_notify_field(self, payload):
200
        on_complete = NotificationSubSchema()
201
        route = (getattr(payload, 'notification_route', None) or
202
                 getattr(payload, 'notification_channel', None))
203
        on_complete.routes = [route]
204
        on_complete.data = {
205
            'user': payload.user,
206
            'source_channel': payload.source_channel
207
        }
208
        notify = NotificationSchema()
209
        notify.on_complete = on_complete
210
        return notify
211
212
    def _schedule_execution(self, action_alias_db, params, notify, context, requester_user,
213
                            show_secrets):
214
        action_ref = action_alias_db.action_ref
215
        action_db = action_utils.get_action_by_ref(action_ref)
216
217
        if not action_db:
218
            raise StackStormDBObjectNotFoundError('Action with ref "%s" not found ' % (action_ref))
219
220
        assert_user_has_resource_db_permission(user_db=requester_user, resource_db=action_db,
221
                                               permission_type=PermissionType.ACTION_EXECUTE)
222
223
        try:
224
            # prior to shipping off the params cast them to the right type.
225
            params = action_param_utils.cast_params(action_ref=action_alias_db.action_ref,
226
                                                    params=params,
227
                                                    cast_overrides=CAST_OVERRIDES)
228
            if not context:
229
                context = {
230
                    'action_alias_ref': reference.get_ref_from_model(action_alias_db),
231
                    'user': get_system_username()
232
                }
233
            liveaction = LiveActionDB(action=action_alias_db.action_ref, context=context,
234
                                      parameters=params, notify=notify)
235
            _, action_execution_db = action_service.request(liveaction)
236
            mask_secrets = self._get_mask_secrets(requester_user, show_secrets=show_secrets)
237
            return ActionExecutionAPI.from_model(action_execution_db, mask_secrets=mask_secrets)
238
        except ValueError as e:
239
            LOG.exception('Unable to execute action.')
240
            abort(http_client.BAD_REQUEST, str(e))
241
        except jsonschema.ValidationError as e:
242
            LOG.exception('Unable to execute action. Parameter validation failed.')
243
            abort(http_client.BAD_REQUEST, str(e))
244
        except Exception as e:
245
            LOG.exception('Unable to execute action. Unexpected error encountered.')
246
            abort(http_client.INTERNAL_SERVER_ERROR, str(e))
247
248
249
action_alias_execution_controller = ActionAliasExecutionController()
250