Test Failed
Push — master ( e380d0...f5671d )
by W
02:58
created

st2common/st2common/models/api/action.py (1 issue)

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 copy
18
19
from st2common.util import isotime
20
from st2common.util import schema as util_schema
21
from st2common import log as logging
22
from st2common.constants.pack import DEFAULT_PACK_NAME
23
from st2common.models.api.base import BaseAPI
24
from st2common.models.api.base import APIUIDMixin
25
from st2common.models.api.tag import TagsHelper
26
from st2common.models.api.notification import (NotificationSubSchemaAPI, NotificationsHelper)
27
from st2common.models.db.action import ActionDB
28
from st2common.models.db.actionalias import ActionAliasDB
29
from st2common.models.db.executionstate import ActionExecutionStateDB
30
from st2common.models.db.liveaction import LiveActionDB
31
from st2common.models.db.runner import RunnerTypeDB
32
from st2common.constants.action import LIVEACTION_STATUSES
33
from st2common.models.system.common import ResourceReference
34
35
36
__all__ = [
37
    'ActionAPI',
38
    'ActionCreateAPI',
39
    'LiveActionAPI',
40
    'LiveActionCreateAPI',
41
    'RunnerTypeAPI',
42
43
    'AliasExecutionAPI',
44
    'AliasMatchAndExecuteInputAPI',
45
    'ActionAliasAPI',
46
    'ActionAliasMatchAPI',
47
    'ActionAliasHelpAPI'
48
]
49
50
51
LOG = logging.getLogger(__name__)
52
53
54
class RunnerTypeAPI(BaseAPI):
55
    """
56
    The representation of an RunnerType in the system. An RunnerType
57
    has a one-to-one mapping to a particular ActionRunner implementation.
58
    """
59
    model = RunnerTypeDB
60
    schema = {
61
        "title": "Runner",
62
        "description": "A handler for a specific type of actions.",
63
        "type": "object",
64
        "properties": {
65
            "id": {
66
                "description": "The unique identifier for the action runner.",
67
                "type": "string",
68
                "default": None
69
            },
70
            "uid": {
71
                "type": "string"
72
            },
73
            "name": {
74
                "description": "The name of the action runner.",
75
                "type": "string",
76
                "required": True
77
            },
78
            "description": {
79
                "description": "The description of the action runner.",
80
                "type": "string"
81
            },
82
            "enabled": {
83
                "description": "Enable or disable the action runner.",
84
                "type": "boolean",
85
                "default": True
86
            },
87
            "runner_package": {
88
                "description": "The python package that implements the "
89
                               "action runner for this type.",
90
                "type": "string",
91
                "required": False
92
            },
93
            "runner_module": {
94
                "description": "The python module that implements the "
95
                               "action runner for this type.",
96
                "type": "string",
97
                "required": True
98
            },
99
            "query_module": {
100
                "description": "The python module that implements the "
101
                               "results tracker (querier) for the runner.",
102
                "type": "string",
103
                "required": False
104
            },
105
            "runner_parameters": {
106
                "description": "Input parameters for the action runner.",
107
                "type": "object",
108
                "patternProperties": {
109
                    "^\w+$": util_schema.get_action_parameters_schema()
110
                },
111
                'additionalProperties': False
112
            }
113
        },
114
        "additionalProperties": False
115
    }
116
117
    def __init__(self, **kw):
118
        # Ideally, you should not do that. You should not redefine __init__ to validate and then set
119
        # default values, instead you should define defaults in schema and use BaseAPI __init__
120
        # validator to unwrap them. The problem here is that draft schema also contains default
121
        # values and we don't want them to be unwrapped at the same time. I've tried to remove the
122
        # default values from draft schema, but, either because of a bug or some weird intention, it
123
        # has continued to resolve $ref'erenced properties against the initial draft schema, not the
124
        # modified one
125
        for key, value in kw.items():
126
            setattr(self, key, value)
127
        if not hasattr(self, 'runner_parameters'):
128
            setattr(self, 'runner_parameters', dict())
129
130
    @classmethod
