Passed
Push — develop ( 82f97f...71d0f8 )
by Plexxi
07:37 queued 03:48
created

KeyValuePairController   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 119
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 119
rs 10
wmc 13

6 Methods

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