Passed
Pull Request — master (#3804)
by Lakshmi
05:45
created

RuleAPI   A

Complexity

Total Complexity 7

Size/Duplication

Total Lines 162
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 162
rs 10
wmc 7

2 Methods

Rating   Name   Duplication   Size   Complexity  
A to_model() 0 53 3
A from_model() 0 17 4
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
import copy
17
18
import six
19
20
from st2common import log as logging
21
from st2common.constants import keyvalue as kv_constants
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.db.rule import RuleDB, RuleTypeDB, RuleTypeSpecDB, ActionExecutionSpecDB
27
from st2common.models.system.common import ResourceReference
28
from st2common.persistence.trigger import Trigger
29
from st2common.services import keyvalues as kv_service
30
import st2common.services.triggers as TriggerService
31
from st2common.util import reference
32
import st2common.util.jinja as jinja_utils
33
import st2common.validators.api.reactor as validator
34
35
LOG = logging.getLogger(__name__)
36
37
38
class RuleTypeSpec(BaseAPI):
39
    schema = {
40
        'type': 'object',
41
        'properties': {
42
            'ref': {
43
                'type': 'string',
44
                'required': True
45
            },
46
            'parameters': {
47
                'type': 'object'
48
            }
49
        },
50
        'additionalProperties': False
51
    }
52
53
54
class ActionSpec(BaseAPI):
55
    schema = {
56
        'type': 'object',
57
        'properties': {
58
            'ref': {
59
                'type': 'string',
60
                'required': True
61
            },
62
            'parameters': {
63
                'type': 'object'
64
            }
65
        },
66
        'additionalProperties': False
67
    }
68
69
70
REQUIRED_ATTR_SCHEMAS = {
71
    'action': copy.deepcopy(ActionSpec.schema)
72
}
73
74
for k, v in six.iteritems(REQUIRED_ATTR_SCHEMAS):
75
    v.update({'required': True})
76
77
78
class RuleTypeAPI(BaseAPI):
79
    model = RuleTypeDB
80
    schema = {
81
        'title': 'RuleType',
82
        'description': 'A specific type of rule.',
83
        'type': 'object',
84
        'properties': {
85
            'id': {
86
                'description': 'The unique identifier for the action runner.',
87
                'type': 'string',
88
                'default': None
89
            },
90
            'name': {
91
                'description': 'The name of the action runner.',
92
                'type': 'string',
93
                'required': True
94
            },
95
            'description': {
96
                'description': 'The description of the action runner.',
97
                'type': 'string'
98
            },
99
            'enabled': {
100
                'type': 'boolean',
101
                'default': True
102
            },
103
            'parameters': {
104
                'type': 'object'
105
            }
106
        },
107
        'additionalProperties': False
108
    }
109
110
    @classmethod
111
    def to_model(cls, rule_type):
112
        name = getattr(rule_type, 'name', None)
113
        description = getattr(rule_type, 'description', None)
114
        enabled = getattr(rule_type, 'enabled', False)
115
        parameters = getattr(rule_type, 'parameters', {})
116
117
        return cls.model(name=name, description=description, enabled=enabled,
118
                         parameters=parameters)
119
120
121
class RuleAPI(BaseAPI, APIUIDMixin):
122
    """
123
    Attribute:
124
        trigger_type: Trigger that trips this rule. Of the form {'id':'1234', 'name':'trigger-1'}.
125
        Only 1 of the id or name is required and if both are specified name is ignored.
126
        criteria: Criteria used to further restrict the trigger that applies to this rule.
127
        e.g.
128
        { "trigger.from" :
129
            { "pattern": "@gmail.com$"
130
            , "type": "matchregex" }
131
        , "trigger.subject" :
132
            { "pattern": "RE:"
133
            , "operator": "contain" }
134
        }
135
        action: Specification of the action to execute and the mappings to apply.
136
        expected arguments are name, parameters.
137
        e.g.
138
        "action":
139
        { "name": "st2.action.foo"
140
        , "parameters":
141
            { "command": "{{ system.foo }}"
142
            , "args": "--email {{ trigger.from }} --subject \'{{ user[stanley].ALERT_SUBJECT }}\'"}
143
        }
144
        status: enabled or disabled. If disabled occurrence of the trigger
145
        does not lead to execution of a action and vice-versa.
146
    """
147
    model = RuleDB
148
    schema = {
149
        'type': 'object',
150
        'properties': {
151
            'id': {
152
                'type': 'string',
153
                'default': None
154
            },
155
            "ref": {
156
                "description": "System computed user friendly reference for the action. \
157
                                Provided value will be overridden by computed value.",
158
                "type": "string"
159
            },
160
            'uid': {
161
                'type': 'string'
162
            },
163
            'name': {
164
                'type': 'string',
165
                'required': True
166
            },
167
            'pack': {
168
                'type': 'string',
169
                'default': DEFAULT_PACK_NAME
170
            },
171
            'description': {
172
                'type': 'string'
173
            },
174
            'type': RuleTypeSpec.schema,
175
            'trigger': {
176
                'type': 'object',
177
                'required': True,
178
                'properties': {
179
                    'type': {
180
                        'type': 'string',
181
                        'required': True
182
                    },
183
                    'parameters': {
184
                        'type': 'object',
185
                        'default': {}
186
                    },
187
                    'ref': {
188
                        'type': 'string',
189
                        'required': False
190
                    }
191
                },
192
                'additionalProperties': True
193
            },
194
            'criteria': {
195
                'type': 'object',
196
                'default': {}
197
            },
198
            'action': REQUIRED_ATTR_SCHEMAS['action'],
199
            'enabled': {
200
                'type': 'boolean',
201
                'default': False
202
            },
203
            "tags": {
204
                "description": "User associated metadata assigned to this object.",
205
                "type": "array",
206
                "items": {"type": "object"}
207
            }
208
        },
209
        'additionalProperties': False
210
    }
211
212
    @classmethod
213
    def from_model(cls, model, mask_secrets=False, ignore_missing_trigger=False):
0 ignored issues
show
Bug introduced by
Arguments number differs from overridden 'from_model' method
Loading history...
214
        rule = cls._from_model(model, mask_secrets=mask_secrets)
215
        trigger_db = reference.get_model_by_resource_ref(Trigger, model.trigger)
216
217
        if not ignore_missing_trigger and not trigger_db:
218
            raise ValueError('Missing TriggerDB object for rule %s' % (rule['id']))
219
220
        if trigger_db:
221
            rule['trigger'] = {
222
                'type': trigger_db.type,
223
                'parameters': trigger_db.parameters,
224
                'ref': model.trigger
225
            }
226
227
        rule['tags'] = TagsHelper.from_model(model.tags)
228
        return cls(**rule)
229
230
    @classmethod
231
    def to_model(cls, rule):
232
        kwargs = {}
233
        kwargs['name'] = getattr(rule, 'name', None)
234
        kwargs['description'] = getattr(rule, 'description', None)
235
236
        # Validate trigger parameters
237
        # Note: This must happen before we create a trigger, otherwise create trigger could fail
238
        # with a cryptic error
239
        trigger = getattr(rule, 'trigger', {})
240
        trigger_type_ref = trigger.get('type', None)
241
        parameters = trigger.get('parameters', {})
242
243
        if parameters:
244
            context = {}
245
            context.update({
246
                kv_constants.DATASTORE_PARENT_SCOPE: {
247
                    kv_constants.SYSTEM_SCOPE: kv_service.KeyValueLookup(
248
                        scope=kv_constants.FULL_SYSTEM_SCOPE)
249
                }
250
            })
251
            parameters = jinja_utils.render_values(mapping=parameters, context=context,
252
                                                   allow_undefined=True)
253
            rule.trigger['parameters'] = parameters
254
            LOG.debug('Rendered trigger parameters: %s', parameters)
255
            validator.validate_trigger_parameters(trigger_type_ref=trigger_type_ref,
256
                                                  parameters=parameters)
257
258
        # Create a trigger for the provided rule
259
        trigger_db = TriggerService.create_trigger_db_from_rule(rule)
260
        kwargs['trigger'] = reference.get_str_resource_ref_from_model(trigger_db)
261
262
        kwargs['pack'] = getattr(rule, 'pack', DEFAULT_PACK_NAME)
263
        kwargs['ref'] = ResourceReference.to_string_reference(pack=kwargs['pack'],
264
                                                              name=kwargs['name'])
265
266
        # Validate criteria
267
        kwargs['criteria'] = dict(getattr(rule, 'criteria', {}))
268
        validator.validate_criteria(kwargs['criteria'])
269
270
        kwargs['action'] = ActionExecutionSpecDB(ref=rule.action['ref'],
271
                                                 parameters=rule.action.get('parameters', {}))
272
273
        rule_type = dict(getattr(rule, 'type', {}))
274
        if rule_type:
275
            kwargs['type'] = RuleTypeSpecDB(ref=rule_type['ref'],
276
                                            parameters=rule_type.get('parameters', {}))
277
278
        kwargs['enabled'] = getattr(rule, 'enabled', False)
279
        kwargs['tags'] = TagsHelper.to_model(getattr(rule, 'tags', []))
280
281
        model = cls.model(**kwargs)
282
        return model
283
284
285
class RuleViewAPI(RuleAPI):
286
287
    # Always deep-copy to avoid breaking the original.
288
    schema = copy.deepcopy(RuleAPI.schema)
289
    # Update the schema to include the description properties
290
    schema['properties']['action'].update({
291
        'description': {
292
            'type': 'string'
293
        }
294
    })
295
    schema['properties']['trigger'].update({
296
        'description': {
297
            'type': 'string'
298
        }
299
    })
300