Passed
Push — develop ( 71cfc9...483b2a )
by Plexxi
07:06 queued 03:42
created

KeyValuePairController.put()   C

Complexity

Conditions 7

Size

Total Lines 49

Duplication

Lines 0
Ratio 0 %

Importance

Changes 7
Bugs 1 Features 0
Metric Value
cc 7
c 7
b 1
f 0
dl 0
loc 49
rs 5.5
1
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
0 ignored issues
show
Bug introduced by
There seems to be a cyclic import (st2api.controllers.v1.packs -> st2api.controllers.v1.packviews).

Cyclic imports may cause partly loaded modules to be returned. This might lead to unexpected runtime behavior which is hard to debug.

Loading history...
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 pecan import abort
17
import six
18
from mongoengine import ValidationError
19
20
from st2api.controllers.resource import ResourceController
21
from st2common import log as logging
22
from st2common.constants.keyvalue import SYSTEM_SCOPE, USER_SCOPE, ALLOWED_SCOPES
23
from st2common.exceptions.db import StackStormDBObjectNotFoundError
24
from st2common.exceptions.keyvalue import CryptoKeyNotSetupException, InvalidScopeException
25
from st2common.models.api.keyvalue import KeyValuePairAPI
26
from st2common.models.api.base import jsexpose
27
from st2common.persistence.keyvalue import KeyValuePair
28
from st2common.services import coordination
29
from st2common.services.keyvalues import get_key_reference
30
from st2common.util.api import get_requester
31
32
http_client = six.moves.http_client
33
34
LOG = logging.getLogger(__name__)
35
36
__all__ = [
37
    'KeyValuePairController'
38
]
39
40
41
class KeyValuePairController(ResourceController):
42
    """
43
    Implements the REST endpoint for managing the key value store.
44
    """
45
46
    model = KeyValuePairAPI
47
    access = KeyValuePair
48
    supported_filters = {
49
        'prefix': 'name__startswith',
50
        'scope': 'scope'
51
    }
52
53
    def __init__(self):
54
        super(KeyValuePairController, self).__init__()
55
        self._coordinator = coordination.get_coordinator()
56
        self.get_one_db_method = self._get_by_name
57
58
    @jsexpose(arg_types=[str, str, bool])
59
    def get_one(self, name, scope=SYSTEM_SCOPE, decrypt=False):
0 ignored issues
show
Bug introduced by
Arguments number differs from overridden 'get_one' method
Loading history...
60
        """
61
            List key by name.
62
63
            Handle:
64
                GET /keys/key1
65
        """
66
        self._validate_scope(scope=scope)
67
        key_ref = get_key_reference(scope=scope, name=name, user=get_requester())
68
        from_model_kwargs = {'mask_secrets': not decrypt}
69
        try:
70
            kvp_api = self._get_one_by_scope_and_name(
71
                name=key_ref,
72
                scope=scope,
73
                from_model_kwargs=from_model_kwargs
74
            )
75
        except StackStormDBObjectNotFoundError as e:
76
            abort(http_client.NOT_FOUND, e.message)
77
            return
78
79
        return kvp_api
80
81
    @jsexpose(arg_types=[str, str, bool])
82
    def get_all(self, prefix=None, scope=SYSTEM_SCOPE, decrypt=False, **kwargs):
83
        """
84
            List all keys.
85
86
            Handles requests:
87
                GET /keys/
88
        """
89
        from_model_kwargs = {'mask_secrets': not decrypt}
90
        kwargs['prefix'] = prefix
91
        if scope:
92
            self._validate_scope(scope=scope)
93
            kwargs['scope'] = scope
94
95
        if scope == USER_SCOPE and kwargs['prefix']:
96
            kwargs['prefix'] = get_key_reference(name=kwargs['prefix'], scope=scope,
97
                                                 user=get_requester())
98
99
        kvp_apis = super(KeyValuePairController, self)._get_all(from_model_kwargs=from_model_kwargs,
100
                                                                **kwargs)
101
        return kvp_apis
102
103
    @jsexpose(arg_types=[str, str, str], body_cls=KeyValuePairAPI)
104
    def put(self, name, kvp, scope=SYSTEM_SCOPE):
105
        """
106
        Create a new entry or update an existing one.
107
        """
108
        scope = getattr(kvp, 'scope', scope)
109
        self._validate_scope(scope=scope)
110
        key_ref = get_key_reference(scope=scope, name=name, user=get_requester())
111
        lock_name = self._get_lock_name_for_key(name=key_ref, scope=scope)
112
        LOG.debug('PUT scope: %s, name: %s', scope, name)
113
        # TODO: Custom permission check since the key doesn't need to exist here
114
115
        # Note: We use lock to avoid a race
116
        with self._coordinator.get_lock(lock_name):
117
            try:
118
                existing_kvp_api = self._get_one_by_scope_and_name(
119
                    scope=scope,
120
                    name=key_ref
121
                )
122
            except StackStormDBObjectNotFoundError:
123
                existing_kvp_api = None
124
125
            kvp.name = key_ref
126
            kvp.scope = scope
127
128
            try:
129
                kvp_db = KeyValuePairAPI.to_model(kvp)
130
131
                if existing_kvp_api:
132
                    kvp_db.id = existing_kvp_api.id
133
134
                kvp_db = KeyValuePair.add_or_update(kvp_db)
135
            except (ValidationError, ValueError) as e:
136
                LOG.exception('Validation failed for key value data=%s', kvp)
137
                abort(http_client.BAD_REQUEST, str(e))
138
                return
139
            except CryptoKeyNotSetupException as e:
140
                LOG.exception(str(e))
141
                abort(http_client.BAD_REQUEST, str(e))
142
                return
143
            except InvalidScopeException as e:
144
                LOG.exception(str(e))
145
                abort(http_client.BAD_REQUEST, str(e))
146
                return
147
        extra = {'kvp_db': kvp_db}
148
        LOG.audit('KeyValuePair updated. KeyValuePair.id=%s' % (kvp_db.id), extra=extra)
149
150
        kvp_api = KeyValuePairAPI.from_model(kvp_db)
151
        return kvp_api
152
153
    @jsexpose(arg_types=[str, str], status_code=http_client.NO_CONTENT)
154
    def delete(self, name, scope=SYSTEM_SCOPE):
155
        """
156
            Delete the key value pair.
157
158
            Handles requests:
159
                DELETE /keys/1
160
        """
161
        self._validate_scope(scope=scope)
162
        key_ref = get_key_reference(scope=scope, name=name, user=get_requester())
163
        lock_name = self._get_lock_name_for_key(name=key_ref, scope=scope)
164
165
        # Note: We use lock to avoid a race
166
        with self._coordinator.get_lock(lock_name):
167
            from_model_kwargs = {'mask_secrets': True}
168
            try:
169
                kvp_api = self._get_one_by_scope_and_name(
170
                    name=key_ref,
171
                    scope=scope,
172
                    from_model_kwargs=from_model_kwargs
173
                )
174
            except StackStormDBObjectNotFoundError as e:
175
                abort(http_client.NOT_FOUND, e.message)
176
                return
177
178
            kvp_db = KeyValuePairAPI.to_model(kvp_api)
179
180
            LOG.debug('DELETE /keys/ lookup with scope=%s name=%s found object: %s',
181
                      scope, name, kvp_db)
182
183
            try:
184
                KeyValuePair.delete(kvp_db)
185
            except Exception as e:
186
                LOG.exception('Database delete encountered exception during '
187
                              'delete of name="%s". ', name)
188
                abort(http_client.INTERNAL_SERVER_ERROR, str(e))
189
                return
190
191
        extra = {'kvp_db': kvp_db}
192
        LOG.audit('KeyValuePair deleted. KeyValuePair.id=%s' % (kvp_db.id), extra=extra)
193
194
    def _get_lock_name_for_key(self, name, scope=SYSTEM_SCOPE):
195
        """
196
        Retrieve a coordination lock name for the provided datastore item name.
197
198
        :param name: Datastore item name (PK).
199
        :type name: ``str``
200
        """
201
        lock_name = 'kvp-crud-%s.%s' % (scope, name)
202
        return lock_name
203
204
    def _validate_scope(self, scope):
205
        if scope not in ALLOWED_SCOPES:
206
            msg = 'Scope %s is not in allowed scopes list: %s.' % (scope, ALLOWED_SCOPES)
207
            abort(http_client.BAD_REQUEST, msg)
208
            return
209