|
1 |
| -from __future__ import absolute_import |
2 |
| -import re |
3 |
| -from cached_property import cached_property |
4 |
| - |
5 |
| -from wrapanapi.exceptions import (RequestFailedException, InvalidValueException, |
6 |
| - LabelNotFoundException, ResourceAlreadyExistsException, |
7 |
| - UncreatableResourceException) |
8 |
| - |
9 |
| - |
10 |
| -class ContainersResourceBase(object): |
11 |
| - """ |
12 |
| - A container resource base class. This class includes the base functions of (almost) |
13 |
| - all container resources. Each resource has its own API entry and use different API |
14 |
| - (Kubernetes or OpenShift). Each resource has get, post, patch and delete methods which |
15 |
| - directed to the path of the resource. |
16 |
| - The following parameters should be statically defined: |
17 |
| - * RESOURCE_TYPE: (str) the resource type name in the API |
18 |
| - * CREATABLE (optional): (bool) Specify whether this resource is creatable or not (some |
19 |
| - resources are not, e.g. Pod is created by Replication Controller |
20 |
| - and not manually). set to False by default. |
21 |
| - * (optional) API: (str) The API to use - the default is Kubernetes ('k_api') but some |
22 |
| - resources use OpenShift ('o_api'). |
23 |
| - * (optional) VALID_NAME_PATTERN: (str) the regex pattern that match a valid object name |
24 |
| - * (optional) KIND: (str) the resource 'Kind' property as it appear in JSON. |
25 |
| - if not specified, grabbing the Kind from the class name. |
26 |
| - """ |
27 |
| - CREATABLE = False |
28 |
| - API = 'k_api' |
29 |
| - |
30 |
| - def __init__(self, provider, name, namespace): |
31 |
| - """ |
32 |
| - Args: |
33 |
| - provider: (Openshift || Kubernetes) The containers provider |
34 |
| - name: (str) The name of the resource |
35 |
| - namespace: (str) The namespace used for this resource |
36 |
| - """ |
37 |
| - if hasattr(self, 'VALID_NAME_PATTERN') and not re.match(self.VALID_NAME_PATTERN, name): |
38 |
| - raise InvalidValueException('{0} name "{1}" is invalid. {0} name must ' |
39 |
| - 'match the regex "{2}"' |
40 |
| - .format(self.RESOURCE_TYPE, name, self.VALID_NAME_PATTERN)) |
41 |
| - self.provider = provider |
42 |
| - self.name = name |
43 |
| - self.namespace = namespace |
44 |
| - |
45 |
| - def __eq__(self, other): |
46 |
| - return (getattr(self, 'namespace', None) == getattr(other, 'namespace', None) and |
47 |
| - self.name == getattr(other, 'name', None)) |
48 |
| - |
49 |
| - def __repr__(self): |
50 |
| - return '<{} name="{}" namespace="{}">'.format( |
51 |
| - self.__class__.__name__, self.name, self.namespace) |
52 |
| - |
53 |
| - def exists(self): |
54 |
| - """Return whether this object exists or not.""" |
55 |
| - try: |
56 |
| - self.get() |
57 |
| - return True |
58 |
| - except RequestFailedException: |
59 |
| - return False |
60 |
| - |
61 |
| - @cached_property |
62 |
| - def api(self): |
63 |
| - """Return the used API according to the defined API.""" |
64 |
| - return getattr(self.provider, self.API) |
65 |
| - |
66 |
| - @classmethod |
67 |
| - def kind(cls): |
68 |
| - """Return the resource Kind property as it should be in the JSON""" |
69 |
| - return getattr(cls, 'KIND', None) or cls.__name__ |
70 |
| - |
71 |
| - @classmethod |
72 |
| - def create(cls, provider, payload): |
73 |
| - """Creating the object if it doesn't exist and creatable. |
74 |
| - Args: |
75 |
| - provider: (Openshift || Kubernetes) The containers provider. |
76 |
| - payload: The JSON data to create this object. |
77 |
| - Returns: |
78 |
| - The created instance of that resource. |
79 |
| - Raises: |
80 |
| - UncreatableResourceException, ResourceAlreadyExistsException. |
81 |
| - """ |
82 |
| - if not cls.CREATABLE: |
83 |
| - raise UncreatableResourceException(cls.RESOURCE_TYPE) |
84 |
| - api = getattr(provider, cls.API) |
85 |
| - # Checking name validity |
86 |
| - name = payload['metadata']['name'] |
87 |
| - if hasattr(cls, 'VALID_NAME_PATTERN') and not re.match(cls.VALID_NAME_PATTERN, name): |
88 |
| - raise InvalidValueException('{0} name "{1}" is invalid. {0} name must ' |
89 |
| - 'match the regex "{2}"' |
90 |
| - .format(cls.RESOURCE_TYPE, name, cls.VALID_NAME_PATTERN)) |
91 |
| - # Choosing the arguments accordingly, some resources are |
92 |
| - # not namespaced and require different arguments. |
93 |
| - if 'namespace' in payload['metadata']: |
94 |
| - obj = cls(provider, name, payload['metadata']['namespace']) |
95 |
| - else: |
96 |
| - obj = cls(provider, name) |
97 |
| - # Defining default/predefined parameters |
98 |
| - payload['apiVersion'] = payload.get('apiVersion', 'v1') |
99 |
| - payload['kind'] = cls.kind() |
100 |
| - # Checking existence |
101 |
| - if obj.exists(): |
102 |
| - raise ResourceAlreadyExistsException( |
103 |
| - '{} "{}" already exists.'.format(cls.RESOURCE_TYPE, obj.name)) |
104 |
| - status_code, json_content = api.post(cls.RESOURCE_TYPE, payload, |
105 |
| - namespace=obj.namespace) |
106 |
| - # Verifying success |
107 |
| - if status_code not in (200, 201): |
108 |
| - raise RequestFailedException( |
109 |
| - 'Failed to create {} "{}". status_code: {}; json_content: {};' |
110 |
| - .format(cls.RESOURCE_TYPE, obj.name, status_code, json_content) |
111 |
| - ) |
112 |
| - return obj |
113 |
| - |
114 |
| - @property |
115 |
| - def name_for_api(self): |
116 |
| - """The name used for the API (In Image the name for API is id)""" |
117 |
| - return self.name |
118 |
| - |
119 |
| - @property |
120 |
| - def project_name(self): |
121 |
| - # For backward compatibility |
122 |
| - return self.namespace |
123 |
| - |
124 |
| - @property |
125 |
| - def metadata(self): |
126 |
| - return self.get()['metadata'] |
127 |
| - |
128 |
| - @property |
129 |
| - def spec(self): |
130 |
| - return self.get()['spec'] |
131 |
| - |
132 |
| - @property |
133 |
| - def status(self): |
134 |
| - return self.get()['status'] |
135 |
| - |
136 |
| - def get(self, convert=None): |
137 |
| - """Sends a GET request to the resource.""" |
138 |
| - status_code, json_content = self.api.get(self.RESOURCE_TYPE, name=self.name_for_api, |
139 |
| - namespace=self.namespace, convert=convert) |
140 |
| - if status_code != 200: |
141 |
| - raise RequestFailedException('GET request of {} "{}" returned status code {}. ' |
142 |
| - 'json content: {}' |
143 |
| - .format(self.RESOURCE_TYPE, self.name_for_api, |
144 |
| - status_code, json_content)) |
145 |
| - return json_content |
146 |
| - |
147 |
| - def post(self, data, convert=None): |
148 |
| - """Sends a POST request with the given data to the resource.""" |
149 |
| - return self.api.post(self.RESOURCE_TYPE, data, name=self.name_for_api, |
150 |
| - namespace=self.namespace, convert=convert) |
151 |
| - |
152 |
| - def patch(self, data, convert=None, |
153 |
| - headers={'Content-Type': 'application/strategic-merge-patch+json'}): |
154 |
| - """Sends a PATCH request with the given data/headers to the resource.""" |
155 |
| - return self.api.patch(self.RESOURCE_TYPE, data, name=self.name_for_api, |
156 |
| - namespace=self.namespace, convert=convert, headers=headers) |
157 |
| - |
158 |
| - def delete(self, convert=None): |
159 |
| - """Sends a DELETE request to the resource (delete the resource).""" |
160 |
| - return self.api.delete(self.RESOURCE_TYPE, self.name_for_api, |
161 |
| - namespace=self.namespace, convert=convert) |
162 |
| - |
163 |
| - def list_labels(self): |
164 |
| - """List the labels of this resource""" |
165 |
| - return self.metadata.get('labels', {}) |
166 |
| - |
167 |
| - def set_label(self, key, value): |
168 |
| - """Sets a label for this resource""" |
169 |
| - return self.patch({'metadata': {'labels': {key: str(value)}}}) |
170 |
| - |
171 |
| - def delete_label(self, key): |
172 |
| - """Deletes a label from this resource""" |
173 |
| - original_labels = self.list_labels() |
174 |
| - if key not in original_labels: |
175 |
| - raise LabelNotFoundException(key) |
176 |
| - del original_labels[key] |
177 |
| - labels = {'$patch': 'replace'} |
178 |
| - labels.update(original_labels) |
179 |
| - return self.patch({'metadata': {'labels': labels}}) |
0 commit comments