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 | import os |
||
17 | import json |
||
18 | import logging |
||
19 | from os.path import join as pjoin |
||
20 | |||
21 | from st2client.commands import resource |
||
22 | from st2client.commands.noop import NoopCommand |
||
23 | from st2client.formatters import table |
||
24 | from st2client.models.keyvalue import KeyValuePair |
||
25 | from st2client.utils.date import format_isodate_for_user_timezone |
||
26 | |||
27 | LOG = logging.getLogger(__name__) |
||
28 | |||
29 | DEFAULT_SCOPE = 'system' |
||
30 | |||
31 | |||
32 | class KeyValuePairBranch(resource.ResourceBranch): |
||
33 | |||
34 | def __init__(self, description, app, subparsers, parent_parser=None): |
||
35 | super(KeyValuePairBranch, self).__init__( |
||
36 | KeyValuePair, description, app, subparsers, |
||
37 | parent_parser=parent_parser, |
||
38 | commands={ |
||
39 | 'list': KeyValuePairListCommand, |
||
40 | 'get': KeyValuePairGetCommand, |
||
41 | 'delete': KeyValuePairDeleteCommand, |
||
42 | 'create': NoopCommand, |
||
43 | 'update': NoopCommand |
||
44 | }) |
||
45 | |||
46 | # Registers extended commands |
||
47 | self.commands['set'] = KeyValuePairSetCommand(self.resource, self.app, |
||
48 | self.subparsers) |
||
49 | self.commands['load'] = KeyValuePairLoadCommand( |
||
50 | self.resource, self.app, self.subparsers) |
||
51 | self.commands['delete_by_prefix'] = KeyValuePairDeleteByPrefixCommand( |
||
52 | self.resource, self.app, self.subparsers) |
||
53 | |||
54 | # Remove unsupported commands |
||
55 | # TODO: Refactor parent class and make it nicer |
||
56 | del self.commands['create'] |
||
57 | del self.commands['update'] |
||
58 | |||
59 | |||
60 | class KeyValuePairListCommand(resource.ResourceTableCommand): |
||
61 | display_attributes = ['name', 'value', 'secret', 'encrypted', 'scope', 'user', |
||
62 | 'expire_timestamp'] |
||
63 | attribute_transform_functions = { |
||
64 | 'expire_timestamp': format_isodate_for_user_timezone, |
||
65 | } |
||
66 | |||
67 | def __init__(self, resource, *args, **kwargs): |
||
0 ignored issues
–
show
|
|||
68 | super(KeyValuePairListCommand, self).__init__(resource, 'list', |
||
69 | 'Get the list of the 50 most recent %s.' % |
||
70 | resource.get_plural_display_name().lower(), |
||
71 | *args, **kwargs) |
||
72 | self.default_limit = 50 |
||
73 | self.resource_name = resource.get_plural_display_name().lower() |
||
74 | # Filter options |
||
75 | self.parser.add_argument('--prefix', help=('Only return values with names starting with ' |
||
76 | 'the provided prefix.')) |
||
77 | self.parser.add_argument('--decrypt', action='store_true', |
||
78 | help='Decrypt secrets and displays plain text.') |
||
79 | self.parser.add_argument('-s', '--scope', default='system', dest='scope', |
||
80 | help='Scope item is under. Example: "user".') |
||
81 | self.parser.add_argument('-u', '--user', dest='user', default=None, |
||
82 | help='User for user scoped items (admin only).') |
||
83 | self.parser.add_argument('-n', '--last', type=int, dest='last', |
||
84 | default=self.default_limit, |
||
85 | help=('List N most recent %s. Default is 50.' % |
||
86 | self.resource_name)) |
||
87 | |||
88 | @resource.add_auth_token_to_kwargs_from_cli |
||
89 | def run(self, args, **kwargs): |
||
90 | # Filtering options |
||
91 | if args.prefix: |
||
92 | kwargs['prefix'] = args.prefix |
||
93 | |||
94 | decrypt = getattr(args, 'decrypt', False) |
||
95 | kwargs['params'] = {'decrypt': str(decrypt).lower()} |
||
96 | scope = getattr(args, 'scope', DEFAULT_SCOPE) |
||
97 | kwargs['params']['scope'] = scope |
||
98 | if args.user: |
||
99 | kwargs['params']['user'] = args.user |
||
100 | kwargs['params']['limit'] = args.last |
||
101 | |||
102 | return self.manager.query_with_count(**kwargs) |
||
103 | |||
104 | @resource.add_auth_token_to_kwargs_from_cli |
||
105 | def run_and_print(self, args, **kwargs): |
||
106 | instances, count = self.run(args, **kwargs) |
||
107 | if args.json or args.yaml: |
||
108 | self.print_output(reversed(instances), table.MultiColumnTable, |
||
109 | attributes=args.attr, widths=args.width, |
||
110 | json=args.json, yaml=args.yaml, |
||
111 | attribute_transform_functions=self.attribute_transform_functions) |
||
112 | else: |
||
113 | self.print_output(instances, table.MultiColumnTable, |
||
114 | attributes=args.attr, widths=args.width, |
||
115 | attribute_transform_functions=self.attribute_transform_functions) |
||
116 | |||
117 | if args.last and count and count > args.last: |
||
118 | table.SingleRowTable.note_box(self.resource_name, args.last) |
||
119 | |||
120 | |||
121 | class KeyValuePairGetCommand(resource.ResourceGetCommand): |
||
122 | pk_argument_name = 'name' |
||
123 | display_attributes = ['name', 'value', 'secret', 'encrypted', 'scope', 'expire_timestamp'] |
||
124 | |||
125 | def __init__(self, kv_resource, *args, **kwargs): |
||
126 | super(KeyValuePairGetCommand, self).__init__(kv_resource, *args, **kwargs) |
||
127 | self.parser.add_argument('-d', '--decrypt', action='store_true', |
||
128 | help='Decrypt secret if encrypted and show plain text.') |
||
129 | self.parser.add_argument('-s', '--scope', default=DEFAULT_SCOPE, dest='scope', |
||
130 | help='Scope item is under. Example: "user".') |
||
131 | |||
132 | @resource.add_auth_token_to_kwargs_from_cli |
||
133 | def run(self, args, **kwargs): |
||
134 | resource_name = getattr(args, self.pk_argument_name, None) |
||
135 | decrypt = getattr(args, 'decrypt', False) |
||
136 | scope = getattr(args, 'scope', DEFAULT_SCOPE) |
||
137 | kwargs['params'] = {'decrypt': str(decrypt).lower()} |
||
138 | kwargs['params']['scope'] = scope |
||
139 | return self.get_resource_by_id(id=resource_name, **kwargs) |
||
140 | |||
141 | |||
142 | class KeyValuePairSetCommand(resource.ResourceCommand): |
||
143 | display_attributes = ['name', 'value', 'expire_timestamp'] |
||
144 | |||
145 | def __init__(self, resource, *args, **kwargs): |
||
0 ignored issues
–
show
resource is re-defining a name which is already available in the outer-scope (previously defined on line 21 ).
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
![]() |
|||
146 | super(KeyValuePairSetCommand, self).__init__( |
||
147 | resource, 'set', |
||
148 | 'Set an existing %s.' % resource.get_display_name().lower(), |
||
149 | *args, **kwargs |
||
150 | ) |
||
151 | |||
152 | self.parser.add_argument('name', |
||
153 | metavar='name', |
||
154 | help='Name of the key value pair.') |
||
155 | self.parser.add_argument('value', help='Value paired with the key.') |
||
156 | self.parser.add_argument('-l', '--ttl', dest='ttl', type=int, default=None, |
||
157 | help='TTL (in seconds) for this value.') |
||
158 | self.parser.add_argument('-e', '--encrypt', dest='secret', |
||
159 | action='store_true', |
||
160 | help='Encrypt value before saving.') |
||
161 | self.parser.add_argument('-s', '--scope', dest='scope', default=DEFAULT_SCOPE, |
||
162 | help='Specify the scope under which you want ' + |
||
163 | 'to place the item.') |
||
164 | self.parser.add_argument('-u', '--user', dest='user', default=None, |
||
165 | help='User for user scoped items (admin only).') |
||
166 | |||
167 | @resource.add_auth_token_to_kwargs_from_cli |
||
168 | def run(self, args, **kwargs): |
||
169 | instance = KeyValuePair() |
||
170 | instance.id = args.name # TODO: refactor and get rid of id |
||
171 | instance.name = args.name |
||
172 | instance.value = args.value |
||
173 | instance.scope = args.scope |
||
174 | instance.user = args.user |
||
175 | |||
176 | if args.secret: |
||
177 | instance.secret = args.secret |
||
178 | |||
179 | if args.ttl: |
||
180 | instance.ttl = args.ttl |
||
181 | |||
182 | return self.manager.update(instance, **kwargs) |
||
183 | |||
184 | def run_and_print(self, args, **kwargs): |
||
185 | instance = self.run(args, **kwargs) |
||
186 | self.print_output(instance, table.PropertyValueTable, |
||
187 | attributes=self.display_attributes, json=args.json, |
||
188 | yaml=args.yaml) |
||
189 | |||
190 | |||
191 | class KeyValuePairDeleteCommand(resource.ResourceDeleteCommand): |
||
192 | pk_argument_name = 'name' |
||
193 | |||
194 | def __init__(self, resource, *args, **kwargs): |
||
0 ignored issues
–
show
resource is re-defining a name which is already available in the outer-scope (previously defined on line 21 ).
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
![]() |
|||
195 | super(KeyValuePairDeleteCommand, self).__init__(resource, *args, **kwargs) |
||
196 | |||
197 | self.parser.add_argument('-s', '--scope', dest='scope', default=DEFAULT_SCOPE, |
||
198 | help='Specify the scope under which you want ' + |
||
199 | 'to place the item.') |
||
200 | self.parser.add_argument('-u', '--user', dest='user', default=None, |
||
201 | help='User for user scoped items (admin only).') |
||
202 | |||
203 | @resource.add_auth_token_to_kwargs_from_cli |
||
204 | def run(self, args, **kwargs): |
||
205 | resource_id = getattr(args, self.pk_argument_name, None) |
||
206 | scope = getattr(args, 'scope', DEFAULT_SCOPE) |
||
207 | kwargs['params'] = {} |
||
208 | kwargs['params']['scope'] = scope |
||
209 | kwargs['params']['user'] = args.user |
||
210 | instance = self.get_resource(resource_id, **kwargs) |
||
211 | |||
212 | if not instance: |
||
213 | raise resource.ResourceNotFoundError('KeyValuePair with id "%s" not found', |
||
214 | resource_id) |
||
215 | |||
216 | instance.id = resource_id # TODO: refactor and get rid of id |
||
217 | self.manager.delete(instance, **kwargs) |
||
218 | |||
219 | |||
220 | class KeyValuePairDeleteByPrefixCommand(resource.ResourceCommand): |
||
221 | """ |
||
222 | Commands which delete all the key value pairs which match the provided |
||
223 | prefix. |
||
224 | """ |
||
225 | def __init__(self, resource, *args, **kwargs): |
||
0 ignored issues
–
show
resource is re-defining a name which is already available in the outer-scope (previously defined on line 21 ).
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
![]() |
|||
226 | super(KeyValuePairDeleteByPrefixCommand, self).__init__(resource, 'delete_by_prefix', |
||
227 | 'Delete KeyValue pairs which \ |
||
228 | match the provided prefix', |
||
229 | *args, **kwargs) |
||
230 | |||
231 | self.parser.add_argument('-p', '--prefix', required=True, |
||
232 | help='Name prefix (e.g. twitter.TwitterSensor:)') |
||
233 | |||
234 | @resource.add_auth_token_to_kwargs_from_cli |
||
235 | def run(self, args, **kwargs): |
||
236 | prefix = args.prefix |
||
237 | key_pairs = self.manager.get_all(prefix=prefix) |
||
238 | |||
239 | to_delete = [] |
||
240 | for key_pair in key_pairs: |
||
241 | key_pair.id = key_pair.name |
||
242 | to_delete.append(key_pair) |
||
243 | |||
244 | deleted = [] |
||
245 | for key_pair in to_delete: |
||
246 | self.manager.delete(instance=key_pair, **kwargs) |
||
247 | deleted.append(key_pair) |
||
248 | |||
249 | return deleted |
||
250 | |||
251 | def run_and_print(self, args, **kwargs): |
||
252 | # TODO: Need to use args, instead of kwargs (args=) because of bad API |
||
253 | # FIX ME |
||
254 | deleted = self.run(args, **kwargs) |
||
255 | key_ids = [key_pair.id for key_pair in deleted] |
||
256 | |||
257 | print('Deleted %s keys' % (len(deleted))) |
||
258 | print('Deleted key ids: %s' % (', '.join(key_ids))) |
||
259 | |||
260 | |||
261 | class KeyValuePairLoadCommand(resource.ResourceCommand): |
||
262 | pk_argument_name = 'name' |
||
263 | display_attributes = ['name', 'value'] |
||
264 | |||
265 | def __init__(self, resource, *args, **kwargs): |
||
0 ignored issues
–
show
resource is re-defining a name which is already available in the outer-scope (previously defined on line 21 ).
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
![]() |
|||
266 | help_text = ('Load a list of %s from file.' % |
||
267 | resource.get_plural_display_name().lower()) |
||
268 | super(KeyValuePairLoadCommand, self).__init__(resource, 'load', |
||
269 | help_text, *args, **kwargs) |
||
270 | |||
271 | self.parser.add_argument('-c', '--convert', action='store_true', |
||
272 | help=('Convert non-string types (hash, array, boolean,' |
||
273 | ' int, float) to a JSON string before loading it' |
||
274 | ' into the datastore.')) |
||
275 | self.parser.add_argument( |
||
276 | 'file', help=('JSON/YAML file containing the %s(s) to load' |
||
277 | % resource.get_plural_display_name().lower())) |
||
278 | |||
279 | @resource.add_auth_token_to_kwargs_from_cli |
||
280 | def run(self, args, **kwargs): |
||
281 | # normalize the file path to allow for relative files to be specified |
||
282 | file_path = os.path.normpath(pjoin(os.getcwd(), args.file)) |
||
283 | |||
284 | # load the data (JSON/YAML) from the file |
||
285 | kvps = resource.load_meta_file(file_path) |
||
286 | |||
287 | # if the data is not a list (ie. it's a single entry) |
||
288 | # then make it a list so our process loop is generic |
||
289 | if not isinstance(kvps, list): |
||
290 | kvps = [kvps] |
||
291 | |||
292 | instances = [] |
||
293 | for item in kvps: |
||
294 | # parse required KeyValuePair properties |
||
295 | name = item['name'] |
||
296 | value = item['value'] |
||
297 | |||
298 | # parse optional KeyValuePair properties |
||
299 | scope = item.get('scope', DEFAULT_SCOPE) |
||
300 | user = item.get('user', None) |
||
301 | secret = item.get('secret', False) |
||
302 | ttl = item.get('ttl', None) |
||
303 | |||
304 | # if the value is not a string, convert it to JSON |
||
305 | # all keys in the datastore must strings |
||
306 | if not isinstance(value, basestring): |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
307 | if args.convert: |
||
308 | value = json.dumps(value) |
||
309 | else: |
||
310 | raise ValueError(("Item '%s' has a value that is not a string." |
||
311 | " Either pass in the -c/--convert option to convert" |
||
312 | " non-string types to JSON strings automatically, or" |
||
313 | " convert the data to a string in the file") % name) |
||
314 | |||
315 | # create the KeyValuePair instance |
||
316 | instance = KeyValuePair() |
||
317 | instance.id = name # TODO: refactor and get rid of id |
||
318 | instance.name = name |
||
319 | instance.value = value |
||
320 | instance.scope = scope |
||
321 | if user: |
||
322 | instance.user = user |
||
323 | if secret: |
||
324 | instance.secret = secret |
||
325 | if ttl: |
||
326 | instance.ttl = ttl |
||
327 | |||
328 | # call the API to create/update the KeyValuePair |
||
329 | self.manager.update(instance, **kwargs) |
||
330 | instances.append(instance) |
||
331 | |||
332 | return instances |
||
333 | |||
334 | def run_and_print(self, args, **kwargs): |
||
335 | instances = self.run(args, **kwargs) |
||
336 | self.print_output(instances, table.MultiColumnTable, |
||
337 | attributes=['name', 'value', 'secret', 'scope', 'user', 'ttl'], |
||
338 | json=args.json, |
||
339 | yaml=args.yaml) |
||
340 |
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: