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 | import sys |
||
18 | |||
19 | import editor |
||
20 | import yaml |
||
21 | |||
22 | from st2client.models import Config |
||
23 | from st2client.models import Pack |
||
24 | from st2client.models import LiveAction |
||
25 | from st2client.commands import resource |
||
26 | from st2client.commands.resource import add_auth_token_to_kwargs_from_cli |
||
27 | from st2client.commands.action import ActionRunCommandMixin |
||
28 | from st2client.formatters import table |
||
29 | from st2client.exceptions.operations import OperationFailureException |
||
30 | import st2client.utils.terminal as term |
||
31 | from st2client.utils import interactive |
||
32 | |||
33 | |||
34 | LIVEACTION_STATUS_REQUESTED = 'requested' |
||
35 | LIVEACTION_STATUS_SCHEDULED = 'scheduled' |
||
36 | LIVEACTION_STATUS_DELAYED = 'delayed' |
||
37 | LIVEACTION_STATUS_RUNNING = 'running' |
||
38 | LIVEACTION_STATUS_SUCCEEDED = 'succeeded' |
||
39 | LIVEACTION_STATUS_FAILED = 'failed' |
||
40 | LIVEACTION_STATUS_TIMED_OUT = 'timeout' |
||
41 | LIVEACTION_STATUS_ABANDONED = 'abandoned' |
||
42 | LIVEACTION_STATUS_CANCELING = 'canceling' |
||
43 | LIVEACTION_STATUS_CANCELED = 'canceled' |
||
44 | |||
45 | LIVEACTION_COMPLETED_STATES = [ |
||
46 | LIVEACTION_STATUS_SUCCEEDED, |
||
47 | LIVEACTION_STATUS_FAILED, |
||
48 | LIVEACTION_STATUS_TIMED_OUT, |
||
49 | LIVEACTION_STATUS_CANCELED, |
||
50 | LIVEACTION_STATUS_ABANDONED |
||
51 | ] |
||
52 | |||
53 | |||
54 | class PackBranch(resource.ResourceBranch): |
||
55 | def __init__(self, description, app, subparsers, parent_parser=None): |
||
56 | super(PackBranch, self).__init__( |
||
57 | Pack, description, app, subparsers, |
||
58 | parent_parser=parent_parser, |
||
59 | read_only=True, |
||
60 | commands={ |
||
61 | 'list': PackListCommand, |
||
62 | 'get': PackGetCommand |
||
63 | }) |
||
64 | |||
65 | self.commands['show'] = PackShowCommand(self.resource, self.app, self.subparsers) |
||
66 | self.commands['search'] = PackSearchCommand(self.resource, self.app, self.subparsers) |
||
67 | self.commands['install'] = PackInstallCommand(self.resource, self.app, self.subparsers) |
||
68 | self.commands['remove'] = PackRemoveCommand(self.resource, self.app, self.subparsers) |
||
69 | self.commands['register'] = PackRegisterCommand(self.resource, self.app, self.subparsers) |
||
70 | self.commands['config'] = PackConfigCommand(self.resource, self.app, self.subparsers) |
||
71 | |||
72 | |||
73 | class PackResourceCommand(resource.ResourceCommand): |
||
74 | def run_and_print(self, args, **kwargs): |
||
75 | try: |
||
76 | instance = self.run(args, **kwargs) |
||
77 | if not instance: |
||
78 | raise resource.ResourceNotFoundError("No matching items found") |
||
79 | self.print_output(instance, table.PropertyValueTable, |
||
80 | attributes=['all'], json=args.json, yaml=args.yaml) |
||
81 | except resource.ResourceNotFoundError: |
||
82 | print("No matching items found") |
||
83 | except Exception as e: |
||
84 | message = e.message or str(e) |
||
85 | print('ERROR: %s' % (message)) |
||
86 | raise OperationFailureException(message) |
||
87 | |||
88 | |||
89 | class PackAsyncCommand(ActionRunCommandMixin, resource.ResourceCommand): |
||
90 | def __init__(self, *args, **kwargs): |
||
91 | super(PackAsyncCommand, self).__init__(*args, **kwargs) |
||
92 | |||
93 | self.parser.add_argument('-w', '--width', nargs='+', type=int, default=None, |
||
94 | help='Set the width of columns in output.') |
||
95 | |||
96 | detail_arg_grp = self.parser.add_mutually_exclusive_group() |
||
97 | detail_arg_grp.add_argument('--attr', nargs='+', |
||
98 | default=['name', 'description', 'version', 'author'], |
||
99 | help=('List of attributes to include in the ' |
||
100 | 'output. "all" or unspecified will ' |
||
101 | 'return all attributes.')) |
||
102 | detail_arg_grp.add_argument('-d', '--detail', action='store_true', |
||
103 | help='Display full detail of the execution in table format.') |
||
104 | |||
105 | @add_auth_token_to_kwargs_from_cli |
||
106 | def run_and_print(self, args, **kwargs): |
||
107 | instance = self.run(args, **kwargs) |
||
108 | if not instance: |
||
109 | raise Exception('Server did not create instance.') |
||
110 | |||
111 | parent_id = instance.execution_id |
||
112 | |||
113 | stream_mgr = self.app.client.managers['Stream'] |
||
114 | |||
115 | execution = None |
||
116 | |||
117 | with term.TaskIndicator() as indicator: |
||
118 | events = ['st2.execution__create', 'st2.execution__update'] |
||
119 | for event in stream_mgr.listen(events, **kwargs): |
||
120 | execution = LiveAction(**event) |
||
121 | |||
122 | if execution.id == parent_id \ |
||
123 | and execution.status in LIVEACTION_COMPLETED_STATES: |
||
124 | break |
||
125 | |||
126 | # Suppress intermediate output in case output formatter is requested |
||
127 | if args.json or args.yaml: |
||
128 | continue |
||
129 | |||
130 | if getattr(execution, 'parent', None) == parent_id: |
||
131 | status = execution.status |
||
132 | name = execution.context['chain']['name'] |
||
133 | |||
134 | if status == LIVEACTION_STATUS_SCHEDULED: |
||
135 | indicator.add_stage(status, name) |
||
136 | if status == LIVEACTION_STATUS_RUNNING: |
||
137 | indicator.update_stage(status, name) |
||
138 | if status in LIVEACTION_COMPLETED_STATES: |
||
139 | indicator.finish_stage(status, name) |
||
140 | |||
141 | if execution and execution.status == LIVEACTION_STATUS_FAILED: |
||
142 | args.depth = 1 |
||
143 | self._print_execution_details(execution=execution, args=args, **kwargs) |
||
144 | sys.exit(1) |
||
145 | |||
146 | return self.app.client.managers['LiveAction'].get_by_id(parent_id, **kwargs) |
||
147 | |||
148 | |||
149 | class PackListCommand(resource.ResourceListCommand): |
||
150 | display_attributes = ['ref', 'name', 'description', 'version', 'author'] |
||
151 | attribute_display_order = ['ref', 'name', 'description', 'version', 'author'] |
||
152 | |||
153 | |||
154 | class PackGetCommand(resource.ResourceGetCommand): |
||
155 | pk_argument_name = 'ref' |
||
156 | display_attributes = ['name', 'version', 'author', 'email', 'keywords', 'description'] |
||
157 | attribute_display_order = ['name', 'version', 'author', 'email', 'keywords', 'description'] |
||
158 | help_string = 'Get information about an installed pack.' |
||
159 | |||
160 | |||
161 | class PackShowCommand(PackResourceCommand): |
||
162 | def __init__(self, resource, *args, **kwargs): |
||
0 ignored issues
–
show
|
|||
163 | help_string = ('Get information about an available %s from the index.' % |
||
164 | resource.get_display_name().lower()) |
||
165 | super(PackShowCommand, self).__init__(resource, 'show', help_string, |
||
166 | *args, **kwargs) |
||
167 | |||
168 | self.parser.add_argument('pack', |
||
169 | help='Name of the %s to show.' % |
||
170 | resource.get_display_name().lower()) |
||
171 | |||
172 | @add_auth_token_to_kwargs_from_cli |
||
173 | def run(self, args, **kwargs): |
||
174 | return self.manager.search(args, **kwargs) |
||
175 | |||
176 | |||
177 | class PackInstallCommand(PackAsyncCommand): |
||
178 | 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 25 ).
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(PackInstallCommand, self).__init__(resource, 'install', 'Install new %s.' |
||
180 | % resource.get_plural_display_name().lower(), |
||
181 | *args, **kwargs) |
||
182 | |||
183 | self.parser.add_argument('packs', |
||
184 | nargs='+', |
||
185 | metavar='pack', |
||
186 | help='Name of the %s in Exchange, or a git repo URL.' % |
||
187 | resource.get_plural_display_name().lower()) |
||
188 | self.parser.add_argument('--python3', |
||
189 | action='store_true', |
||
190 | default=False, |
||
191 | help='Use Python 3 binary for pack virtual environment.') |
||
192 | self.parser.add_argument('--force', |
||
193 | action='store_true', |
||
194 | default=False, |
||
195 | help='Force pack installation.') |
||
196 | |||
197 | def run(self, args, **kwargs): |
||
198 | self._get_content_counts_for_pack(args, **kwargs) |
||
199 | return self.manager.install(args.packs, python3=args.python3, force=args.force, **kwargs) |
||
200 | |||
201 | def _get_content_counts_for_pack(self, args, **kwargs): |
||
202 | # Global content list, excluding "tests" |
||
203 | # Note: We skip this step for local packs |
||
204 | pack_content = {'actions': 0, 'rules': 0, 'sensors': 0, 'aliases': 0, 'triggers': 0} |
||
205 | |||
206 | if len(args.packs) == 1: |
||
207 | args.pack = args.packs[0] |
||
208 | |||
209 | if args.pack.startswith('file://'): |
||
210 | return |
||
211 | |||
212 | pack_info = self.manager.search(args, ignore_errors=True, **kwargs) |
||
213 | content = getattr(pack_info, 'content', {}) |
||
214 | |||
215 | if content: |
||
216 | for entity in content.keys(): |
||
217 | if entity in pack_content: |
||
218 | pack_content[entity] += content[entity]['count'] |
||
219 | self._print_pack_content(args.packs, pack_content) |
||
220 | |||
221 | else: |
||
222 | pack_content = pack_content.fromkeys(pack_content, 0) |
||
223 | # TODO: Better solution is to update endpoint query param for one API call |
||
224 | # example: ?packs=pack1,pack2,pack3 |
||
225 | for pack in args.packs: |
||
226 | # args.pack required for search |
||
227 | args.pack = pack |
||
228 | |||
229 | if args.pack.startswith('file://'): |
||
230 | return |
||
231 | |||
232 | pack_info = self.manager.search(args, ignore_errors=True, **kwargs) |
||
233 | content = getattr(pack_info, 'content', {}) |
||
234 | |||
235 | if content: |
||
236 | for entity in content.keys(): |
||
237 | if entity in pack_content: |
||
238 | pack_content[entity] += content[entity]['count'] |
||
239 | if content: |
||
240 | self._print_pack_content(args.packs, pack_content) |
||
241 | |||
242 | @staticmethod |
||
243 | def _print_pack_content(pack_name, pack_content): |
||
244 | print('\nFor the "%s" %s, the following content will be registered:\n' |
||
245 | % (', '.join(pack_name), 'pack' if len(pack_name) == 1 else 'packs')) |
||
246 | for item, count in pack_content.items(): |
||
247 | print('%-10s| %s' % (item, count)) |
||
248 | print('\nInstallation may take a while for packs with many items.') |
||
249 | |||
250 | @add_auth_token_to_kwargs_from_cli |
||
251 | def run_and_print(self, args, **kwargs): |
||
252 | instance = super(PackInstallCommand, self).run_and_print(args, **kwargs) |
||
253 | # Hack to get a list of resolved references of installed packs |
||
254 | packs = instance.result['tasks'][1]['result']['result'] |
||
255 | |||
256 | if len(packs) == 1: |
||
257 | pack_instance = self.app.client.managers['Pack'].get_by_ref_or_id(packs[0], **kwargs) |
||
258 | self.print_output(pack_instance, table.PropertyValueTable, |
||
259 | attributes=args.attr, json=args.json, yaml=args.yaml, |
||
260 | attribute_display_order=self.attribute_display_order) |
||
261 | else: |
||
262 | all_pack_instances = self.app.client.managers['Pack'].get_all(**kwargs) |
||
263 | pack_instances = [] |
||
264 | |||
265 | for pack in all_pack_instances: |
||
266 | if pack.name in packs: |
||
267 | pack_instances.append(pack) |
||
268 | |||
269 | self.print_output(pack_instances, table.MultiColumnTable, |
||
270 | attributes=args.attr, widths=args.width, |
||
271 | json=args.json, yaml=args.yaml) |
||
272 | |||
273 | |||
274 | class PackRemoveCommand(PackAsyncCommand): |
||
275 | 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 25 ).
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...
|
|||
276 | super(PackRemoveCommand, self).__init__(resource, 'remove', 'Remove %s.' |
||
277 | % resource.get_plural_display_name().lower(), |
||
278 | *args, **kwargs) |
||
279 | |||
280 | self.parser.add_argument('packs', |
||
281 | nargs='+', |
||
282 | metavar='pack', |
||
283 | help='Name of the %s to remove.' % |
||
284 | resource.get_plural_display_name().lower()) |
||
285 | |||
286 | def run(self, args, **kwargs): |
||
287 | return self.manager.remove(args.packs, **kwargs) |
||
288 | |||
289 | @add_auth_token_to_kwargs_from_cli |
||
290 | def run_and_print(self, args, **kwargs): |
||
291 | all_pack_instances = self.app.client.managers['Pack'].get_all(**kwargs) |
||
292 | |||
293 | super(PackRemoveCommand, self).run_and_print(args, **kwargs) |
||
294 | |||
295 | packs = args.packs |
||
296 | |||
297 | if len(packs) == 1: |
||
298 | pack_instance = self.app.client.managers['Pack'].get_by_ref_or_id(packs[0], **kwargs) |
||
299 | |||
300 | if pack_instance: |
||
301 | raise OperationFailureException('Pack %s has not been removed properly', packs[0]) |
||
302 | |||
303 | removed_pack_instance = next((pack for pack in all_pack_instances |
||
304 | if pack.name == packs[0]), None) |
||
305 | |||
306 | self.print_output(removed_pack_instance, table.PropertyValueTable, |
||
307 | attributes=args.attr, json=args.json, yaml=args.yaml, |
||
308 | attribute_display_order=self.attribute_display_order) |
||
309 | else: |
||
310 | remaining_pack_instances = self.app.client.managers['Pack'].get_all(**kwargs) |
||
311 | pack_instances = [] |
||
312 | |||
313 | for pack in all_pack_instances: |
||
314 | if pack.name in packs: |
||
315 | pack_instances.append(pack) |
||
316 | if pack in remaining_pack_instances: |
||
317 | raise OperationFailureException('Pack %s has not been removed properly', |
||
318 | pack.name) |
||
319 | |||
320 | self.print_output(pack_instances, table.MultiColumnTable, |
||
321 | attributes=args.attr, widths=args.width, |
||
322 | json=args.json, yaml=args.yaml) |
||
323 | |||
324 | |||
325 | class PackRegisterCommand(PackResourceCommand): |
||
326 | 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 25 ).
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...
|
|||
327 | super(PackRegisterCommand, self).__init__(resource, 'register', |
||
328 | 'Register %s(s): sync all file changes with DB.' |
||
329 | % resource.get_display_name().lower(), |
||
330 | *args, **kwargs) |
||
331 | |||
332 | self.parser.add_argument('packs', |
||
333 | nargs='*', |
||
334 | metavar='pack', |
||
335 | help='Name of the %s(s) to register.' % |
||
336 | resource.get_display_name().lower()) |
||
337 | |||
338 | self.parser.add_argument('--types', |
||
339 | nargs='+', |
||
340 | help='Types of content to register.') |
||
341 | |||
342 | @add_auth_token_to_kwargs_from_cli |
||
343 | def run(self, args, **kwargs): |
||
344 | return self.manager.register(args.packs, args.types, **kwargs) |
||
345 | |||
346 | |||
347 | class PackSearchCommand(resource.ResourceTableCommand): |
||
348 | display_attributes = ['name', 'description', 'version', 'author'] |
||
349 | attribute_display_order = ['name', 'description', 'version', 'author'] |
||
350 | |||
351 | 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 25 ).
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...
|
|||
352 | super(PackSearchCommand, self).__init__(resource, 'search', |
||
353 | 'Search the index for a %s with any attribute \ |
||
354 | matching the query.' |
||
355 | % resource.get_display_name().lower(), |
||
356 | *args, **kwargs) |
||
357 | |||
358 | self.parser.add_argument('query', |
||
359 | help='Search query.') |
||
360 | |||
361 | @add_auth_token_to_kwargs_from_cli |
||
362 | def run(self, args, **kwargs): |
||
363 | return self.manager.search(args, **kwargs) |
||
364 | |||
365 | |||
366 | class PackConfigCommand(resource.ResourceCommand): |
||
367 | def __init__(self, resource, *args, **kwargs): |
||
368 | super(PackConfigCommand, self).__init__(resource, 'config', |
||
369 | 'Configure a %s based on config schema.' |
||
370 | % resource.get_display_name().lower(), |
||
371 | *args, **kwargs) |
||
372 | |||
373 | self.parser.add_argument('name', |
||
374 | help='Name of the %s(s) to configure.' % |
||
375 | resource.get_display_name().lower()) |
||
376 | |||
377 | @add_auth_token_to_kwargs_from_cli |
||
378 | def run(self, args, **kwargs): |
||
379 | schema = self.app.client.managers['ConfigSchema'].get_by_ref_or_id(args.name, **kwargs) |
||
380 | |||
381 | if not schema: |
||
382 | msg = '%s "%s" doesn\'t exist or doesn\'t have a config schema defined.' |
||
383 | raise resource.ResourceNotFoundError(msg % (self.resource.get_display_name(), |
||
384 | args.name)) |
||
385 | |||
386 | config = interactive.InteractiveForm(schema.attributes).initiate_dialog() |
||
387 | |||
388 | message = '---\nDo you want to preview the config in an editor before saving?' |
||
389 | description = 'Secrets will be shown in plain text.' |
||
390 | preview_dialog = interactive.Question(message, {'default': 'y', |
||
391 | 'description': description}) |
||
392 | if preview_dialog.read() == 'y': |
||
393 | try: |
||
394 | contents = yaml.safe_dump(config, indent=4, default_flow_style=False) |
||
395 | modified = editor.edit(contents=contents) |
||
396 | config = yaml.safe_load(modified) |
||
397 | except editor.EditorError as e: |
||
398 | print(str(e)) |
||
399 | |||
400 | message = '---\nDo you want me to save it?' |
||
401 | save_dialog = interactive.Question(message, {'default': 'y'}) |
||
402 | if save_dialog.read() == 'n': |
||
403 | raise OperationFailureException('Interrupted') |
||
404 | |||
405 | config_item = Config(pack=args.name, values=config) |
||
406 | result = self.app.client.managers['Config'].update(config_item, **kwargs) |
||
407 | |||
408 | return result |
||
409 | |||
410 | def run_and_print(self, args, **kwargs): |
||
411 | try: |
||
412 | instance = self.run(args, **kwargs) |
||
413 | if not instance: |
||
414 | raise Exception("Configuration failed") |
||
415 | self.print_output(instance, table.PropertyValueTable, |
||
416 | attributes=['all'], json=args.json, yaml=args.yaml) |
||
417 | except (KeyboardInterrupt, SystemExit): |
||
418 | raise OperationFailureException('Interrupted') |
||
419 | except Exception as e: |
||
420 | if self.app.client.debug: |
||
421 | raise |
||
422 | |||
423 | message = e.message or str(e) |
||
424 | print('ERROR: %s' % (message)) |
||
425 | raise OperationFailureException(message) |
||
426 |
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: