Passed
Push — develop ( b7e2c3...d0345b )
by Plexxi
08:11 queued 04:18
created

KeyValuePairController.get_all()   B

Complexity

Conditions 4

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 4
dl 0
loc 28
rs 8.5806
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
from pecan.rest import RestController
18
import six
19
from mongoengine import ValidationError
20
21
from st2common import log as logging
22
from st2common.exceptions.keyvalue import CryptoKeyNotSetupException
23
from st2common.models.api.keyvalue import KeyValuePairAPI
24
from st2common.models.api.base import jsexpose
25
from st2common.persistence.keyvalue import KeyValuePair
26
from st2common.services import coordination
27
28
http_client = six.moves.http_client
29
30
LOG = logging.getLogger(__name__)
31
32
__all__ = [
33
    'KeyValuePairController'
34
]
35
36
37
class KeyValuePairController(RestController):
38
    """
39
    Implements the REST endpoint for managing the key value store.
40
    """
41
42
    # TODO: Port to use ResourceController
43
    def __init__(self):
44
        super(KeyValuePairController, self).__init__()
45
        self._coordinator = coordination.get_coordinator()
46
        self.get_one_db_method = self.__get_by_name
47
48
    @jsexpose(arg_types=[str, str])
49
    def get_one(self, name, decrypt='false'):
50
        """
51
            List key by name.
52
53
            Handle:
54
                GET /keys/key1
55
        """
56
57
        if not decrypt:
58
            decrypt = False
59
        else:
60
            decrypt = (decrypt == 'true' or decrypt == 'True' or decrypt == '1')
61
62
        kvp_db = self.__get_by_name(name=name)
63
64
        if not kvp_db:
65
            LOG.exception('Database lookup for name="%s" resulted in exception.', name)
66
            abort(http_client.NOT_FOUND)
67
            return
68
69
        try:
70
            kvp_api = KeyValuePairAPI.from_model(kvp_db, mask_secrets=(not decrypt))
71
        except (ValidationError, ValueError) as e:
72
            abort(http_client.INTERNAL_SERVER_ERROR, str(e))
73
            return
74
75
        return kvp_api
76
77
    @jsexpose(arg_types=[str])
78
    def get_all(self, **kw):
79
        """
80
            List all keys.
81
82
            Handles requests:
83
                GET /keys/
84
        """
85
        # Prefix filtering
86
        prefix_filter = kw.get('prefix', None)
87
88
        decrypt = kw.get('decrypt', None)
89
        if not decrypt:
90
            decrypt = False
91
        else:
92
            decrypt = (decrypt == 'true' or decrypt == 'True' or decrypt == '1')
93
            del kw['decrypt']
94
95
        if prefix_filter:
96
            kw['name__startswith'] = prefix_filter
97
            del kw['prefix']
98
99
        kvp_dbs = KeyValuePair.get_all(**kw)
100
        kvps = [KeyValuePairAPI.from_model(
101
            kvp_db, mask_secrets=(not decrypt)) for kvp_db in kvp_dbs
102
        ]
103
104
        return kvps
105
106
    @jsexpose(arg_types=[str, str], body_cls=KeyValuePairAPI)
107
    def put(self, name, kvp):
108
        """
109
        Create a new entry or update an existing one.
110
        """
111
        lock_name = self._get_lock_name_for_key(name=name)
112
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
            existing_kvp = self.__get_by_name(name=name)
118
119
            kvp.name = name
120
121
            try:
122
                kvp_db = KeyValuePairAPI.to_model(kvp)
123
124
                if existing_kvp:
125
                    kvp_db.id = existing_kvp.id
126
127
                kvp_db = KeyValuePair.add_or_update(kvp_db)
128
            except (ValidationError, ValueError) as e:
129
                LOG.exception('Validation failed for key value data=%s', kvp)
130
                abort(http_client.BAD_REQUEST, str(e))
131
                return
132
            except CryptoKeyNotSetupException as e:
133
                LOG.exception(str(e))
134
                abort(http_client.BAD_REQUEST, str(e))
135
                return
136
        extra = {'kvp_db': kvp_db}
137
        LOG.audit('KeyValuePair updated. KeyValuePair.id=%s' % (kvp_db.id), extra=extra)
138
139
        kvp_api = KeyValuePairAPI.from_model(kvp_db)
140
        return kvp_api
141
142
    @jsexpose(arg_types=[str], status_code=http_client.NO_CONTENT)
143
    def delete(self, name):
144
        """
145
            Delete the key value pair.
146
147
            Handles requests:
148
                DELETE /keys/1
149
        """
150
        lock_name = self._get_lock_name_for_key(name=name)
151
152
        # Note: We use lock to avoid a race
153
        with self._coordinator.get_lock(lock_name):
154
            kvp_db = self.__get_by_name(name=name)
155
156
            if not kvp_db:
157
                abort(http_client.NOT_FOUND)
158
                return
159
160
            LOG.debug('DELETE /keys/ lookup with name=%s found object: %s', name, kvp_db)
161
162
            try:
163
                KeyValuePair.delete(kvp_db)
164
            except Exception as e:
165
                LOG.exception('Database delete encountered exception during '
166
                              'delete of name="%s". ', name)
167
                abort(http_client.INTERNAL_SERVER_ERROR, str(e))
168
                return
169
170
        extra = {'kvp_db': kvp_db}
171
        LOG.audit('KeyValuePair deleted. KeyValuePair.id=%s' % (kvp_db.id), extra=extra)
172
173
    @staticmethod
174
    def __get_by_name(name):
175
        try:
176
            return KeyValuePair.get_by_name(name)
177
        except ValueError as e:
178
            LOG.debug('Database lookup for name="%s" resulted in exception : %s.', name, e)
179
            return None
180
181
    def _get_lock_name_for_key(self, name):
182
        """
183
        Retrieve a coordination lock name for the provided datastore item name.
184
185
        :param name: Datastore item name (PK).
186
        :type name: ``str``
187
        """
188
        lock_name = 'kvp-crud-%s' % (name)
189
        return lock_name
190