131
    def to_model(cls, runner_type):
132
        name = runner_type.name
133
        description = runner_type.description
134
        enabled = getattr(runner_type, 'enabled', True)
135
        runner_package = getattr(runner_type, 'runner_package', runner_type.runner_module)
136
        runner_module = str(runner_type.runner_module)
137
        runner_parameters = getattr(runner_type, 'runner_parameters', dict())
138
        query_module = getattr(runner_type, 'query_module', None)
139
140
        model = cls.model(name=name, description=description, enabled=enabled,
141
                          runner_package=runner_package, runner_module=runner_module,
142
                          runner_parameters=runner_parameters,
143
                          query_module=query_module)
144
145
        return model
146
147
148
class ActionAPI(BaseAPI, APIUIDMixin):
149
    """
150
    The system entity that represents a Stack Action/Automation in the system.
151
    """
152
153
    model = ActionDB
154
    schema = {
155
        "title": "Action",
156
        "description": "An activity that happens as a response to the external event.",
157
        "type": "object",
158
        "properties": {
159
            "id": {
160
                "description": "The unique identifier for the action.",
161
                "type": "string"
162
            },
163
            "ref": {
164
                "description": "System computed user friendly reference for the action. \
165
                                Provided value will be overridden by computed value.",
166
                "type": "string"
167
            },
168
            "uid": {
169
                "type": "string"
170
            },
171
            "name": {
172
                "description": "The name of the action.",
173
                "type": "string",
174
                "required": True
175
            },
176
            "description": {
177
                "description": "The description of the action.",
178
                "type": "string"
179
            },
180
            "enabled": {
181
                "description": "Enable or disable the action from invocation.",
182
                "type": "boolean",
183
                "default": True
184
            },
185
            "runner_type": {
186
                "description": "The type of runner that executes the action.",
187
                "type": "string",
188
                "required": True
189
            },
190
            "entry_point": {
191
                "description": "The entry point for the action.",
192
                "type": "string",
193
                "default": ""
194
            },
195
            "pack": {
196
                "description": "The content pack this action belongs to.",
197
                "type": "string",
198
                "default": DEFAULT_PACK_NAME
199
            },
200
            "parameters": {
201
                "description": "Input parameters for the action.",
202
                "type": "object",
203
                "patternProperties": {
204
                    "^\w+$": util_schema.get_action_parameters_schema()
205
                },
206
                'additionalProperties': False,
207
                "default": {}
208
            },
209
            "tags": {
210
                "description": "User associated metadata assigned to this object.",
211
                "type": "array",
212
                "items": {"type": "object"}
213
            },
214
            "notify": {
215
                "description": "Notification settings for action.",
216
                "type": "object",
217
                "properties": {
218
                    "on-complete": NotificationSubSchemaAPI,
219
                    "on-failure": NotificationSubSchemaAPI,
220
                    "on-success": NotificationSubSchemaAPI
221
                },
222
                "additionalProperties": False
223
            }
224
        },
225
        "additionalProperties": False
226
    }
227
228
    def __init__(self, **kw):
229
        for key, value in kw.items():
230
            setattr(self, key, value)
231
        if not hasattr(self, 'parameters'):
232
            setattr(self, 'parameters', dict())
233
        if not hasattr(self, 'entry_point'):
234
            setattr(self, 'entry_point', '')
235
236
    @classmethod
237
    def from_model(cls, model, mask_secrets=False):
238
        action = cls._from_model(model)
239
        action['runner_type'] = action['runner_type']['name']
240
        action['tags'] = TagsHelper.from_model(model.tags)
241
242
        if getattr(model, 'notify', None):
243
            action['notify'] = NotificationsHelper.from_model(model.notify)
244
245
        return cls(**action)
246
247
    @classmethod
248
    def to_model(cls, action):
249
        name = getattr(action, 'name', None)
250
        description = getattr(action, 'description', None)
251
        enabled = bool(getattr(action, 'enabled', True))
252
        entry_point = str(action.entry_point)
253
        pack = str(action.pack)
254
        runner_type = {'name': str(action.runner_type)}
255
        parameters = getattr(action, 'parameters', dict())
256
        tags = TagsHelper.to_model(getattr(action, 'tags', []))
257
        ref = ResourceReference.to_string_reference(pack=pack, name=name)
258
259
        if getattr(action, 'notify', None):
260
            notify = NotificationsHelper.to_model(action.notify)
261
        else:
262
            # We use embedded document model for ``notify`` in action model. If notify is
263
            # set notify to None, Mongoengine interprets ``None`` as unmodified
264
            # field therefore doesn't delete the embedded document. Therefore, we need
265
            # to use an empty document.
266
            notify = NotificationsHelper.to_model({})
267
268
        model = cls.model(name=name, description=description, enabled=enabled,
269
                          entry_point=entry_point, pack=pack, runner_type=runner_type,
270
                          tags=tags, parameters=parameters, notify=notify,
271
                          ref=ref)
272
273
        return model
274
275
276
class ActionCreateAPI(ActionAPI, APIUIDMixin):
277
    """
278
    API model for create action operation.
279
    """
280
    schema = copy.deepcopy(ActionAPI.schema)
281
    schema['properties']['data_files'] = {
282
        'description': 'Optional action script and data files which are written to the filesystem.',
283
        'type': 'array',
284
        'items': {
285
            'type': 'object',
286
            'properties': {
287
                'file_path': {
288
                    'type': 'string',
289
                    'description': ('Path to the file relative to the pack actions directory '
290
                                    '(e.g. my_action.py)'),
291
                    'required': True
292
                },
293
                'content': {
294
                    'type': 'string',
295
                    'description': 'Raw file content.',
296
                    'required': True
297
                },
298
            },
299
            'additionalProperties': False
300
        },
301
        'default': []
302
    }
303
304
305
class ActionUpdateAPI(ActionAPI, APIUIDMixin):
306
    """
307
    API model for update action operation.
308
    """
309
    schema = copy.deepcopy(ActionCreateAPI.schema)
310
    del schema['properties']['pack']['default']
311
312
313
class LiveActionAPI(BaseAPI):
314
    """The system entity that represents the execution of a Stack Action/Automation
315
    in the system.
316
    """
317
318
    model = LiveActionDB
319
    schema = {
320
        "title": "liveaction",
321
        "description": "An execution of an action.",
322
        "type": "object",
323
        "properties": {
324
            "id": {
325
                "description": "The unique identifier for the action execution.",
326
                "type": "string"
327
            },
328
            "status": {
329
                "description": "The current status of the action execution.",
330
                "type": "string",
331
                "enum": LIVEACTION_STATUSES
332
            },
333
            "start_timestamp": {
334
                "description": "The start time when the action is executed.",
335
                "type": "string",
336
                "pattern": isotime.ISO8601_UTC_REGEX
337
            },
338
            "end_timestamp": {
339
                "description": "The timestamp when the action has finished.",
340
                "type": "string",
341
                "pattern": isotime.ISO8601_UTC_REGEX
342
            },
343
            "action": {
344
                "description": "Reference to the action to be executed.",
345
                "type": "string",
346
                "required": True
347
            },
348
            "parameters": {
349
                "description": "Input parameters for the action.",
350
                "type": "object",
351
                "patternProperties": {
352
                    "^\w+$": {
353
                        "anyOf": [
354
                            {"type": "array"},
355
                            {"type": "boolean"},
356
                            {"type": "integer"},
357
                            {"type": "number"},
358
                            {"type": "object"},
359
                            {"type": "string"},
360
                            {"type": "null"}
361
                        ]
362
                    }
363
                },
364
                'additionalProperties': False
365
            },
366
            "result": {
367
                "anyOf": [{"type": "array"},
368
                          {"type": "boolean"},
369
                          {"type": "integer"},
370
                          {"type": "number"},
371
                          {"type": "object"},
372
                          {"type": "string"}]
373
            },
374
            "context": {
375
                "type": "object"
376
            },
377
            "callback": {
378
                "type": "object"
379
            },
380
            "runner_info": {
381
                "type": "object"
382
            },
383
            "notify": {
384
                "description": "Notification settings for liveaction.",
385
                "type": "object",
386
                "properties": {
387
                    "on-complete": NotificationSubSchemaAPI,
388
                    "on-failure": NotificationSubSchemaAPI,
389
                    "on-success": NotificationSubSchemaAPI
390
                },
391
                "additionalProperties": False
392
            }
393
        },
394
        "additionalProperties": False
395
    }
