Completed
Pull Request — master (#2631)
by Lakshmi
05:51
created

KeyValuePairController.__init__()   A

Complexity

Conditions 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 4
rs 10
cc 1
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
        if prefix_filter:
89
            kw['name__startswith'] = prefix_filter
90
            del kw['prefix']
91
92
        kvp_dbs = KeyValuePair.get_all(**kw)
93
        kvps = [KeyValuePairAPI.from_model(kvp_db) for kvp_db in kvp_dbs]
94
95
        return kvps
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(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
        extra = {'kvp_db': kvp_db}
128
        LOG.audit('KeyValuePair updated. KeyValuePair.id=%s' % (kvp_db.id), extra=extra)
129
130
        kvp_api = KeyValuePairAPI.from_model(kvp_db)
131
        return kvp_api
132
133
    @jsexpose(arg_types=[str], status_code=http_client.NO_CONTENT)
134
    def delete(self, name):
135
        """
136
            Delete the key value pair.
137
138
            Handles requests:
139
                DELETE /keys/1
140
        """
141
        lock_name = self._get_lock_name_for_key(name=name)
142
143
        # Note: We use lock to avoid a race
144
        with self._coordinator.get_lock(lock_name):
145
            kvp_db = self.__get_by_name(name=name)
146
147
            if not kvp_db:
148
                abort(http_client.NOT_FOUND)
149
                return
150
151
            LOG.debug('DELETE /keys/ lookup with name=%s found object: %s', name, kvp_db)
152
153
            try:
154
                KeyValuePair.delete(kvp_db)
155
            except Exception as e:
156
                LOG.exception('Database delete encountered exception during '
157
                              'delete of name="%s". ', name)
158
                abort(http_client.INTERNAL_SERVER_ERROR, str(e))
159
                return
160
161
        extra = {'kvp_db': kvp_db}
162
        LOG.audit('KeyValuePair deleted. KeyValuePair.id=%s' % (kvp_db.id), extra=extra)
163
164
    @staticmethod
165
    def __get_by_name(name):
166
        try:
167
            return KeyValuePair.get_by_name(name)
168
        except ValueError as e:
169
            LOG.debug('Database lookup for name="%s" resulted in exception : %s.', name, e)
170
            return None
171
172
    def _get_lock_name_for_key(self, name):
173
        """
174
        Retrieve a coordination lock name for the provided datastore item name.
175
176
        :param name: Datastore item name (PK).
177
        :type name: ``str``
178
        """
179
        lock_name = 'kvp-crud-%s' % (name)
180
        return lock_name
181