Completed
Pull Request — master (#2669)
by Lakshmi
06:19
created

KeyValuePairController.get_one()   B

Complexity

Conditions 5

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 5
c 2
b 0
f 0
dl 0
loc 30
rs 8.0894
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.keyvalue import SYSTEM_SCOPE, ALLOWED_SCOPES
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_SCOPE, 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
            if scope not in ALLOWED_SCOPES:
66
                msg = 'Scope "%s" is not valid. Allowed scopes are: %s.' % (scope, ALLOWED_SCOPES)
67
                abort(http_client.BAD_REQUEST, msg)
68
69
            kwargs = {'name': name, 'scope': scope}
70
            kvp_db = super(KeyValuePairController, self)._get_all(
71
                from_model_kwargs=from_model_kwargs,
72
                **kwargs
73
            )
74
            kvp_db = kvp_db[0] if kvp_db else None
75
            if not kvp_db:
76
                msg = 'Key with name: %s and scope: %s not found!' % (name, scope)
77
                abort(http_client.NOT_FOUND, msg)
78
                return
79
        else:
80
            kvp_db = super(KeyValuePairController, self)._get_one_by_name_or_id(
81
                name_or_id=name,
82
                from_model_kwargs=from_model_kwargs
83
            )
84
        return kvp_db
85
86
    @jsexpose(arg_types=[str, str, bool])
87
    def get_all(self, prefix=None, scope=SYSTEM_SCOPE, decrypt=False, **kwargs):
88
        """
89
            List all keys.
90
91
            Handles requests:
92
                GET /keys/
93
        """
94
        from_model_kwargs = {'mask_secrets': not decrypt}
95
        kwargs['prefix'] = prefix
96
        if scope:
97
            if scope not in ALLOWED_SCOPES:
98
                msg = 'Scope "%s" is not valid. Allowed scopes are: %s.' % (scope, ALLOWED_SCOPES)
99
                abort(http_client.BAD_REQUEST, msg)
100
                return
101
            kwargs['scope'] = scope
102
        kvp_dbs = super(KeyValuePairController, self)._get_all(from_model_kwargs=from_model_kwargs,
103
                                                               **kwargs)
104
        return kvp_dbs
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(resource_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
            except InvalidScopeException as e:
137
                LOG.exception(str(e))
138
                abort(http_client.BAD_REQUEST, str(e))
139
                return
140
        extra = {'kvp_db': kvp_db}
141
        LOG.audit('KeyValuePair updated. KeyValuePair.id=%s' % (kvp_db.id), extra=extra)
142
143
        kvp_api = KeyValuePairAPI.from_model(kvp_db)
144
        return kvp_api
145
146
    @jsexpose(arg_types=[str], status_code=http_client.NO_CONTENT)
147
    def delete(self, name):
148
        """
149
            Delete the key value pair.
150
151
            Handles requests:
152
                DELETE /keys/1
153
        """
154
        lock_name = self._get_lock_name_for_key(name=name)
155
156
        # Note: We use lock to avoid a race
157
        with self._coordinator.get_lock(lock_name):
158
            kvp_db = self._get_by_name(resource_name=name)
159
160
            if not kvp_db:
161
                abort(http_client.NOT_FOUND)
162
                return
163
164
            LOG.debug('DELETE /keys/ lookup with name=%s found object: %s', name, kvp_db)
165
166
            try:
167
                KeyValuePair.delete(kvp_db)
168
            except Exception as e:
169
                LOG.exception('Database delete encountered exception during '
170
                              'delete of name="%s". ', name)
171
                abort(http_client.INTERNAL_SERVER_ERROR, str(e))
172
                return
173
174
        extra = {'kvp_db': kvp_db}
175
        LOG.audit('KeyValuePair deleted. KeyValuePair.id=%s' % (kvp_db.id), extra=extra)
176
177
    def _get_lock_name_for_key(self, name):
178
        """
179
        Retrieve a coordination lock name for the provided datastore item name.
180
181
        :param name: Datastore item name (PK).
182
        :type name: ``str``
183
        """
184
        lock_name = 'kvp-crud-%s' % (name)
185
        return lock_name
186