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

st2client/st2client/models/core.py (3 issues)

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
import os
18
import json
19
import logging
20
from functools import wraps
21
22
import six
23
from six.moves import urllib
24
from six.moves import http_client
25
26
from st2client.utils import httpclient
27
28
29
LOG = logging.getLogger(__name__)
30
31
32
def add_auth_token_to_kwargs_from_env(func):
33
    @wraps(func)
34
    def decorate(*args, **kwargs):
35
        if not kwargs.get('token') and os.environ.get('ST2_AUTH_TOKEN', None):
36
            kwargs['token'] = os.environ.get('ST2_AUTH_TOKEN')
37
        if not kwargs.get('api_key') and os.environ.get('ST2_API_KEY', None):
38
            kwargs['api_key'] = os.environ.get('ST2_API_KEY')
39
40
        return func(*args, **kwargs)
41
    return decorate
42
43
44
class Resource(object):
45
46
    # An alias to use for the resource if different than the class name.
47
    _alias = None
48
49
    # Display name of the resource. This may be different than its resource
50
    # name specifically when the resource name is composed of multiple words.
51
    _display_name = None
52
53
    # URL path for the resource.
54
    _url_path = None
55
56
    # Plural form of the resource name. This will be used to build the
57
    # latter part of the REST URL.
58
    _plural = None
59
60
    # Plural form of the resource display name.
61
    _plural_display_name = None
62
63
    # A list of class attributes which will be included in __repr__ return value
64
    _repr_attributes = []
65
66
    def __init__(self, *args, **kwargs):
67
        for k, v in six.iteritems(kwargs):
68
            setattr(self, k, v)
69
70
    def to_dict(self, exclude_attributes=None):
71
        """
72
        Return a dictionary representation of this object.
73
74
        :param exclude_attributes: Optional list of attributes to exclude.
75
        :type exclude_attributes: ``list``
76
77
        :rtype: ``dict``
78
        """
79
        exclude_attributes = exclude_attributes or []
80
81
        attributes = list(self.__dict__.keys())
82
        attributes = [attr for attr in attributes if not attr.startswith('__') and
83
                      attr not in exclude_attributes]
84
85
        result = {}
86
        for attribute in attributes:
87
            value = getattr(self, attribute, None)
88
            result[attribute] = value
89
90
        return result
91
92
    @classmethod
93
    def get_alias(cls):
94
        return cls._alias if cls._alias else cls.__name__
95
96
    @classmethod
97
    def get_display_name(cls):
98
        return cls._display_name if cls._display_name else cls.__name__
99
100
    @classmethod
101
    def get_plural_name(cls):
102
        if not cls._plural:
103
            raise Exception('The %s class is missing class attributes '
104
                            'in its definition.' % cls.__name__)
105
        return cls._plural
106
107
    @classmethod
108
    def get_plural_display_name(cls):
109
        return (cls._plural_display_name
110
                if cls._plural_display_name
111
                else cls._plural)
112
113
    @classmethod
114
    def get_url_path_name(cls):
115
        if cls._url_path:
116
            return cls._url_path
117
118
        return cls.get_plural_name().lower()
119
120
    def serialize(self):
121
        return dict((k, v)
122
                    for k, v in six.iteritems(self.__dict__)
123
                    if not k.startswith('_'))
124
125
    @classmethod
126
    def deserialize(cls, doc):
127
        if type(doc) is not dict:
128
            doc = json.loads(doc)
129
        return cls(**doc)
130
131
    def __str__(self):
132
        return str(self.__repr__())
133
134
    def __repr__(self):
135
        if not self._repr_attributes:
136
            return super(Resource, self).__repr__()
137
138
        attributes = []
139
        for attribute in self._repr_attributes:
140
            value = getattr(self, attribute, None)
141
            attributes.append('%s=%s' % (attribute, value))
142
143
        attributes = ','.join(attributes)
144
        class_name = self.__class__.__name__
145
        result = '<%s %s>' % (class_name, attributes)
146
        return result
147
148
149
class ResourceManager(object):
150
151
    def __init__(self, resource, endpoint, cacert=None, debug=False):
152
        self.resource = resource
153
        self.debug = debug
154
        self.client = httpclient.HTTPClient(endpoint, cacert=cacert, debug=debug)
155
156
    @staticmethod
157
    def handle_error(response):
158
        try:
159
            content = response.json()
160
            fault = content.get('faultstring', '') if content else ''