396
397
    @classmethod
398
    def from_model(cls, model, mask_secrets=False):
399
        doc = super(cls, cls)._from_model(model, mask_secrets=mask_secrets)
400
        if model.start_timestamp:
401
            doc['start_timestamp'] = isotime.format(model.start_timestamp, offset=False)
402
        if model.end_timestamp:
403
            doc['end_timestamp'] = isotime.format(model.end_timestamp, offset=False)
404
405
        if getattr(model, 'notify', None):
406
            doc['notify'] = NotificationsHelper.from_model(model.notify)
407
408
        return cls(**doc)
409
410
    @classmethod
411
    def to_model(cls, live_action):
412
        action = live_action.action
413
414
        if getattr(live_action, 'start_timestamp', None):
415
            start_timestamp = isotime.parse(live_action.start_timestamp)
416
        else:
417
            start_timestamp = None
418
419
        if getattr(live_action, 'end_timestamp', None):
420
            end_timestamp = isotime.parse(live_action.end_timestamp)
421
        else:
422
            end_timestamp = None
423
424
        status = getattr(live_action, 'status', None)
425
        parameters = getattr(live_action, 'parameters', dict())
426
        context = getattr(live_action, 'context', dict())
427
        callback = getattr(live_action, 'callback', dict())
428
        result = getattr(live_action, 'result', None)
429
430
        if getattr(live_action, 'notify', None):
431
            notify = NotificationsHelper.to_model(live_action.notify)
432
        else:
433
            notify = None
434
435
        model = cls.model(action=action,
436
                          start_timestamp=start_timestamp, end_timestamp=end_timestamp,
437
                          status=status, parameters=parameters, context=context,
438
                          callback=callback, result=result, notify=notify)
439
440
        return model
441
442
443
class LiveActionCreateAPI(LiveActionAPI):
444
    """
445
    API model for action execution create (run action) operations.
446
    """
447
    schema = copy.deepcopy(LiveActionAPI.schema)
448
    schema['properties']['user'] = {
449
        'description': 'User context under which action should run (admins only)',
450
        'type': 'string',
451
        'default': None
452
    }
453
454
455
class ActionExecutionStateAPI(BaseAPI):
456
    """
457
    System entity that represents state of an action in the system.
458
    This is used only in tests for now.
459
    """
460
    model = ActionExecutionStateDB
461
    schema = {
462
        "title": "ActionExecutionState",
463
        "description": "Execution state of an action.",
464
        "type": "object",
465
        "properties": {
466
            "id": {
467
                "description": "The unique identifier for the action execution state.",
468
                "type": "string"
469
            },
470
            "execution_id": {
471
                "type": "string",
472
                "description": "ID of the action execution.",
473
                "required": True
474
            },
475
            "query_context": {
476
                "type": "object",
477
                "description": "query context to be used by querier.",
478
                "required": True
479
            },
480
            "query_module": {
481
                "type": "string",
482
                "description": "Name of the query module.",
483
                "required": True
484
            }
485
        },
486
        "additionalProperties": False
487
    }
488
489
    @classmethod
490
    def to_model(cls, state):
491
        execution_id = state.execution_id
492
        query_module = state.query_module
493
        query_context = state.query_context
494
495
        model = cls.model(execution_id=execution_id, query_module=query_module,
496
                          query_context=query_context)
497
        return model
498
499
500
class ActionAliasAPI(BaseAPI, APIUIDMixin):
501
    """
502
    Alias for an action in the system.
503
    """
504
    model = ActionAliasDB
505
    schema = {
506
        "title": "ActionAlias",
507
        "description": "Alias for an action.",
508
        "type": "object",
509
        "properties": {
510
            "id": {
511
                "description": "The unique identifier for the action alias.",
512
                "type": "string"
513
            },
514
            "ref": {
515
                "description": "System computed user friendly reference for the alias. \
516
                                Provided value will be overridden by computed value.",
517
                "type": "string"
518
            },
519
            "uid": {
520
                "type": "string"
521
            },
522
            "name": {
523
                "type": "string",
524
                "description": "Name of the action alias.",
525
                "required": True
526
            },
527
            "pack": {
528
                "description": "The content pack this actionalias belongs to.",
529
                "type": "string",
530
                "required": True
531
            },
532
            "description": {
533
                "type": "string",
534
                "description": "Description of the action alias.",
535
                "default": None
536
            },
537
            "enabled": {
538
                "description": "Flag indicating of action alias is enabled.",
539
                "type": "boolean",
540
                "default": True
541
            },
542
            "action_ref": {
543
                "type": "string",
544
                "description": "Reference to the aliased action.",
545
                "required": True
546
            },
547
            "formats": {
548
                "type": "array",
549
                "items": {
550
                    "anyOf": [
551
                        {"type": "string"},
552
                        {
553
                            "type": "object",
554
                            "properties": {
555
                                "display": {"type": "string"},
556
                                "representation": {
557
                                    "type": "array",
558
                                    "items": {"type": "string"}
559
                                }
560
                            }
561
                        }
562
                    ]
563
                },
564
                "description": "Possible parameter format."
565
            },
566
            "ack": {
567
                "type": "object",
568
                "properties": {
569
                    "enabled": {"type": "boolean"},
570
                    "format": {"type": "string"},
571
                    "extra": {"type": "object"},
572
                    "append_url": {"type": "boolean"}
573
                },
574
                "description": "Acknowledgement message format."
575
            },
576
            "result": {
577
                "type": "object",
578
                "properties": {
579
                    "enabled": {"type": "boolean"},
580
                    "format": {"type": "string"},
581
                    "extra": {"type": "object"}
582
                },
583
                "description": "Execution message format."
584
            },
585
            "extra": {
586
                "type": "object",
587
                "description": "Extra parameters, usually adapter-specific."
588
            }
589
        },
590
        "additionalProperties": False
591
    }
592
593
    @classmethod
594
    def to_model(cls, alias):
595
        name = alias.name
596
        description = getattr(alias, 'description', None)
597
        pack = alias.pack
598
        ref = ResourceReference.to_string_reference(pack=pack, name=name)
599
        enabled = getattr(alias, 'enabled', True)
600
        action_ref = alias.action_ref
601
        formats = alias.formats
602
        ack = getattr(alias, 'ack', None)
603
        result = getattr(alias, 'result', None)
604
        extra = getattr(alias, 'extra', None)
605
606
        model = cls.model(name=name, description=description, pack=pack, ref=ref,
607
                          enabled=enabled, action_ref=action_ref, formats=formats,
608
                          ack=ack, result=result, extra=extra)
609
        return model
610
611
612
class AliasExecutionAPI(BaseAPI):
613
    """
614
    Alias for an action in the system.
615
    """
616
    model = None
617
    schema = {
618
        "title": "AliasExecution",
619
        "description": "Execution of an ActionAlias.",
620
        "type": "object",
621
        "properties": {
622
            "name": {
623
                "type": "string",
624
                "description": "Name of the action alias which matched.",
625
                "required": True
626
            },
627
            "format": {
628
                "type": "string",
629
                "description": "Format string which matched.",
630
                "required": True
631
            },
632
            "command": {
633
                "type": "string",
634
                "description": "Command used in chat.",
635
                "required": True
636
            },
637
            "user": {
638
                "type": "string",
639
                "description": "User that requested the execution.",
640
                "default": "channel"  # TODO: This value doesnt get set
641
            },
642
            "source_channel": {
643
                "type": "string",
644
                "description": "Channel from which the execution was requested. This is not the \
645
                                channel as defined by the notification system.",
646
                "required": True
647
            },
648
            "notification_channel": {
649
                "type": "string",
650
                "description": "StackStorm notification channel to use to respond.",
651
                "required": False
652
            },
653
            "notification_route": {
654
                "type": "string",
655
                "description": "StackStorm notification route to use to respond.",
656
                "required": False
657
            }
658
        },
659
        "additionalProperties": False
660
    }
661
662
    @classmethod
663
    def to_model(cls, aliasexecution):
664
        # probably should be unsupported
665
        raise NotImplementedError()
666
667
    @classmethod
668
    def from_model(cls, aliasexecution):
0 ignored issues
show
Arguments number differs from overridden 'from_model' method
Loading history...
669
        raise NotImplementedError()
670
671
672
class AliasMatchAndExecuteInputAPI(BaseAPI):
673
    """
674
    API object used for alias execution "match and execute" API endpoint request payload.
675
    """
676
    model = None
677
    schema = {
678
        "title": "ActionAliasMatchAndExecuteInputAPI",
679
        "description": "Input for alias execution match and execute API.",
680
        "type": "object",
681
        "properties": {
682
            "command": {
683
                "type": "string",
684
                "description": "Command used in chat.",
685
                "required": True
686
            },
687
            "user": {
688
                "type": "string",
689
                "description": "User that requested the execution.",
690
            },
691
            "source_channel": {
692
                "type": "string",
693
                "description": "Channel from which the execution was requested. This is not the \
694
                                channel as defined by the notification system.",
695
                "required": True
696
            },
697
            "notification_channel": {
698
                "type": "string",
699
                "description": "StackStorm notification channel to use to respond.",
700
                "required": False,
701
                "default": None
702
            },
703
            "notification_route": {
704
                "type": "string",
705
                "description": "StackStorm notification route to use to respond.",
706
                "required": False,
707
                "default": None
708
            }
709
        },
710
        "additionalProperties": False
711
    }
712
713
714
class ActionAliasMatchAPI(BaseAPI):
715
    """
716
    API model used for alias match API endpoint.
717
    """
718
    model = None
719
720
    schema = {
721
        "title": "ActionAliasMatchAPI",
722
        "description": "ActionAliasMatchAPI.",
723
        "type": "object",
724
        "properties": {
725
            "command": {
726
                "type": "string",
727
                "description": "Command string to try to match the aliases against.",
728
                "required": True
729
            }
730
        },
731
        "additionalProperties": False
732
    }
733
734
    @classmethod
735
    def to_model(cls, aliasexecution):
736
        raise NotImplementedError()
737
738
    @classmethod
739
    def from_model(cls, aliasexecution):
740
        raise NotImplementedError()
741
742
743
class ActionAliasHelpAPI(BaseAPI):
744
    """
745
    API model used to display action-alias help API endpoint.
746
    """
747
    model = None
748
749
    schema = {
750
        "title": "ActionAliasHelpAPI",
751
        "description": "ActionAliasHelpAPI.",
752
        "type": "object",
753
        "properties": {
754
            "filter": {
755
                "type": "string",
756
                "description": "Find help strings containing keyword.",
757
                "required": False,
758
                "default": ""
759
            },
760
            "pack": {
761
                "type": "string",
762
                "description": "List help strings for a specific pack.",
763
                "required": False,
764
                "default": ""
765
            },
766
            "offset": {
767
                "type": "integer",
768
                "description": "List help strings from the offset position.",
769
                "required": False,
770
                "default": 0
771
            },
772
            "limit": {
773
                "type": "integer",
774
                "description": "Limit the number of help strings returned.",
775
                "required": False,
776
                "default": 0
777
            }
778
        },
779
        "additionalProperties": False
780
    }
781
782
    @classmethod
783
    def to_model(cls, aliasexecution):
784
        raise NotImplementedError()
785
786
    @classmethod
787
    def from_model(cls, aliasexecution):
788
        raise NotImplementedError()
789