Completed
Pull Request — master (#2258)
by Manas
09:20 queued 03:35
created

st2client.commands.TraceGetCommand.__init__()   A

Complexity

Conditions 1

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 21
rs 9.3143
cc 1
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
from st2client.models import Resource, Trace, TriggerInstance, Rule, LiveAction
17
from st2client.exceptions.operations import OperationFailureException
18
from st2client.formatters import table
19
from st2client.formatters import execution as execution_formatter
20
from st2client.commands import resource
21
from st2client.utils.date import format_isodate
22
23
24
TRACE_ATTRIBUTE_DISPLAY_ORDER = ['id', 'trace_tag', 'action_executions', 'rules',
25
                                 'trigger_instances', 'start_timestamp']
26
27
TRACE_HEADER_DISPLAY_ORDER = ['id', 'trace_tag', 'start_timestamp']
28
29
TRACE_COMPONENT_DISPLAY_LABELS = ['id', 'type', 'updated_at']
30
31
TRACE_DISPLAY_ATTRIBUTES = ['all']
32
33
TRIGGER_INSTANCE_DISPLAY_OPTIONS = [
34
    'all',
35
    'trigger-instances',
36
    'trigger_instances',
37
    'triggerinstances',
38
    'triggers'
39
]
40
41
ACTION_EXECUTION_DISPLAY_OPTIONS = [
42
    'all',
43
    'executions',
44
    'action-executions',
45
    'action_executions',
46
    'actionexecutions',
47
    'actions'
48
]
49
50
51
class TraceBranch(resource.ResourceBranch):
52
    def __init__(self, description, app, subparsers, parent_parser=None):
53
        super(TraceBranch, self).__init__(
54
            Trace, description, app, subparsers,
55
            parent_parser=parent_parser,
56
            read_only=True,
57
            commands={
58
                'list': TraceListCommand,
59
                'get': TraceGetCommand
60
            })
61
62
63
class SingleTraceDisplayMixin(object):
64
65
    def print_trace_details(self, trace, args, **kwargs):
66
        options = {'attributes': TRACE_ATTRIBUTE_DISPLAY_ORDER if args.json else
67
                   TRACE_HEADER_DISPLAY_ORDER}
68
        options['json'] = args.json
69
        options['attribute_transform_functions'] = self.attribute_transform_functions
70
71
        formatter = execution_formatter.ExecutionResult
72
73
        self.print_output(trace, formatter, **options)
74
75
        # Everything should be printed if we are printing json.
76
        if args.json:
77
            return
78
79
        components = []
80
        if any(attr in args.attr for attr in TRIGGER_INSTANCE_DISPLAY_OPTIONS):
81
            components.extend([Resource(**{'id': trigger_instance['object_id'],
82
                                           'type': TriggerInstance._alias.lower(),
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _alias was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
83
                                           'updated_at': trigger_instance['updated_at']})
84
                               for trigger_instance in trace.trigger_instances])
85
        if any(attr in args.attr for attr in ['all', 'rules']):
86
            components.extend([Resource(**{'id': rule['object_id'],
87
                                           'type': Rule._alias.lower(),
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _alias was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
88
                                           'updated_at': rule['updated_at']})
89
                               for rule in trace.rules])
90
        if any(attr in args.attr for attr in ACTION_EXECUTION_DISPLAY_OPTIONS):
91
            components.extend([Resource(**{'id': execution['object_id'],
92
                                           'type': LiveAction._alias.lower(),
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _alias was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
93
                                           'updated_at': execution['updated_at']})
94
                               for execution in trace.action_executions])
95
        if components:
96
            components.sort(key=lambda resource: resource.updated_at)
97
            self.print_output(components, table.MultiColumnTable,
98
                              attributes=TRACE_COMPONENT_DISPLAY_LABELS,
99
                              json=args.json)
100
101
102
class TraceListCommand(resource.ResourceCommand, SingleTraceDisplayMixin):
103
    display_attributes = ['id', 'trace_tag', 'start_timestamp']
104
105
    attribute_transform_functions = {
106
        'start_timestamp': format_isodate
107
    }
108
109
    attribute_display_order = TRACE_ATTRIBUTE_DISPLAY_ORDER
110
111
    def __init__(self, resource, *args, **kwargs):
0 ignored issues
show
Comprehensibility Bug introduced by
resource is re-defining a name which is already available in the outer-scope (previously defined on line 20).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
112
        super(TraceListCommand, self).__init__(
113
            resource, 'list', 'Get the list of the 50 most recent %s.' %
114
            resource.get_plural_display_name().lower(),
115
            *args, **kwargs)
116
117
        self.group = self.parser.add_mutually_exclusive_group()
118
        self.parser.add_argument('-n', '--last', type=int, dest='last',
119
                                 default=50,
120
                                 help=('List N most recent %s; '
121
                                       'list all if 0.' %
122
                                       resource.get_plural_display_name().lower()))
123
124
        # Filter options
125
        self.group.add_argument('-c', '--trace-tag', help='Trace-tag to filter the list.')
126
        self.group.add_argument('-e', '--execution', help='Execution to filter the list.')
127
        self.group.add_argument('-r', '--rule', help='Rule to filter the list.')
128
        self.group.add_argument('-g', '--trigger-instance',
129
                                help='TriggerInstance to filter the list.')
130
        # Display options
131
        self.parser.add_argument('-a', '--attr', nargs='+',
132
                                 default=self.display_attributes,
133
                                 help=('List of attributes to include in the '
134
                                       'output. "all" will return all '
135
                                       'attributes.'))
136
        self.parser.add_argument('-w', '--width', nargs='+', type=int,
137
                                 default=None,
138
                                 help=('Set the width of columns in output.'))
139
140
    @resource.add_auth_token_to_kwargs_from_cli
141
    def run(self, args, **kwargs):
142
        # Filtering options
143
        if args.trace_tag:
144
            kwargs['trace_tag'] = args.trace_tag
145
        if args.trigger_instance:
146
            kwargs['trigger_instance'] = args.trigger_instance
147
        if args.execution:
148
            kwargs['execution'] = args.execution
149
        if args.rule:
150
            kwargs['rule'] = args.rule
151
152
        return self.manager.query(limit=args.last, **kwargs)
153
154
    def run_and_print(self, args, **kwargs):
155
        instances = self.run(args, **kwargs)
156
        if instances and len(instances) == 1:
157
            # For a single Trace we must include the components unless
158
            # user has overriden the attributes to display
159
            if args.attr == self.display_attributes:
160
                args.attr = ['all']
161
            self.print_trace_details(trace=instances[0], args=args)
162
        else:
163
            self.print_output(reversed(instances), table.MultiColumnTable,
164
                              attributes=args.attr, widths=args.width,
165
                              json=args.json,
166
                              attribute_transform_functions=self.attribute_transform_functions)
167
168
169
class TraceGetCommand(resource.ResourceGetCommand, SingleTraceDisplayMixin):
170
    display_attributes = ['all']
171
    attribute_display_order = TRACE_ATTRIBUTE_DISPLAY_ORDER
172
    attribute_transform_functions = {
173
        'start_timestamp': format_isodate
174
    }
175
176
    pk_argument_name = 'id'
177
178
    def __init__(self, resource, *args, **kwargs):
0 ignored issues
show
Comprehensibility Bug introduced by
resource is re-defining a name which is already available in the outer-scope (previously defined on line 20).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
179
        super(TraceGetCommand, self).__init__(resource, *args, **kwargs)
180
181
        # Causation chains
182
        self.causation_group = self.parser.add_mutually_exclusive_group()
183
184
        self.causation_group.add_argument('-e', '--execution',
185
                                          help='Execution to show causation chain.')
186
        self.causation_group.add_argument('-r', '--rule', help='Rule to show causation chain.')
187
        self.causation_group.add_argument('-g', '--trigger-instance',
188
                                          help='TriggerInstance to show causation chain.')
189
190
        # display filter group
191
        self.display_filter_group = self.parser.add_argument_group()
192
193
        self.display_filter_group.add_argument('--show-executions', action='store_true',
194
                                              help='Only show executions.')
195
        self.display_filter_group.add_argument('--show-rules', action='store_true',
196
                                              help='Only show rules.')
197
        self.display_filter_group.add_argument('--show-trigger-instances', action='store_true',
198
                                              help='Only show trigger instances.')
199
200
    @resource.add_auth_token_to_kwargs_from_cli
201
    def run(self, args, **kwargs):
202
        resource_id = getattr(args, self.pk_argument_name, None)
203
        return self.get_resource_by_id(resource_id, **kwargs)
204
205
    @resource.add_auth_token_to_kwargs_from_cli
206
    def run_and_print(self, args, **kwargs):
207
        trace = None
208
        try:
209
            trace = self.run(args, **kwargs)
210
        except resource.ResourceNotFoundError:
211
            self.print_not_found(args.id)
212
            raise OperationFailureException('Trace %s not found.' % (args.id))
213
        # First filter for causation chains
214
        trace = self._filter_trace_components(trace=trace, args=args)
215
        # next filter for display purposes
216
        trace = self._apply_display_filters(trace=trace, args=args)
217
        return self.print_trace_details(trace=trace, args=args)
218
219
    @staticmethod
220
    def _filter_trace_components(trace, args):
221
        """
222
        This function walks up the component causal chain. It only returns
223
        properties in the causal chain and nothing else.
224
        """
225
        # check if any filtering is desired
226
        if not (args.execution or args.rule or args.trigger_instance):
227
            return trace
228
229
        component_id = None
230
        component_type = None
231
232
        # pick the right component type
233
        if args.execution:
234
            component_id = args.execution
235
            component_type = 'action_execution'
236
        elif args.rule:
237
            component_id = args.rule
238
            component_type = 'rule'
239
        elif args.trigger_instance:
240
            component_id = args.trigger_instance
241
            component_type = 'trigger_instance'
242
243
        # Initialize collection to use
244
        action_executions = []
245
        rules = []
246
        trigger_instances = []
247
248
        # setup flag to properly manage termination conditions
249
        search_target_found = component_id and component_type
250
251
        while search_target_found:
252
            components_list = []
253
            if component_type == 'action_execution':
254
                components_list = trace.action_executions
255
                to_update_list = action_executions
256
            elif component_type == 'rule':
257
                components_list = trace.rules
258
                to_update_list = rules
259
            elif component_type == 'trigger_instance':
260
                components_list = trace.trigger_instances
261
                to_update_list = trigger_instances
262
            # Look for search_target in the right collection and
263
            # once found look up the caused_by to keep movig up
264
            # the chain.
265
            search_target_found = False
266
            # init to default value
267
            component_caused_by_id = None
268
            for component in components_list:
269
                test_id = component['object_id']
270
                if test_id == component_id:
271
                    caused_by = component.get('caused_by', {})
272
                    component_id = caused_by.get('id', None)
273
                    component_type = caused_by.get('type', None)
274
                    # If provided the component_caused_by_id must match as well. This is mostly
275
                    # applicable for rules since the same rule may appear multiple times and can
276
                    # only be distinguished by causing TriggerInstance.
277
                    if component_caused_by_id and component_caused_by_id != component_id:
278
                        continue
279
                    component_caused_by_id = None
280
                    to_update_list.append(component)
281
                    # In some cases the component_id and the causing component are combined to
282
                    # provide the complete causation chain. Think rule + triggerinstance
283
                    if component_id and ':' in component_id:
284
                        component_id_split = component_id.split(':')
285
                        component_id = component_id_split[0]
286
                        component_caused_by_id = component_id_split[1]
287
                    search_target_found = True
288
                    break
289
290
        trace.action_executions = action_executions
291
        trace.rules = rules
292
        trace.trigger_instances = trigger_instances
293
        return trace
294
295
    @staticmethod
296
    def _apply_display_filters(trace, args):
297
        """
298
        This function looks at the disaply filters to determine which components
299
        should be displayed.
300
        """
301
        # If all the filters are false nothing is to be filtered.
302
        if not(args.show_executions or args.show_rules or args.show_trigger_instances):
303
            return trace
304
305
        if not args.show_executions:
306
            trace.action_executions = []
307
308
        if not args.show_rules:
309
            trace.rules = []
310
311
        if not args.show_trigger_instances:
312
            trace.trigger_instances = []
313
314
        return trace
315