161
            if fault:
162
                response.reason += '\nMESSAGE: %s' % fault
163
        except Exception as e:
164
            response.reason += ('\nUnable to retrieve detailed message '
165
                                'from the HTTP response. %s\n' % str(e))
166
        response.raise_for_status()
167
168
    @add_auth_token_to_kwargs_from_env
169
    def get_all(self, **kwargs):
170
        # TODO: This is ugly, stop abusing kwargs
171
        url = '/%s' % self.resource.get_url_path_name()
172
        limit = kwargs.pop('limit', None)
173
        pack = kwargs.pop('pack', None)
174
        prefix = kwargs.pop('prefix', None)
175
        user = kwargs.pop('user', None)
176
177
        params = kwargs.pop('params', {})
178
179
        if limit:
180
            params['limit'] = limit
181
182
        if pack:
183
            params['pack'] = pack
184
185
        if prefix:
186
            params['prefix'] = prefix
187
188
        if user:
189
            params['user'] = user
190
191
        response = self.client.get(url=url, params=params, **kwargs)
192
        if response.status_code != http_client.OK:
193
            self.handle_error(response)
194
        return [self.resource.deserialize(item)
195
                for item in response.json()]
196
197
    @add_auth_token_to_kwargs_from_env
198
    def get_by_id(self, id, **kwargs):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
199
        url = '/%s/%s' % (self.resource.get_url_path_name(), id)
200
        response = self.client.get(url, **kwargs)
201
        if response.status_code == http_client.NOT_FOUND:
202
            return None
203
        if response.status_code != http_client.OK:
204
            self.handle_error(response)
205
        return self.resource.deserialize(response.json())
206
207
    @add_auth_token_to_kwargs_from_env
208
    def get_property(self, id_, property_name, self_deserialize=True, **kwargs):
209
        """
210
        Gets a property of a Resource.
211
        id_ : Id of the resource
212
        property_name: Name of the property
213
        self_deserialize: #Implies use the deserialize method implemented by this resource.
214
        """
215
        token = kwargs.pop('token', None)
216
        api_key = kwargs.pop('api_key', None)
217
218
        if kwargs:
219
            url = '/%s/%s/%s/?%s' % (self.resource.get_url_path_name(), id_, property_name,
220
                                     urllib.parse.urlencode(kwargs))
221
        else:
222
            url = '/%s/%s/%s/' % (self.resource.get_url_path_name(), id_, property_name)
223
224
        if token:
225
            response = self.client.get(url, token=token)
226
        elif api_key:
227
            response = self.client.get(url, api_key=api_key)
228
        else:
229
            response = self.client.get(url)
230
231
        if response.status_code == http_client.NOT_FOUND:
232
            return None
233
        if response.status_code != http_client.OK:
234
            self.handle_error(response)
235
236
        if self_deserialize:
237
            return [self.resource.deserialize(item) for item in response.json()]
238
        else:
239
            return response.json()
240
241
    @add_auth_token_to_kwargs_from_env
242
    def get_by_ref_or_id(self, ref_or_id, **kwargs):
243
        return self.get_by_id(id=ref_or_id, **kwargs)
244
245
    def _query_details(self, **kwargs):
246
        if not kwargs:
247
            raise Exception('Query parameter is not provided.')
248
249
        token = kwargs.get('token', None)
250
        api_key = kwargs.get('api_key', None)
251
        params = kwargs.get('params', {})
252
253
        for k, v in six.iteritems(kwargs):
254
            # Note: That's a special case to support api_key and token kwargs
255
            if k not in ['token', 'api_key', 'params']:
256
                params[k] = v
257
258
        url = '/%s/?%s' % (self.resource.get_url_path_name(),
259
                           urllib.parse.urlencode(params))
260
261
        if token:
262
            response = self.client.get(url, token=token)
263
        elif api_key:
264
            response = self.client.get(url, api_key=api_key)
265
        else:
266
            response = self.client.get(url)
267
268
        if response.status_code == http_client.NOT_FOUND:
269
            # for query and query_with_count
270
            return [], None
271
        if response.status_code != http_client.OK:
272
            self.handle_error(response)
273
        items = response.json()
274
        instances = [self.resource.deserialize(item) for item in items]
275
        return instances, response
276
277
    @add_auth_token_to_kwargs_from_env
278
    def query(self, **kwargs):
279
        instances, _ = self._query_details(**kwargs)
280
        return instances
281
282
    @add_auth_token_to_kwargs_from_env
283
    def query_with_count(self, **kwargs):
284
        instances, response = self._query_details(**kwargs)
285
        if response and 'X-Total-Count' in response.headers:
286
            return (instances, int(response.headers['X-Total-Count']))
287
        else:
288
            return (instances, None)
289
290
    @add_auth_token_to_kwargs_from_env
291
    def get_by_name(self, name, **kwargs):
292
        instances = self.query(name=name, **kwargs)
293
        if not instances:
294
            return None
295
        else:
296
            if len(instances) > 1:
297
                raise Exception('More than one %s named "%s" are found.' %
298
                                (self.resource.__name__.lower(), name))
299
            return instances[0]
300
301
    @add_auth_token_to_kwargs_from_env
302
    def create(self, instance, **kwargs):
303
        url = '/%s' % self.resource.get_url_path_name()
304
        response = self.client.post(url, instance.serialize(), **kwargs)
305
        if response.status_code != http_client.OK:
306
            self.handle_error(response)
307
        instance = self.resource.deserialize(response.json())
308
        return instance
309
310
    @add_auth_token_to_kwargs_from_env
311
    def update(self, instance, **kwargs):
312
        url = '/%s/%s' % (self.resource.get_url_path_name(), instance.id)
313
        response = self.client.put(url, instance.serialize(), **kwargs)
314
        if response.status_code != http_client.OK:
315
            self.handle_error(response)
316
        instance = self.resource.deserialize(response.json())
317
        return instance
318
319
    @add_auth_token_to_kwargs_from_env
320
    def delete(self, instance, **kwargs):
321
        url = '/%s/%s' % (self.resource.get_url_path_name(), instance.id)
322
        response = self.client.delete(url, **kwargs)
323
324
        if response.status_code not in [http_client.OK,
325
                                        http_client.NO_CONTENT,
326
                                        http_client.NOT_FOUND]:
327
            self.handle_error(response)
328
            return False
329
330
        return True
331
332
    @add_auth_token_to_kwargs_from_env
333
    def delete_by_id(self, instance_id, **kwargs):
334
        url = '/%s/%s' % (self.resource.get_url_path_name(), instance_id)
335
        response = self.client.delete(url, **kwargs)
336
        if response.status_code not in [http_client.OK,
337
                                        http_client.NO_CONTENT,
338
                                        http_client.NOT_FOUND]:
339
            self.handle_error(response)
340
            return False
341
        try:
342
            resp_json = response.json()
343
            if resp_json:
344
                return resp_json
345
        except:
346
            pass
347
        return True
348
349
350
class ActionAliasResourceManager(ResourceManager):
351
    def __init__(self, resource, endpoint, cacert=None, debug=False):
0 ignored issues
show
The __init__ method of the super-class ResourceManager is not called.

It is generally advisable to initialize the super-class by calling its __init__ method:

class SomeParent:
    def __init__(self):
        self.x = 1

class SomeChild(SomeParent):
    def __init__(self):
        # Initialize the super class
        SomeParent.__init__(self)
Loading history...
352
        self.resource = resource
353
        self.debug = debug
354
        self.client = httpclient.HTTPClient(root=endpoint, cacert=cacert, debug=debug)
355
356
    @add_auth_token_to_kwargs_from_env
357
    def match(self, instance, **kwargs):
358
        url = '/%s/match' % self.resource.get_url_path_name()
359
        response = self.client.post(url, instance.serialize(), **kwargs)
360
        if response.status_code != http_client.OK:
361
            self.handle_error(response)
362
        match = response.json()
363
        return (self.resource.deserialize(match['actionalias']), match['representation'])
364
365
366
class ActionAliasExecutionManager(ResourceManager):
367
    @add_auth_token_to_kwargs_from_env
368
    def match_and_execute(self, instance, **kwargs):
369
        url = '/%s/match_and_execute' % self.resource.get_url_path_name()
370
        response = self.client.post(url, instance.serialize(), **kwargs)
371
372
        if response.status_code != http_client.OK:
373
            self.handle_error(response)
374
        instance = self.resource.deserialize(response.json())
375
        return instance
376
377
378
class LiveActionResourceManager(ResourceManager):
379
    @add_auth_token_to_kwargs_from_env
380
    def re_run(self, execution_id, parameters=None, tasks=None, no_reset=None, **kwargs):
381
        url = '/%s/%s/re_run' % (self.resource.get_url_path_name(), execution_id)
382
383
        tasks = tasks or []
384
        no_reset = no_reset or []
385
386
        if list(set(no_reset) - set(tasks)):
387
            raise ValueError('List of tasks to reset does not match the tasks to rerun.')
388
389
        data = {
390
            'parameters': parameters or {},
391
            'tasks': tasks,
392
            'reset': list(set(tasks) - set(no_reset))
393
        }
394
395
        response = self.client.post(url, data, **kwargs)
396
        if response.status_code != http_client.OK:
397
            self.handle_error(response)
398
399
        instance = self.resource.deserialize(response.json())
400
        return instance
401
402
    @add_auth_token_to_kwargs_from_env
403
    def get_output(self, execution_id, output_type=None, **kwargs):
404
        url = '/%s/%s/output' % (self.resource.get_url_path_name(), execution_id)
405
406
        if output_type:
407
            url += '?' + urllib.parse.urlencode({'output_type': output_type})
408
409
        response = self.client.get(url, **kwargs)
410
        if response.status_code != http_client.OK:
411
            self.handle_error(response)
412
413
        return response.text
414
415
    @add_auth_token_to_kwargs_from_env
416
    def pause(self, execution_id, **kwargs):
417
        url = '/%s/%s' % (self.resource.get_url_path_name(), execution_id)
418
        data = {'status': 'pausing'}
419
420
        response = self.client.put(url, data, **kwargs)
421
422
        if response.status_code != http_client.OK:
423
            self.handle_error(response)
424
425
        return self.resource.deserialize(response.json())
426
427
    @add_auth_token_to_kwargs_from_env
428
    def resume(self, execution_id, **kwargs):
429
        url = '/%s/%s' % (self.resource.get_url_path_name(), execution_id)
430
        data = {'status': 'resuming'}
431
432
        response = self.client.put(url, data, **kwargs)
433
434
        if response.status_code != http_client.OK:
435
            self.handle_error(response)
436
437
        return self.resource.deserialize(response.json())
438
439
440
class InquiryResourceManager(ResourceManager):
441
442
    @add_auth_token_to_kwargs_from_env
443
    def respond(self, inquiry_id, inquiry_response, **kwargs):
444
        """
445
        Update st2.inquiry.respond action
446
        Update st2client respond command to use this?
447
        """
448
        url = '/%s/%s' % (self.resource.get_url_path_name(), inquiry_id)
449
450
        payload = {
451
            "id": inquiry_id,
452
            "response": inquiry_response
453
        }
454
455
        response = self.client.put(url, payload, **kwargs)
456
457
        if response.status_code != http_client.OK:
458
            self.handle_error(response)
459
460
        return self.resource.deserialize(response.json())
461
462
463
class TriggerInstanceResourceManager(ResourceManager):
464
    @add_auth_token_to_kwargs_from_env
465
    def re_emit(self, trigger_instance_id, **kwargs):
466
        url = '/%s/%s/re_emit' % (self.resource.get_url_path_name(), trigger_instance_id)
467
        response = self.client.post(url, None, **kwargs)
468
        if response.status_code != http_client.OK:
469
            self.handle_error(response)
470
        return response.json()
471
472
473
class AsyncRequest(Resource):
474
    pass
475
476
477
class PackResourceManager(ResourceManager):
478
    @add_auth_token_to_kwargs_from_env
479
    def install(self, packs, force=False, python3=False, **kwargs):
480
        url = '/%s/install' % (self.resource.get_url_path_name())
481
        payload = {
482
            'packs': packs,
483
            'force': force,
484
            'python3': python3
485
        }
486
        response = self.client.post(url, payload, **kwargs)
487
        if response.status_code != http_client.OK:
488
            self.handle_error(response)
489
        instance = AsyncRequest.deserialize(response.json())
490
        return instance
491
492
    @add_auth_token_to_kwargs_from_env
493
    def remove(self, packs, **kwargs):
494
        url = '/%s/uninstall' % (self.resource.get_url_path_name())
495
        response = self.client.post(url, {'packs': packs}, **kwargs)
496
        if response.status_code != http_client.OK:
497
            self.handle_error(response)
498
        instance = AsyncRequest.deserialize(response.json())
499
        return instance
500
501
    @add_auth_token_to_kwargs_from_env
502
    def search(self, args, ignore_errors=False, **kwargs):
503
        url = '/%s/index/search' % (self.resource.get_url_path_name())
504
        if 'query' in vars(args):
505
            payload = {'query': args.query}
506
        else:
507
            payload = {'pack': args.pack}
508
509
        response = self.client.post(url, payload, **kwargs)
510
511
        if response.status_code != http_client.OK:
512
            if ignore_errors:
513
                return None
514
515
            self.handle_error(response)
516
        data = response.json()
517
        if isinstance(data, list):
518
            return [self.resource.deserialize(item) for item in data]
519
        else:
520
            return self.resource.deserialize(data) if data else None
521
522
    @add_auth_token_to_kwargs_from_env
523
    def register(self, packs=None, types=None, **kwargs):
524
        url = '/%s/register' % (self.resource.get_url_path_name())
525
        payload = {}
526
        if types:
527
            payload['types'] = types
528
        if packs:
529
            payload['packs'] = packs
530
        response = self.client.post(url, payload, **kwargs)
531
        if response.status_code != http_client.OK:
532
            self.handle_error(response)
533
        instance = self.resource.deserialize(response.json())
534
        return instance
535
536
537
class ConfigManager(ResourceManager):
538
    @add_auth_token_to_kwargs_from_env
539
    def update(self, instance, **kwargs):
540
        url = '/%s/%s' % (self.resource.get_url_path_name(), instance.pack)
541
        response = self.client.put(url, instance.values, **kwargs)
542
        if response.status_code != http_client.OK:
543
            self.handle_error(response)
544
        instance = self.resource.deserialize(response.json())
545
        return instance
546
547
548
class WebhookManager(ResourceManager):
549
    def __init__(self, resource, endpoint, cacert=None, debug=False):
0 ignored issues
show
The __init__ method of the super-class ResourceManager is not called.

It is generally advisable to initialize the super-class by calling its __init__ method:

class SomeParent:
    def __init__(self):
        self.x = 1

class SomeChild(SomeParent):
    def __init__(self):
        # Initialize the super class
        SomeParent.__init__(self)
Loading history...
550
        self.resource = resource
551
        self.debug = debug
552
        self.client = httpclient.HTTPClient(root=endpoint, cacert=cacert, debug=debug)
553
554
    @add_auth_token_to_kwargs_from_env
555
    def post_generic_webhook(self, trigger, payload=None, trace_tag=None, **kwargs):
556
        url = '/webhooks/st2'
557
558
        headers = {}
559
        data = {
560
            'trigger': trigger,
561
            'payload': payload or {}
562
        }
563
564
        if trace_tag:
565
            headers['St2-Trace-Tag'] = trace_tag
566
567
        response = self.client.post(url, data=data, headers=headers, **kwargs)
568
569
        if response.status_code != http_client.OK:
570
            self.handle_error(response)
571
572
        return response.json()
573
574
    @add_auth_token_to_kwargs_from_env
575
    def match(self, instance, **kwargs):
576
        url = '/%s/match' % self.resource.get_url_path_name()
577
        response = self.client.post(url, instance.serialize(), **kwargs)
578
        if response.status_code != http_client.OK:
579
            self.handle_error(response)
580
        match = response.json()
581
        return (self.resource.deserialize(match['actionalias']), match['representation'])
582
583
584
class StreamManager(object):
585
    def __init__(self, endpoint, cacert, debug):
586
        self._url = httpclient.get_url_without_trailing_slash(endpoint) + '/stream'
587
        self.debug = debug
588
        self.cacert = cacert
589
590
    @add_auth_token_to_kwargs_from_env
591
    def listen(self, events=None, **kwargs):
592
        # Late import to avoid very expensive in-direct import (~1 second) when this function is
593
        # not called / used
594
        from sseclient import SSEClient
595
596
        url = self._url
597
        query_params = {}
598
599
        if events and isinstance(events, six.string_types):
600
            events = [events]
601
602
        if 'token' in kwargs:
603
            query_params['x-auth-token'] = kwargs.get('token')
604
605
        if 'api_key' in kwargs:
606
            query_params['st2-api-key'] = kwargs.get('api_key')
607
608
        if events:
609
            query_params['events'] = ','.join(events)
610
611
        query_string = '?' + urllib.parse.urlencode(query_params)
612
        url = url + query_string
613
614
        for message in SSEClient(url):
615
616
            # If the execution on the API server takes too long, the message
617
            # can be empty. In this case, rerun the query.
618
            if not message.data:
619
                continue
620
621
            yield json.loads(message.data)
622