Completed
Pull Request — master (#2836)
by Edward
06:28
created

PackResourceManager.search()   A

Complexity

Conditions 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
c 1
b 0
f 0
dl 0
loc 7
rs 9.4285
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 functools import wraps
20
21
import six
22
23
from six.moves import urllib
24
from st2client.utils import httpclient
25
26
27
LOG = logging.getLogger(__name__)
28
29
30
def add_auth_token_to_kwargs_from_env(func):
31
    @wraps(func)
32
    def decorate(*args, **kwargs):
33
        if not kwargs.get('token') and os.environ.get('ST2_AUTH_TOKEN', None):
34
            kwargs['token'] = os.environ.get('ST2_AUTH_TOKEN')
35
        if not kwargs.get('api_key') and os.environ.get('ST2_API_KEY', None):
36
            kwargs['api_key'] = os.environ.get('ST2_API_KEY')
37
38
        return func(*args, **kwargs)
39
    return decorate
40
41
42
class Resource(object):
43
44
    # An alias to use for the resource if different than the class name.
45
    _alias = None
46
47
    # Display name of the resource. This may be different than its resource
48
    # name specifically when the resource name is composed of multiple words.
49
    _display_name = None
50
51
    # URL path for the resource.
52
    _url_path = None
53
54
    # Plural form of the resource name. This will be used to build the
55
    # latter part of the REST URL.
56
    _plural = None
57
58
    # Plural form of the resource display name.
59
    _plural_display_name = None
60
61
    # A list of class attributes which will be included in __repr__ return value
62
    _repr_attributes = []
63
64
    def __init__(self, *args, **kwargs):
65
        for k, v in six.iteritems(kwargs):
66
            setattr(self, k, v)
67
68
    def to_dict(self, exclude_attributes=None):
69
        """
70
        Return a dictionary representation of this object.
71
72
        :param exclude_attributes: Optional list of attributes to exclude.
73
        :type exclude_attributes: ``list``
74
75
        :rtype: ``dict``
76
        """
77
        exclude_attributes = exclude_attributes or []
78
79
        attributes = self.__dict__.keys()
80
        attributes = [attr for attr in attributes if not attr.startswith('__') and
81
                      attr not in exclude_attributes]
82
83
        result = {}
84
        for attribute in attributes:
85
            value = getattr(self, attribute, None)
86
            result[attribute] = value
87
88
        return result
89
90
    @classmethod
91
    def get_alias(cls):
92
        return cls._alias if cls._alias else cls.__name__
93
94
    @classmethod
95
    def get_display_name(cls):
96
        return cls._display_name if cls._display_name else cls.__name__
97
98
    @classmethod
99
    def get_plural_name(cls):
100
        if not cls._plural:
101
            raise Exception('The %s class is missing class attributes '
102
                            'in its definition.' % cls.__name__)
103
        return cls._plural
104
105
    @classmethod
106
    def get_plural_display_name(cls):
107
        return (cls._plural_display_name
108
                if cls._plural_display_name
109
                else cls._plural)
110
111
    @classmethod
112
    def get_url_path_name(cls):
113
        if cls._url_path:
114
            return cls._url_path
115
116
        return cls.get_plural_name().lower()
117
118
    def serialize(self):
119
        return dict((k, v)
120
                    for k, v in six.iteritems(self.__dict__)
121
                    if not k.startswith('_'))
122
123
    @classmethod
124
    def deserialize(cls, doc):
125
        if type(doc) is not dict:
126
            doc = json.loads(doc)
127
        return cls(**doc)
128
129
    def __str__(self):
130
        return str(self.__repr__())
131
132
    def __repr__(self):
133
        if not self._repr_attributes:
134
            return super(Resource, self).__repr__()
135
136
        attributes = []
137
        for attribute in self._repr_attributes:
138
            value = getattr(self, attribute, None)
139
            attributes.append('%s=%s' % (attribute, value))
140
141
        attributes = ','.join(attributes)
142
        class_name = self.__class__.__name__
143
        result = '<%s %s>' % (class_name, attributes)
144
        return result
145
146
147
class ResourceManager(object):
148
149
    def __init__(self, resource, endpoint, cacert=None, debug=False):
150
        self.resource = resource
151
        self.debug = debug
152
        self.client = httpclient.HTTPClient(endpoint, cacert=cacert, debug=debug)
153
154
    @staticmethod
155
    def handle_error(response):
156
        try:
157
            content = response.json()
158
            fault = content.get('faultstring', '') if content else ''
159
            if fault:
160
                response.reason += '\nMESSAGE: %s' % fault
161
        except Exception as e:
162
            response.reason += ('\nUnable to retrieve detailed message '
163
                                'from the HTTP response. %s\n' % str(e))
164
        response.raise_for_status()
165
166
    @add_auth_token_to_kwargs_from_env
167
    def get_all(self, **kwargs):
168
        # TODO: This is ugly, stop abusing kwargs
169
        url = '/%s' % self.resource.get_url_path_name()
170
        limit = kwargs.pop('limit', None)
171
        pack = kwargs.pop('pack', None)
172
        prefix = kwargs.pop('prefix', None)
173
        user = kwargs.pop('user', None)
174
175
        params = kwargs.pop('params', {})
176
        if limit and limit <= 0:
177
            limit = None
178
        if limit:
179
            params['limit'] = limit
180
181
        if pack:
182
            params['pack'] = pack
183
184
        if prefix:
185
            params['prefix'] = prefix
186
187
        if user:
188
            params['user'] = user
189
190
        response = self.client.get(url=url, params=params, **kwargs)
191
        if response.status_code != 200:
192
            self.handle_error(response)
193
        return [self.resource.deserialize(item)
194
                for item in response.json()]
195
196
    @add_auth_token_to_kwargs_from_env
197
    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...
198
        url = '/%s/%s' % (self.resource.get_url_path_name(), id)
199
        response = self.client.get(url, **kwargs)
200
        if response.status_code == 404:
201
            return None
202
        if response.status_code != 200:
203
            self.handle_error(response)
204
        return self.resource.deserialize(response.json())
205
206
    @add_auth_token_to_kwargs_from_env
207
    def get_property(self, id_, property_name, self_deserialize=True, **kwargs):
208
        """
209
        Gets a property of a Resource.
210
        id_ : Id of the resource
211
        property_name: Name of the property
212
        self_deserialize: #Implies use the deserialize method implemented by this resource.
213
        """
214
        token = kwargs.pop('token', None)
215
        api_key = kwargs.pop('api_key', None)
216
217
        if kwargs:
218
            url = '/%s/%s/%s/?%s' % (self.resource.get_url_path_name(), id_, property_name,
219
                                     urllib.parse.urlencode(kwargs))
220
        else:
221
            url = '/%s/%s/%s/' % (self.resource.get_url_path_name(), id_, property_name)
222
223
        if token:
224
            response = self.client.get(url, token=token)
225
        elif api_key:
226
            response = self.client.get(url, api_key=api_key)
227
        else:
228
            response = self.client.get(url)
229
230
        if response.status_code == 404:
231
            return None
232
        if response.status_code != 200:
233
            self.handle_error(response)
234
235
        if self_deserialize:
236
            return [self.resource.deserialize(item) for item in response.json()]
237
        else:
238
            return response.json()
239
240
    @add_auth_token_to_kwargs_from_env
241
    def get_by_ref_or_id(self, ref_or_id, **kwargs):
242
        return self.get_by_id(id=ref_or_id, **kwargs)
243
244
    @add_auth_token_to_kwargs_from_env
245
    def query(self, **kwargs):
246
        if not kwargs:
247
            raise Exception('Query parameter is not provided.')
248
        if 'limit' in kwargs and kwargs.get('limit') <= 0:
249
            kwargs.pop('limit')
250
251
        token = kwargs.get('token', None)
252
        api_key = kwargs.get('api_key', None)
253
        params = kwargs.get('params', {})
254
255
        for k, v in six.iteritems(kwargs):
256
            # Note: That's a special case to support api_key and token kwargs
257
            if k not in ['token', 'api_key']:
258
                params[k] = v
259
260
        url = '/%s/?%s' % (self.resource.get_url_path_name(),
261
                           urllib.parse.urlencode(params))
262
263
        if token:
264
            response = self.client.get(url, token=token)
265
        elif api_key:
266
            response = self.client.get(url, api_key=api_key)
267
        else:
268
            response = self.client.get(url)
269
270
        if response.status_code == 404:
271
            return []
272
        if response.status_code != 200:
273
            self.handle_error(response)
274
        items = response.json()
275
        instances = [self.resource.deserialize(item) for item in items]
276
        return instances
277
278
    @add_auth_token_to_kwargs_from_env
279
    def get_by_name(self, name_or_id, **kwargs):
280
        instances = self.query(name=name_or_id, **kwargs)
281
        if not instances:
282
            return None
283
        else:
284
            if len(instances) > 1:
285
                raise Exception('More than one %s named "%s" are found.' %
286
                                (self.resource.__name__.lower(), name_or_id))
287
            return instances[0]
288
289
    @add_auth_token_to_kwargs_from_env
290
    def create(self, instance, **kwargs):
291
        url = '/%s' % self.resource.get_url_path_name()
292
        response = self.client.post(url, instance.serialize(), **kwargs)
293
        if response.status_code != 200:
294
            self.handle_error(response)
295
        instance = self.resource.deserialize(response.json())
296
        return instance
297
298
    @add_auth_token_to_kwargs_from_env
299
    def update(self, instance, **kwargs):
300
        url = '/%s/%s' % (self.resource.get_url_path_name(), instance.id)
301
        response = self.client.put(url, instance.serialize(), **kwargs)
302
        if response.status_code != 200:
303
            self.handle_error(response)
304
        instance = self.resource.deserialize(response.json())
305
        return instance
306
307
    @add_auth_token_to_kwargs_from_env
308
    def delete(self, instance, **kwargs):
309
        url = '/%s/%s' % (self.resource.get_url_path_name(), instance.id)
310
        response = self.client.delete(url, **kwargs)
311
312
        if response.status_code not in [200, 204, 404]:
313
            self.handle_error(response)
314
            return False
315
316
        return True
317
318
    @add_auth_token_to_kwargs_from_env
319
    def delete_by_id(self, instance_id, **kwargs):
320
        url = '/%s/%s' % (self.resource.get_url_path_name(), instance_id)
321
        response = self.client.delete(url, **kwargs)
322
        if response.status_code not in [200, 204, 404]:
323
            self.handle_error(response)
324
            return False
325
        try:
326
            resp_json = response.json()
327
            if resp_json:
328
                return resp_json
329
        except:
330
            pass
331
        return True
332
333
334
class ActionAliasResourceManager(ResourceManager):
335
    def __init__(self, resource, endpoint, cacert=None, debug=False):
0 ignored issues
show
Bug introduced by
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...
336
        self.resource = resource
337
        self.debug = debug
338
        self.client = httpclient.HTTPClient(root=endpoint, cacert=cacert, debug=debug)
339
340
341
class LiveActionResourceManager(ResourceManager):
342
    @add_auth_token_to_kwargs_from_env
343
    def re_run(self, execution_id, parameters=None, tasks=None, no_reset=None, **kwargs):
344
        url = '/%s/%s/re_run' % (self.resource.get_url_path_name(), execution_id)
345
346
        tasks = tasks or []
347
        no_reset = no_reset or []
348
349
        if list(set(no_reset) - set(tasks)):
350
            raise ValueError('List of tasks to reset does not match the tasks to rerun.')
351
352
        data = {
353
            'parameters': parameters,
354
            'tasks': tasks,
355
            'reset': list(set(tasks) - set(no_reset))
356
        }
357
358
        response = self.client.post(url, data, **kwargs)
359
        if response.status_code != 200:
360
            self.handle_error(response)
361
362
        instance = self.resource.deserialize(response.json())
363
        return instance
364
365
366
class TriggerInstanceResourceManager(ResourceManager):
367
    @add_auth_token_to_kwargs_from_env
368
    def re_emit(self, trigger_instance_id, **kwargs):
369
        url = '/%s/%s/re_emit' % (self.resource.get_url_path_name(), trigger_instance_id)
370
        response = self.client.post(url, None)
371
        if response.status_code != 200:
372
            self.handle_error(response)
373
        return response.json()
374
375
376
class PackResourceManager(ResourceManager):
377
    @add_auth_token_to_kwargs_from_env
378
    def install(self, name, **kwargs):
379
        url = '/%s/install' % (self.resource.get_url_path_name())
380
        response = self.client.post(url, {'name': name})
381
        if response.status_code != 200:
382
            self.handle_error(response)
383
        return response.json()
384
385
    @add_auth_token_to_kwargs_from_env
386
    def remove(self, name, **kwargs):
387
        url = '/%s/%s/uninstall' % (self.resource.get_url_path_name(), name)
388
        response = self.client.post(url, None)
389
        if response.status_code != 200:
390
            self.handle_error(response)
391
        return response.json()
392
393
    @add_auth_token_to_kwargs_from_env
394
    def create(self, name, **kwargs):
395
        url = '/%s/init' % (self.resource.get_url_path_name())
396
        response = self.client.post(url, {'name': name})
397
        if response.status_code != 200:
398
            self.handle_error(response)
399
        return response.json()
400
401
    @add_auth_token_to_kwargs_from_env
402
    def search(self, query, **kwargs):
403
        url = '/%s/search' % (self.resource.get_url_path_name())
404
        response = self.client.post(url, {'query': query})
405
        if response.status_code != 200:
406
            self.handle_error(response)
407
        return response.json()
408
409
    @add_auth_token_to_kwargs_from_env
410
    def register(self, name, **kwargs):
411
        url = '/%s/register' % (self.resource.get_url_path_name())
412
        response = self.client.post(url, {'name': name})
413
        if response.status_code != 200:
414
            self.handle_error(response)
415
        return response.json()
416