Test Failed
Push — master ( e380d0...f5671d )
by W
02:58
created

st2client/st2client/commands/keyvalue.py (1 issue)

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 __future__ import absolute_import
17
18
import os
19
import json
20
import logging
21
22
from os.path import join as pjoin
23
24
import six
25
26
from st2client.commands import resource
27
from st2client.commands.noop import NoopCommand
28
from st2client.formatters import table
29
from st2client.models.keyvalue import KeyValuePair
30
from st2client.utils.date import format_isodate_for_user_timezone
31
32
LOG = logging.getLogger(__name__)
33
34
DEFAULT_LIST_SCOPE = 'all'
35
DEFAULT_GET_SCOPE = 'system'
36
DEFAULT_CUD_SCOPE = 'system'
37
38
39
class KeyValuePairBranch(resource.ResourceBranch):
40
41
    def __init__(self, description, app, subparsers, parent_parser=None):
42
        super(KeyValuePairBranch, self).__init__(
43
            KeyValuePair, description, app, subparsers,
44
            parent_parser=parent_parser,
45
            commands={
46
                'list': KeyValuePairListCommand,
47
                'get': KeyValuePairGetCommand,
48
                'delete': KeyValuePairDeleteCommand,
49
                'create': NoopCommand,
50
                'update': NoopCommand
51
            })
52
53
        # Registers extended commands
54
        self.commands['set'] = KeyValuePairSetCommand(self.resource, self.app,
55
                                                      self.subparsers)
56
        self.commands['load'] = KeyValuePairLoadCommand(
57
            self.resource, self.app, self.subparsers)
58
        self.commands['delete_by_prefix'] = KeyValuePairDeleteByPrefixCommand(
59
            self.resource, self.app, self.subparsers)
60
61
        # Remove unsupported commands
62
        # TODO: Refactor parent class and make it nicer
63
        del self.commands['create']
64
        del self.commands['update']
65
66
67
class KeyValuePairListCommand(resource.ResourceTableCommand):
68
    display_attributes = ['name', 'value', 'secret', 'encrypted', 'scope', 'user',
69
                          'expire_timestamp']
70
    attribute_transform_functions = {
71
        'expire_timestamp': format_isodate_for_user_timezone,
72
    }
73
74
    def __init__(self, resource, *args, **kwargs):
0 ignored issues
show
Comprehensibility Bug introduced by
resource is re-defining a name which is already available in the outer-scope (previously defined on line 26).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
75
76
        self.default_limit = 50
77
78
        super(KeyValuePairListCommand, self).__init__(resource, 'list',
79
                                                      'Get the list of the %s most recent %s.' %
80
                                                      (self.default_limit,
81
                                                       resource.get_plural_display_name().lower()),
82
                                                      *args, **kwargs)
83
        self.resource_name = resource.get_plural_display_name().lower()
84
        # Filter options
85
        self.parser.add_argument('--prefix', help=('Only return values with names starting with '
86
                                                   'the provided prefix.'))
87
        self.parser.add_argument('--decrypt', action='store_true',
88
                                 help='Decrypt secrets and displays plain text.')
89
        self.parser.add_argument('-s', '--scope', default=DEFAULT_LIST_SCOPE, dest='scope',
90
                                 help='Scope item is under. Example: "user".')
91
        self.parser.add_argument('-u', '--user', dest='user', default=None,
92
                                 help='User for user scoped items (admin only).')
93
        self.parser.add_argument('-n', '--last', type=int, dest='last',
94
                                 default=self.default_limit,
95
                                 help=('List N most recent %s. Use -n -1 to fetch the full result \
96
                                       set.' % self.resource_name))
97
98
    @resource.add_auth_token_to_kwargs_from_cli
99
    def run(self, args, **kwargs):
100
        # Filtering options
101
        if args.prefix:
102
            kwargs['prefix'] = args.prefix
103
104
        decrypt = getattr(args, 'decrypt', False)
105
        kwargs['params'] = {'decrypt': str(decrypt).lower()}
106
        scope = getattr(args, 'scope', DEFAULT_LIST_SCOPE)
107
        kwargs['params']['scope'] = scope
108
        if args.user:
109
            kwargs['params']['user'] = args.user
110
        kwargs['params']['limit'] = args.last
111
112
        return self.manager.query_with_count(**kwargs)
113
114
    @resource.add_auth_token_to_kwargs_from_cli
115
    def run_and_print(self, args, **kwargs):
116
        instances, count = self.run(args, **kwargs)
117
        if args.json or args.yaml:
118
            self.print_output(reversed(instances), table.MultiColumnTable,
119
                              attributes=args.attr, widths=args.width,
120
                              json=args.json, yaml=args.yaml,
121
                              attribute_transform_functions=self.attribute_transform_functions)
122
        else:
123
            self.print_output(instances, table.MultiColumnTable,
124
                              attributes=args.attr, widths=args.width,
125
                              attribute_transform_functions=self.attribute_transform_functions)
126
127
            if args.last and count and count > args.last:
128
                table.SingleRowTable.note_box(self.resource_name, args.last)
129
130
131
class KeyValuePairGetCommand(resource.ResourceGetCommand):
132
    pk_argument_name = 'name'
133
    display_attributes = ['name', 'value', 'secret', 'encrypted', 'scope', 'expire_timestamp']
134
135
    def __init__(self, kv_resource, *args, **kwargs):
136
        super(KeyValuePairGetCommand, self).__init__(kv_resource, *args, **kwargs)
137
        self.parser.add_argument('-d', '--decrypt', action='store_true',
138
                                 help='Decrypt secret if encrypted and show plain text.')
139
        self.parser.add_argument('-s', '--scope', default=DEFAULT_GET_SCOPE, dest='scope',
140
                                 help='Scope item is under. Example: "user".')
141
142
    @resource.add_auth_token_to_kwargs_from_cli
143
    def run(self, args, **kwargs):
144
        resource_name = getattr(args, self.pk_argument_name, None)
145
        decrypt = getattr(args, 'decrypt', False)
146
        scope = getattr(args, 'scope', DEFAULT_GET_SCOPE)
147
        kwargs['params'] = {'decrypt': str(decrypt).lower()}
148
        kwargs['params']['scope'] = scope
149
        return self.get_resource_by_id(id=resource_name, **kwargs)
150
151
152
class KeyValuePairSetCommand(resource.ResourceCommand):
153
    display_attributes = ['name', 'value', 'scope', 'expire_timestamp']
154
155
    def __init__(self, resource, *args, **kwargs):
156
        super(KeyValuePairSetCommand, self).__init__(
157
            resource, 'set',
158
            'Set an existing %s.' % resource.get_display_name().lower(),
159
            *args, **kwargs
160
        )
161
162
        self.parser.add_argument('name',
163
                                 metavar='name',
164
                                 help='Name of the key value pair.')
165
        self.parser.add_argument('value', help='Value paired with the key.')
166
        self.parser.add_argument('-l', '--ttl', dest='ttl', type=int, default=None,
167
                                 help='TTL (in seconds) for this value.')
168
        self.parser.add_argument('-e', '--encrypt', dest='secret',
169
                                 action='store_true',
170
                                 help='Encrypt value before saving.')
171
        self.parser.add_argument('-s', '--scope', dest='scope', default=DEFAULT_CUD_SCOPE,
172
                                 help='Specify the scope under which you want ' +
173
                                      'to place the item.')
174
        self.parser.add_argument('-u', '--user', dest='user', default=None,
175
                                 help='User for user scoped items (admin only).')
176
177
    @resource.add_auth_token_to_kwargs_from_cli
178
    def run(self, args, **kwargs):
179
        instance = KeyValuePair()
180
        instance.id = args.name  # TODO: refactor and get rid of id
181
        instance.name = args.name
182
        instance.value = args.value
183
        instance.scope = args.scope
184
        instance.user = args.user
185
186
        if args.secret:
187
            instance.secret = args.secret
188
189
        if args.ttl:
190
            instance.ttl = args.ttl
191
192
        return self.manager.update(instance, **kwargs)
193
194
    def run_and_print(self, args, **kwargs):
195
        instance = self.run(args, **kwargs)
196
        self.print_output(instance, table.PropertyValueTable,
197
                          attributes=self.display_attributes, json=args.json,
198
                          yaml=args.yaml)
199
200
201
class KeyValuePairDeleteCommand(resource.ResourceDeleteCommand):
202
    pk_argument_name = 'name'
203
204
    def __init__(self, resource, *args, **kwargs):
205
        super(KeyValuePairDeleteCommand, self).__init__(resource, *args, **kwargs)
206
207
        self.parser.add_argument('-s', '--scope', dest='scope', default=DEFAULT_CUD_SCOPE,
208
                                 help='Specify the scope under which you want ' +
209
                                      'to place the item.')
210
        self.parser.add_argument('-u', '--user', dest='user', default=None,
211
                                 help='User for user scoped items (admin only).')
212
213
    @resource.add_auth_token_to_kwargs_from_cli
214
    def run(self, args, **kwargs):
215
        resource_id = getattr(args, self.pk_argument_name, None)
216
        scope = getattr(args, 'scope', DEFAULT_CUD_SCOPE)
217
        kwargs['params'] = {}
218
        kwargs['params']['scope'] = scope
219
        kwargs['params']['user'] = args.user
220
        instance = self.get_resource(resource_id, **kwargs)
221
222
        if not instance:
223
            raise resource.ResourceNotFoundError('KeyValuePair with id "%s" not found',
224
                                                 resource_id)
225
226
        instance.id = resource_id  # TODO: refactor and get rid of id
227
        self.manager.delete(instance, **kwargs)
228
229
230
class KeyValuePairDeleteByPrefixCommand(resource.ResourceCommand):
231
    """
232
    Commands which delete all the key value pairs which match the provided
233
    prefix.
234
    """
235
    def __init__(self, resource, *args, **kwargs):
236
        super(KeyValuePairDeleteByPrefixCommand, self).__init__(resource, 'delete_by_prefix',
237
                                                                'Delete KeyValue pairs which \
238
                                                                 match the provided prefix',
239
                                                                *args, **kwargs)
240
241
        self.parser.add_argument('-p', '--prefix', required=True,
242
                                 help='Name prefix (e.g. twitter.TwitterSensor:)')
243
244
    @resource.add_auth_token_to_kwargs_from_cli
245
    def run(self, args, **kwargs):
246
        prefix = args.prefix
247
        key_pairs = self.manager.get_all(prefix=prefix)
248
249
        to_delete = []
250
        for key_pair in key_pairs:
251
            key_pair.id = key_pair.name
252
            to_delete.append(key_pair)
253
254
        deleted = []
255
        for key_pair in to_delete:
256
            self.manager.delete(instance=key_pair, **kwargs)
257
            deleted.append(key_pair)
258
259
        return deleted
260
261
    def run_and_print(self, args, **kwargs):
262
        # TODO: Need to use args, instead of kwargs (args=) because of bad API
263
        # FIX ME
264
        deleted = self.run(args, **kwargs)
265
        key_ids = [key_pair.id for key_pair in deleted]
266
267
        print('Deleted %s keys' % (len(deleted)))
268
        print('Deleted key ids: %s' % (', '.join(key_ids)))
269
270
271
class KeyValuePairLoadCommand(resource.ResourceCommand):
272
    pk_argument_name = 'name'
273
    display_attributes = ['name', 'value']
274
275
    def __init__(self, resource, *args, **kwargs):
276
        help_text = ('Load a list of %s from file.' %
277
                     resource.get_plural_display_name().lower())
278
        super(KeyValuePairLoadCommand, self).__init__(resource, 'load',
279
                                                      help_text, *args, **kwargs)
280
281
        self.parser.add_argument('-c', '--convert', action='store_true',
282
                                 help=('Convert non-string types (hash, array, boolean,'
283
                                       ' int, float) to a JSON string before loading it'
284
                                       ' into the datastore.'))
285
        self.parser.add_argument(
286
            'file', help=('JSON/YAML file containing the %s(s) to load'
287
                          % resource.get_plural_display_name().lower()))
288
289
    @resource.add_auth_token_to_kwargs_from_cli
290
    def run(self, args, **kwargs):
291
        # normalize the file path to allow for relative files to be specified
292
        file_path = os.path.normpath(pjoin(os.getcwd(), args.file))
293
294
        # load the data (JSON/YAML) from the file
295
        kvps = resource.load_meta_file(file_path)
296
297
        # if the data is not a list (ie. it's a single entry)
298
        # then make it a list so our process loop is generic
299
        if not isinstance(kvps, list):
300
            kvps = [kvps]
301
302
        instances = []
303
        for item in kvps:
304
            # parse required KeyValuePair properties
305
            name = item['name']
306
            value = item['value']
307
308
            # parse optional KeyValuePair properties
309
            scope = item.get('scope', DEFAULT_CUD_SCOPE)
310
            user = item.get('user', None)
311
            secret = item.get('secret', False)
312
            ttl = item.get('ttl', None)
313
314
            # if the value is not a string, convert it to JSON
315
            # all keys in the datastore must strings
316
            if not isinstance(value, six.string_types):
317
                if args.convert:
318
                    value = json.dumps(value)
319
                else:
320
                    raise ValueError(("Item '%s' has a value that is not a string."
321
                                      " Either pass in the -c/--convert option to convert"
322
                                      " non-string types to JSON strings automatically, or"
323
                                      " convert the data to a string in the file") % name)
324
325
            # create the KeyValuePair instance
326
            instance = KeyValuePair()
327
            instance.id = name  # TODO: refactor and get rid of id
328
            instance.name = name
329
            instance.value = value
330
            instance.scope = scope
331
            if user:
332
                instance.user = user
333
            if secret:
334
                instance.secret = secret
335
            if ttl:
336
                instance.ttl = ttl
337
338
            # call the API to create/update the KeyValuePair
339
            self.manager.update(instance, **kwargs)
340
            instances.append(instance)
341
342
        return instances
343
344
    def run_and_print(self, args, **kwargs):
345
        instances = self.run(args, **kwargs)
346
        self.print_output(instances, table.MultiColumnTable,
347
                          attributes=['name', 'value', 'secret', 'scope', 'user', 'ttl'],
348
                          json=args.json,
349
                          yaml=args.yaml)
350