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): |
||
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): |
||
0 ignored issues
–
show
|
|||
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): |
||
0 ignored issues
–
show
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...
|
|||
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 |
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: