Completed
Pull Request — master (#2258)
by Manas
06:15
created

st2client.commands.TraceGetCommand.__init__()   A

Complexity

Conditions 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 10
rs 9.4286
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
        self.group = self.parser.add_mutually_exclusive_group()
182
183
        # Causation chains
184
        self.group.add_argument('-e', '--execution', help='Execution to show causation chain.')
185
        self.group.add_argument('-r', '--rule', help='Rule to show causation chain.')
186
        self.group.add_argument('-g', '--trigger-instance',
187
                                help='TriggerInstance to show causation chain.')
188
189
    @resource.add_auth_token_to_kwargs_from_cli
190
    def run(self, args, **kwargs):
191
        resource_id = getattr(args, self.pk_argument_name, None)
192
        return self.get_resource_by_id(resource_id, **kwargs)
193
194
    @resource.add_auth_token_to_kwargs_from_cli
195
    def run_and_print(self, args, **kwargs):
196
        trace = None
197
        try:
198
            trace = self.run(args, **kwargs)
199
        except resource.ResourceNotFoundError:
200
            self.print_not_found(args.id)
201
            raise OperationFailureException('Trace %s not found.' % (args.id))
202
        trace = self._filter_trace_components(trace=trace, args=args)
203
        return self.print_trace_details(trace=trace, args=args)
204
205
    def _filter_trace_components(self, trace, args):
206
        """
207
        This function walks up the component causal chain. It only returns
208
        properties in the causal chain and nothing else.
209
        """
210
        # check if any filtering is desired
211
        if not (args.execution or args.rule or args.trigger_instance):
212
            return trace
213
214
        component_id = None
215
        component_type = None
216
217
        # pick the right component type
218
        if args.execution:
219
            component_id = args.execution
220
            component_type = 'action_execution'
221
        elif args.rule:
222
            component_id = args.rule
223
            component_type = 'rule'
224
        elif args.trigger_instance:
225
            component_id = args.trigger_instance
226
            component_type = 'trigger_instance'
227
228
        # Initialize collection to use
229
        action_executions = []
230
        rules = []
231
        trigger_instances = []
232
233
        # setup flag to properly manage termination conditions
234
        search_target_found = component_id and component_type
235
236
        while search_target_found:
237
            components_list = []
238
            if component_type == 'action_execution':
239
                components_list = trace.action_executions
240
                to_update_list = action_executions
241
            elif component_type == 'rule':
242
                components_list = trace.rules
243
                to_update_list = rules
244
            elif component_type == 'trigger_instance':
245
                components_list = trace.trigger_instances
246
                to_update_list = trigger_instances
247
            # Look for search_target in the right collection and
248
            # once found look up the caused_by to keep movig up
249
            # the chain.
250
            search_target_found = False
251
            for component in components_list:
252
                test_id = component['object_id']
253
                if test_id == component_id:
254
                    to_update_list.append(component)
255
                    caused_by = component.get('caused_by', {})
256
                    component_id = caused_by.get('id', None)
257
                    component_type = caused_by.get('type', None)
258
                    # Why? Coz I am evil.
259
                    if component_type == 'rule' and ':' in component_id:
260
                        component_id = component_id.split(':')[0]
261
                    search_target_found = True
262
                    break
263
264
        trace.action_executions = action_executions
265
        trace.rules = rules
266
        trace.trigger_instances = trigger_instances
267
        return trace
268