Completed
Pull Request — master (#2258)
by Manas
06:18
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
        trace = self._filter_trace_components(trace=trace, args=args)
214
        trace = self._apply_display_filters(trace=trace, args=args)
215
        return self.print_trace_details(trace=trace, args=args)
216
217
    def _filter_trace_components(self, trace, args):
218
        """
219
        This function walks up the component causal chain. It only returns
220
        properties in the causal chain and nothing else.
221
        """
222
        # check if any filtering is desired
223
        if not (args.execution or args.rule or args.trigger_instance):
224
            return trace
225
226
        component_id = None
227
        component_type = None
228
229
        # pick the right component type
230
        if args.execution:
231
            component_id = args.execution
232
            component_type = 'action_execution'
233
        elif args.rule:
234
            component_id = args.rule
235
            component_type = 'rule'
236
        elif args.trigger_instance:
237
            component_id = args.trigger_instance
238
            component_type = 'trigger_instance'
239
240
        # Initialize collection to use
241
        action_executions = []
242
        rules = []
243
        trigger_instances = []
244
245
        # setup flag to properly manage termination conditions
246
        search_target_found = component_id and component_type
247
248
        while search_target_found:
249
            components_list = []
250
            if component_type == 'action_execution':
251
                components_list = trace.action_executions
252
                to_update_list = action_executions
253
            elif component_type == 'rule':
254
                components_list = trace.rules
255
                to_update_list = rules
256
            elif component_type == 'trigger_instance':
257
                components_list = trace.trigger_instances
258
                to_update_list = trigger_instances
259
            # Look for search_target in the right collection and
260
            # once found look up the caused_by to keep movig up
261
            # the chain.
262
            search_target_found = False
263
            # init to default value
264
            component_caused_by_id = None
265
            for component in components_list:
266
                test_id = component['object_id']
267
                if test_id == component_id:
268
                    caused_by = component.get('caused_by', {})
269
                    component_id = caused_by.get('id', None)
270
                    component_type = caused_by.get('type', None)
271
                    # If provided the component_caused_by_id must match as well. This is mostly
272
                    # applicable for rules since the same rule may appear multiple times and can
273
                    # only be distinguished by causing TriggerInstance.
274
                    if component_caused_by_id and component_caused_by_id != component_id:
275
                        continue
276
                    component_caused_by_id = None
277
                    to_update_list.append(component)
278
                    # In some cases the component_id and the causing component are combined to
279
                    # provide the complete causation chain. Think rule + triggerinstance
280
                    if ':' in component_id:
281
                        component_id_split = component_id.split(':')
282
                        component_id = component_id_split[0]
283
                        component_caused_by_id = component_id_split[1]
284
                    search_target_found = True
285
                    break
286
287
        trace.action_executions = action_executions
288
        trace.rules = rules
289
        trace.trigger_instances = trigger_instances
290
        return trace
291
292
    def _apply_display_filters(self, trace, args):
293
        """
294
        This function looks at the disaply filters to determine which components
295
        should be displayed.
296
        """
297
        # If all the filters are false nothing is to be filtered.
298
        if not(args.show_executions or args.show_rules or args.show_trigger_instances):
299
            return trace
300
301
        if not args.show_executions:
302
            trace.executions = []
303
304
        if not args.show_rules:
305
            trace.rules = []
306
307
        if not args.show_trigger_instances:
308
            trace.trigger_instances = []
309
310
        return trace
311