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 __future__ import absolute_import |
||
17 | |||
18 | from st2client.models import Resource, Trace, TriggerInstance, Rule, LiveAction |
||
19 | from st2client.exceptions.operations import OperationFailureException |
||
20 | from st2client.formatters import table |
||
21 | from st2client.formatters import execution as execution_formatter |
||
22 | from st2client.commands import resource |
||
23 | from st2client.utils.date import format_isodate_for_user_timezone |
||
24 | |||
25 | |||
26 | TRACE_ATTRIBUTE_DISPLAY_ORDER = ['id', 'trace_tag', 'action_executions', 'rules', |
||
27 | 'trigger_instances', 'start_timestamp'] |
||
28 | |||
29 | TRACE_HEADER_DISPLAY_ORDER = ['id', 'trace_tag', 'start_timestamp'] |
||
30 | |||
31 | TRACE_COMPONENT_DISPLAY_LABELS = ['id', 'type', 'ref', 'updated_at'] |
||
32 | |||
33 | TRACE_DISPLAY_ATTRIBUTES = ['all'] |
||
34 | |||
35 | TRIGGER_INSTANCE_DISPLAY_OPTIONS = [ |
||
36 | 'all', |
||
37 | 'trigger-instances', |
||
38 | 'trigger_instances', |
||
39 | 'triggerinstances', |
||
40 | 'triggers' |
||
41 | ] |
||
42 | |||
43 | ACTION_EXECUTION_DISPLAY_OPTIONS = [ |
||
44 | 'all', |
||
45 | 'executions', |
||
46 | 'action-executions', |
||
47 | 'action_executions', |
||
48 | 'actionexecutions', |
||
49 | 'actions' |
||
50 | ] |
||
51 | |||
52 | |||
53 | class TraceBranch(resource.ResourceBranch): |
||
54 | def __init__(self, description, app, subparsers, parent_parser=None): |
||
55 | super(TraceBranch, self).__init__( |
||
56 | Trace, description, app, subparsers, |
||
57 | parent_parser=parent_parser, |
||
58 | read_only=True, |
||
59 | commands={ |
||
60 | 'list': TraceListCommand, |
||
61 | 'get': TraceGetCommand |
||
62 | }) |
||
63 | |||
64 | |||
65 | class SingleTraceDisplayMixin(object): |
||
66 | |||
67 | def print_trace_details(self, trace, args, **kwargs): |
||
68 | options = {'attributes': TRACE_ATTRIBUTE_DISPLAY_ORDER if args.json else |
||
69 | TRACE_HEADER_DISPLAY_ORDER} |
||
70 | options['json'] = args.json |
||
71 | options['yaml'] = args.yaml |
||
72 | options['attribute_transform_functions'] = self.attribute_transform_functions |
||
73 | |||
74 | formatter = execution_formatter.ExecutionResult |
||
75 | |||
76 | self.print_output(trace, formatter, **options) |
||
77 | |||
78 | # Everything should be printed if we are printing json. |
||
79 | if args.json or args.yaml: |
||
80 | return |
||
81 | |||
82 | components = [] |
||
83 | if any(attr in args.attr for attr in TRIGGER_INSTANCE_DISPLAY_OPTIONS): |
||
84 | components.extend([Resource(**{'id': trigger_instance['object_id'], |
||
85 | 'type': TriggerInstance._alias.lower(), |
||
86 | 'ref': trigger_instance['ref'], |
||
87 | 'updated_at': trigger_instance['updated_at']}) |
||
88 | for trigger_instance in trace.trigger_instances]) |
||
89 | if any(attr in args.attr for attr in ['all', 'rules']): |
||
90 | components.extend([Resource(**{'id': rule['object_id'], |
||
91 | 'type': Rule._alias.lower(), |
||
92 | 'ref': rule['ref'], |
||
93 | 'updated_at': rule['updated_at']}) |
||
94 | for rule in trace.rules]) |
||
95 | if any(attr in args.attr for attr in ACTION_EXECUTION_DISPLAY_OPTIONS): |
||
96 | components.extend([Resource(**{'id': execution['object_id'], |
||
97 | 'type': LiveAction._alias.lower(), |
||
98 | 'ref': execution['ref'], |
||
99 | 'updated_at': execution['updated_at']}) |
||
100 | for execution in trace.action_executions]) |
||
101 | if components: |
||
102 | components.sort(key=lambda resource: resource.updated_at) |
||
103 | self.print_output(components, table.MultiColumnTable, |
||
104 | attributes=TRACE_COMPONENT_DISPLAY_LABELS, |
||
105 | json=args.json, yaml=args.yaml) |
||
106 | |||
107 | |||
108 | class TraceListCommand(resource.ResourceCommand, SingleTraceDisplayMixin): |
||
109 | display_attributes = ['id', 'uid', 'trace_tag', 'start_timestamp'] |
||
110 | |||
111 | attribute_transform_functions = { |
||
112 | 'start_timestamp': format_isodate_for_user_timezone |
||
113 | } |
||
114 | |||
115 | attribute_display_order = TRACE_ATTRIBUTE_DISPLAY_ORDER |
||
116 | |||
117 | def __init__(self, resource, *args, **kwargs): |
||
0 ignored issues
–
show
|
|||
118 | |||
119 | self.default_limit = 50 |
||
120 | |||
121 | super(TraceListCommand, self).__init__( |
||
122 | resource, 'list', 'Get the list of the %s most recent %s.' % |
||
123 | (self.default_limit, resource.get_plural_display_name().lower()), |
||
124 | *args, **kwargs) |
||
125 | |||
126 | self.resource_name = resource.get_plural_display_name().lower() |
||
127 | self.group = self.parser.add_mutually_exclusive_group() |
||
128 | self.parser.add_argument('-n', '--last', type=int, dest='last', |
||
129 | default=self.default_limit, |
||
130 | help=('List N most recent %s. Use -n -1 to fetch the full result \ |
||
131 | set.' % self.resource_name)) |
||
132 | self.parser.add_argument('-s', '--sort', type=str, dest='sort_order', |
||
133 | default='descending', |
||
134 | help=('Sort %s by start timestamp, ' |
||
135 | 'asc|ascending (earliest first) ' |
||
136 | 'or desc|descending (latest first)' % self.resource_name)) |
||
137 | |||
138 | # Filter options |
||
139 | self.group.add_argument('-c', '--trace-tag', help='Trace-tag to filter the list.') |
||
140 | self.group.add_argument('-e', '--execution', help='Execution to filter the list.') |
||
141 | self.group.add_argument('-r', '--rule', help='Rule to filter the list.') |
||
142 | self.group.add_argument('-g', '--trigger-instance', |
||
143 | help='TriggerInstance to filter the list.') |
||
144 | # Display options |
||
145 | self.parser.add_argument('-a', '--attr', nargs='+', |
||
146 | default=self.display_attributes, |
||
147 | help=('List of attributes to include in the ' |
||
148 | 'output. "all" will return all ' |
||
149 | 'attributes.')) |
||
150 | self.parser.add_argument('-w', '--width', nargs='+', type=int, |
||
151 | default=None, |
||
152 | help=('Set the width of columns in output.')) |
||
153 | |||
154 | @resource.add_auth_token_to_kwargs_from_cli |
||
155 | def run(self, args, **kwargs): |
||
156 | # Filtering options |
||
157 | if args.trace_tag: |
||
158 | kwargs['trace_tag'] = args.trace_tag |
||
159 | if args.trigger_instance: |
||
160 | kwargs['trigger_instance'] = args.trigger_instance |
||
161 | if args.execution: |
||
162 | kwargs['execution'] = args.execution |
||
163 | if args.rule: |
||
164 | kwargs['rule'] = args.rule |
||
165 | |||
166 | if args.sort_order: |
||
167 | if args.sort_order in ['asc', 'ascending']: |
||
168 | kwargs['sort_asc'] = True |
||
169 | elif args.sort_order in ['desc', 'descending']: |
||
170 | kwargs['sort_desc'] = True |
||
171 | return self.manager.query_with_count(limit=args.last, **kwargs) |
||
172 | |||
173 | def run_and_print(self, args, **kwargs): |
||
174 | instances, count = self.run(args, **kwargs) |
||
175 | |||
176 | if instances and len(instances) == 1: |
||
177 | # For a single Trace we must include the components unless |
||
178 | # user has overriden the attributes to display |
||
179 | if args.attr == self.display_attributes: |
||
180 | args.attr = ['all'] |
||
181 | self.print_trace_details(trace=instances[0], args=args) |
||
182 | |||
183 | if not args.json and not args.yaml: |
||
184 | if args.last and count and count > args.last: |
||
185 | table.SingleRowTable.note_box(self.resource_name, 1) |
||
186 | else: |
||
187 | if args.json or args.yaml: |
||
188 | self.print_output(instances, table.MultiColumnTable, |
||
189 | attributes=args.attr, widths=args.width, |
||
190 | json=args.json, yaml=args.yaml, |
||
191 | attribute_transform_functions=self.attribute_transform_functions) |
||
192 | else: |
||
193 | self.print_output(instances, table.MultiColumnTable, |
||
194 | attributes=args.attr, widths=args.width, |
||
195 | attribute_transform_functions=self.attribute_transform_functions) |
||
196 | |||
197 | if args.last and count and count > args.last: |
||
198 | table.SingleRowTable.note_box(self.resource_name, args.last) |
||
199 | |||
200 | |||
201 | class TraceGetCommand(resource.ResourceGetCommand, SingleTraceDisplayMixin): |
||
202 | display_attributes = ['all'] |
||
203 | attribute_display_order = TRACE_ATTRIBUTE_DISPLAY_ORDER |
||
204 | attribute_transform_functions = { |
||
205 | 'start_timestamp': format_isodate_for_user_timezone |
||
206 | } |
||
207 | |||
208 | pk_argument_name = 'id' |
||
209 | |||
210 | def __init__(self, resource, *args, **kwargs): |
||
0 ignored issues
–
show
resource is re-defining a name which is already available in the outer-scope (previously defined on line 22 ).
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...
|
|||
211 | super(TraceGetCommand, self).__init__(resource, *args, **kwargs) |
||
212 | |||
213 | # Causation chains |
||
214 | self.causation_group = self.parser.add_mutually_exclusive_group() |
||
215 | |||
216 | self.causation_group.add_argument('-e', '--execution', |
||
217 | help='Execution to show causation chain.') |
||
218 | self.causation_group.add_argument('-r', '--rule', help='Rule to show causation chain.') |
||
219 | self.causation_group.add_argument('-g', '--trigger-instance', |
||
220 | help='TriggerInstance to show causation chain.') |
||
221 | |||
222 | # display filter group |
||
223 | self.display_filter_group = self.parser.add_argument_group() |
||
224 | |||
225 | self.display_filter_group.add_argument('--show-executions', action='store_true', |
||
226 | help='Only show executions.') |
||
227 | self.display_filter_group.add_argument('--show-rules', action='store_true', |
||
228 | help='Only show rules.') |
||
229 | self.display_filter_group.add_argument('--show-trigger-instances', action='store_true', |
||
230 | help='Only show trigger instances.') |
||
231 | self.display_filter_group.add_argument('-n', '--hide-noop-triggers', action='store_true', |
||
232 | help='Hide noop trigger instances.') |
||
233 | |||
234 | @resource.add_auth_token_to_kwargs_from_cli |
||
235 | def run(self, args, **kwargs): |
||
236 | resource_id = getattr(args, self.pk_argument_name, None) |
||
237 | return self.get_resource_by_id(resource_id, **kwargs) |
||
238 | |||
239 | @resource.add_auth_token_to_kwargs_from_cli |
||
240 | def run_and_print(self, args, **kwargs): |
||
241 | trace = None |
||
242 | try: |
||
243 | trace = self.run(args, **kwargs) |
||
244 | except resource.ResourceNotFoundError: |
||
245 | self.print_not_found(args.id) |
||
246 | raise OperationFailureException('Trace %s not found.' % (args.id)) |
||
247 | # First filter for causation chains |
||
248 | trace = self._filter_trace_components(trace=trace, args=args) |
||
249 | # next filter for display purposes |
||
250 | trace = self._apply_display_filters(trace=trace, args=args) |
||
251 | return self.print_trace_details(trace=trace, args=args) |
||
252 | |||
253 | @staticmethod |
||
254 | def _filter_trace_components(trace, args): |
||
255 | """ |
||
256 | This function walks up the component causal chain. It only returns |
||
257 | properties in the causal chain and nothing else. |
||
258 | """ |
||
259 | # check if any filtering is desired |
||
260 | if not (args.execution or args.rule or args.trigger_instance): |
||
261 | return trace |
||
262 | |||
263 | component_id = None |
||
264 | component_type = None |
||
265 | |||
266 | # pick the right component type |
||
267 | if args.execution: |
||
268 | component_id = args.execution |
||
269 | component_type = 'action_execution' |
||
270 | elif args.rule: |
||
271 | component_id = args.rule |
||
272 | component_type = 'rule' |
||
273 | elif args.trigger_instance: |
||
274 | component_id = args.trigger_instance |
||
275 | component_type = 'trigger_instance' |
||
276 | |||
277 | # Initialize collection to use |
||
278 | action_executions = [] |
||
279 | rules = [] |
||
280 | trigger_instances = [] |
||
281 | |||
282 | # setup flag to properly manage termination conditions |
||
283 | search_target_found = component_id and component_type |
||
284 | |||
285 | while search_target_found: |
||
286 | components_list = [] |
||
287 | if component_type == 'action_execution': |
||
288 | components_list = trace.action_executions |
||
289 | to_update_list = action_executions |
||
290 | elif component_type == 'rule': |
||
291 | components_list = trace.rules |
||
292 | to_update_list = rules |
||
293 | elif component_type == 'trigger_instance': |
||
294 | components_list = trace.trigger_instances |
||
295 | to_update_list = trigger_instances |
||
296 | # Look for search_target in the right collection and |
||
297 | # once found look up the caused_by to keep movig up |
||
298 | # the chain. |
||
299 | search_target_found = False |
||
300 | # init to default value |
||
301 | component_caused_by_id = None |
||
302 | for component in components_list: |
||
303 | test_id = component['object_id'] |
||
304 | if test_id == component_id: |
||
305 | caused_by = component.get('caused_by', {}) |
||
306 | component_id = caused_by.get('id', None) |
||
307 | component_type = caused_by.get('type', None) |
||
308 | # If provided the component_caused_by_id must match as well. This is mostly |
||
309 | # applicable for rules since the same rule may appear multiple times and can |
||
310 | # only be distinguished by causing TriggerInstance. |
||
311 | if component_caused_by_id and component_caused_by_id != component_id: |
||
312 | continue |
||
313 | component_caused_by_id = None |
||
314 | to_update_list.append(component) |
||
315 | # In some cases the component_id and the causing component are combined to |
||
316 | # provide the complete causation chain. Think rule + triggerinstance |
||
317 | if component_id and ':' in component_id: |
||
318 | component_id_split = component_id.split(':') |
||
319 | component_id = component_id_split[0] |
||
320 | component_caused_by_id = component_id_split[1] |
||
321 | search_target_found = True |
||
322 | break |
||
323 | |||
324 | trace.action_executions = action_executions |
||
325 | trace.rules = rules |
||
326 | trace.trigger_instances = trigger_instances |
||
327 | return trace |
||
328 | |||
329 | @staticmethod |
||
330 | def _apply_display_filters(trace, args): |
||
331 | """ |
||
332 | This function looks at the disaply filters to determine which components |
||
333 | should be displayed. |
||
334 | """ |
||
335 | # If all the filters are false nothing is to be filtered. |
||
336 | all_component_types = not(args.show_executions or |
||
337 | args.show_rules or |
||
338 | args.show_trigger_instances) |
||
339 | |||
340 | # check if noop_triggers are to be hidden. This check applies whenever TriggerInstances |
||
341 | # are to be shown. |
||
342 | if (all_component_types or args.show_trigger_instances) and args.hide_noop_triggers: |
||
343 | filtered_trigger_instances = [] |
||
344 | for trigger_instance in trace.trigger_instances: |
||
345 | is_noop_trigger_instance = True |
||
346 | for rule in trace.rules: |
||
347 | caused_by_id = rule.get('caused_by', {}).get('id', None) |
||
348 | if caused_by_id == trigger_instance['object_id']: |
||
349 | is_noop_trigger_instance = False |
||
350 | if not is_noop_trigger_instance: |
||
351 | filtered_trigger_instances.append(trigger_instance) |
||
352 | trace.trigger_instances = filtered_trigger_instances |
||
353 | |||
354 | if all_component_types: |
||
355 | return trace |
||
356 | |||
357 | if not args.show_executions: |
||
358 | trace.action_executions = [] |
||
359 | |||
360 | if not args.show_rules: |
||
361 | trace.rules = [] |
||
362 | |||
363 | if not args.show_trigger_instances: |
||
364 | trace.trigger_instances = [] |
||
365 | |||
366 | return trace |
||
367 |
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: