Completed
Pull Request — master (#2669)
by Lakshmi
07:10
created

KeyValuePairController.put()   B

Complexity

Conditions 6

Size

Total Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 6
c 3
b 0
f 0
dl 0
loc 39
rs 7.5384
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 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.system import SYSTEM_KV_PREFIX
23
from st2common.exceptions.keyvalue import CryptoKeyNotSetupException, InvalidScopeException
24
from st2common.models.api.keyvalue import KeyValuePairAPI
25
from st2common.models.api.base import jsexpose
26
from st2common.persistence.keyvalue import KeyValuePair
27
from st2common.services import coordination
28
29
http_client = six.moves.http_client
30
31
LOG = logging.getLogger(__name__)
32
33
__all__ = [
34
    'KeyValuePairController'
35
]
36
37
38
class KeyValuePairController(ResourceController):
39
    """
40
    Implements the REST endpoint for managing the key value store.
41
    """
42
43
    model = KeyValuePairAPI
44
    access = KeyValuePair
45
    supported_filters = {
46
        'prefix': 'name__startswith',
47
        'scope': 'scope'
48
    }
49
50
    def __init__(self):
51
        super(KeyValuePairController, self).__init__()
52
        self._coordinator = coordination.get_coordinator()
53
        self.get_one_db_method = self._get_by_name
54
55
    @jsexpose(arg_types=[str, str, bool])
56
    def get_one(self, name, scope=SYSTEM_KV_PREFIX, decrypt=False):
0 ignored issues
show
Bug introduced by
Arguments number differs from overridden 'get_one' method
Loading history...
57
        """
58
            List key by name.
59
60
            Handle:
61
                GET /keys/key1
62
        """
63
        from_model_kwargs = {'mask_secrets': not decrypt}
64
        if scope:
65
            kwargs = {'name': name, 'scope': scope}
66
            kvp_db = super(KeyValuePairController, self)._get_all(
67
                from_model_kwargs=from_model_kwargs,
68
                **kwargs
69
            )
70
            kvp_db = kvp_db[0] if kvp_db else None
71
            if not kvp_db:
72
                msg = 'Key with name: %s and scope: %s not found!' % (name, scope)
73
                abort(http_client.NOT_FOUND, msg)
74
                return
75
        else:
76
            kvp_db = super(KeyValuePairController, self)._get_one_by_name_or_id(
77
                name_or_id=name,
78
                from_model_kwargs=from_model_kwargs
79
            )
80
        return kvp_db
81
82
    @jsexpose(arg_types=[str, str, bool])
83
    def get_all(self, prefix=None, scope=SYSTEM_KV_PREFIX, decrypt=False, **kwargs):
84
        """
85
            List all keys.
86
87
            Handles requests:
88
                GET /keys/
89
        """
90
        from_model_kwargs = {'mask_secrets': not decrypt}
91
        kwargs['prefix'] = prefix
92
        kwargs['scope'] = scope
93
        kvp_dbs = super(KeyValuePairController, self)._get_all(from_model_kwargs=from_model_kwargs,
94
                                                               **kwargs)
95
        return kvp_dbs
96
97
    @jsexpose(arg_types=[str, str], body_cls=KeyValuePairAPI)
98
    def put(self, name, kvp):
99
        """
100
        Create a new entry or update an existing one.
101
        """
102
        lock_name = self._get_lock_name_for_key(name=name)
103
104
        # TODO: Custom permission check since the key doesn't need to exist here
105
106
        # Note: We use lock to avoid a race
107
        with self._coordinator.get_lock(lock_name):
108
            existing_kvp = self._get_by_name(resource_name=name)
109
110
            kvp.name = name
111
112
            try:
113
                kvp_db = KeyValuePairAPI.to_model(kvp)
114
115
                if existing_kvp:
116
                    kvp_db.id = existing_kvp.id
117
118
                kvp_db = KeyValuePair.add_or_update(kvp_db)
119
            except (ValidationError, ValueError) as e:
120
                LOG.exception('Validation failed for key value data=%s', kvp)
121
                abort(http_client.BAD_REQUEST, str(e))
122
                return
123
            except CryptoKeyNotSetupException as e:
124
                LOG.exception(str(e))
125
                abort(http_client.BAD_REQUEST, str(e))
126
                return
127
            except InvalidScopeException as e:
128
                LOG.exception(str(e))
129
                abort(http_client.BAD_REQUEST, str(e))
130
                return
131
        extra = {'kvp_db': kvp_db}
132
        LOG.audit('KeyValuePair updated. KeyValuePair.id=%s' % (kvp_db.id), extra=extra)
133
134
        kvp_api = KeyValuePairAPI.from_model(kvp_db)
135
        return kvp_api
136
137
    @jsexpose(arg_types=[str], status_code=http_client.NO_CONTENT)
138
    def delete(self, name):
139
        """
140
            Delete the key value pair.
141
142
            Handles requests:
143
                DELETE /keys/1
144
        """
145
        lock_name = self._get_lock_name_for_key(name=name)
146
147
        # Note: We use lock to avoid a race
148
        with self._coordinator.get_lock(lock_name):
149
            kvp_db = self._get_by_name(resource_name=name)
150
151
            if not kvp_db:
152
                abort(http_client.NOT_FOUND)
153
                return
154
155
            LOG.debug('DELETE /keys/ lookup with name=%s found object: %s', name, kvp_db)
156
157
            try:
158
                KeyValuePair.delete(kvp_db)
159
            except Exception as e:
160
                LOG.exception('Database delete encountered exception during '
161
                              'delete of name="%s". ', name)
162
                abort(http_client.INTERNAL_SERVER_ERROR, str(e))
163
                return
164
165
        extra = {'kvp_db': kvp_db}
166
        LOG.audit('KeyValuePair deleted. KeyValuePair.id=%s' % (kvp_db.id), extra=extra)
167
168
    def _get_lock_name_for_key(self, name):
169
        """
170
        Retrieve a coordination lock name for the provided datastore item name.
171
172
        :param name: Datastore item name (PK).
173
        :type name: ``str``
174
        """
175
        lock_name = 'kvp-crud-%s' % (name)
176
        return lock_name
177