1 |
|
# Copyright 2019 Whitestack, LLC |
2 |
|
# |
3 |
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may |
4 |
|
# not use this file except in compliance with the License. You may obtain |
5 |
|
# a copy of the License at |
6 |
|
# |
7 |
|
# http://www.apache.org/licenses/LICENSE-2.0 |
8 |
|
# |
9 |
|
# Unless required by applicable law or agreed to in writing, software |
10 |
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
11 |
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
12 |
|
# License for the specific language governing permissions and limitations |
13 |
|
# under the License. |
14 |
|
# |
15 |
|
# For those usages not covered by the Apache License, Version 2.0 please |
16 |
|
# contact: esousa@whitestack.com or glavado@whitestack.com |
17 |
|
## |
18 |
|
|
19 |
1 |
""" |
20 |
|
OSM role mgmt API |
21 |
|
""" |
22 |
|
|
23 |
1 |
from osmclient.common import utils |
24 |
1 |
from osmclient.common.exceptions import ClientException |
25 |
1 |
from osmclient.common.exceptions import NotFound |
26 |
1 |
import json |
27 |
1 |
import yaml |
28 |
1 |
import logging |
29 |
|
|
30 |
|
|
31 |
1 |
class Role(object): |
32 |
1 |
def __init__(self, http=None, client=None): |
33 |
0 |
self._http = http |
34 |
0 |
self._client = client |
35 |
0 |
self._logger = logging.getLogger("osmclient") |
36 |
0 |
self._apiName = "/admin" |
37 |
0 |
self._apiVersion = "/v1" |
38 |
0 |
self._apiResource = "/roles" |
39 |
0 |
self._apiBase = "{}{}{}".format( |
40 |
|
self._apiName, self._apiVersion, self._apiResource |
41 |
|
) |
42 |
|
|
43 |
1 |
def create(self, name, permissions): |
44 |
|
""" |
45 |
|
Creates a new OSM role. |
46 |
|
|
47 |
|
:param name: name of the role. |
48 |
|
:param permissions: permissions of the role in YAML. |
49 |
|
:raises ClientException: when receives an unexpected from the server. |
50 |
|
:raises ClientException: when fails creating a role. |
51 |
|
""" |
52 |
0 |
self._logger.debug("") |
53 |
0 |
self._client.get_token() |
54 |
0 |
role = {"name": name} |
55 |
|
|
56 |
0 |
if permissions: |
57 |
0 |
role_permissions = yaml.safe_load(permissions) |
58 |
|
|
59 |
0 |
if not isinstance(role_permissions, dict): |
60 |
0 |
raise ClientException( |
61 |
|
"Role permissions should be provided in a key-value fashion" |
62 |
|
) |
63 |
|
|
64 |
0 |
for key, value in role_permissions.items(): |
65 |
0 |
if not isinstance(value, bool): |
66 |
0 |
raise ClientException( |
67 |
|
"Value of '{}' in a role permissions should be boolean".format( |
68 |
|
key |
69 |
|
) |
70 |
|
) |
71 |
|
|
72 |
0 |
role["permissions"] = role_permissions |
73 |
|
|
74 |
0 |
http_code, resp = self._http.post_cmd( |
75 |
|
endpoint=self._apiBase, postfields_dict=role, skip_query_admin=True |
76 |
|
) |
77 |
|
# print('HTTP CODE: {}'.format(http_code)) |
78 |
|
# print('RESP: {}'.format(resp)) |
79 |
|
# if http_code in (200, 201, 202, 204): |
80 |
0 |
if resp: |
81 |
0 |
resp = json.loads(resp) |
82 |
0 |
if not resp or "id" not in resp: |
83 |
0 |
raise ClientException("Unexpected response from server - {}".format(resp)) |
84 |
0 |
print(resp["id"]) |
85 |
|
# else: |
86 |
|
# msg = "" |
87 |
|
# if resp: |
88 |
|
# try: |
89 |
|
# msg = json.loads(resp) |
90 |
|
# except ValueError: |
91 |
|
# msg = resp |
92 |
|
# raise ClientException("Failed to create role {} - {}".format(name, msg)) |
93 |
|
|
94 |
1 |
def update(self, name, new_name, permissions, add=None, remove=None): |
95 |
|
""" |
96 |
|
Updates an OSM role identified by name. |
97 |
|
|
98 |
|
NOTE: definition and add/remove are mutually exclusive. |
99 |
|
|
100 |
|
:param name: name of the role |
101 |
|
:param set_name: if provided, change the name. |
102 |
|
:param permissions: if provided, overwrites the existing role specification. NOT IMPLEMENTED |
103 |
|
:param add: if provided, adds new rules to the definition. |
104 |
|
:param remove: if provided, removes rules from the definition. |
105 |
|
:raises ClientException: when receives an unexpected response from the server. |
106 |
|
:raises ClientException: when fails updating a role. |
107 |
|
""" |
108 |
0 |
self._logger.debug("") |
109 |
0 |
self._client.get_token() |
110 |
0 |
if new_name is None and permissions is None and add is None and remove is None: |
111 |
0 |
raise ClientException("At least one option should be provided") |
112 |
0 |
elif permissions and (add or remove): |
113 |
0 |
raise ClientException("permissions and add/remove are mutually exclusive") |
114 |
|
|
115 |
0 |
role_obj = self.get(name) |
116 |
0 |
new_role_obj = {"permissions": {}} |
117 |
0 |
if new_name: |
118 |
0 |
new_role_obj["name"] = new_name |
119 |
|
|
120 |
0 |
if permissions: |
121 |
0 |
role_definition = yaml.safe_load(permissions) |
122 |
|
|
123 |
0 |
if not isinstance(role_definition, dict): |
124 |
0 |
raise ClientException( |
125 |
|
"Role permissions should be provided in a key-value fashion" |
126 |
|
) |
127 |
|
|
128 |
0 |
for key, value in role_definition.items(): |
129 |
0 |
if not isinstance(value, bool) and value is not None: |
130 |
0 |
raise ClientException( |
131 |
|
"Value in a role permissions should be boolean or None to remove" |
132 |
|
) |
133 |
|
|
134 |
0 |
new_role_obj["permissions"] = role_definition |
135 |
|
else: |
136 |
0 |
if remove: |
137 |
0 |
keys_from_remove = yaml.safe_load(remove) |
138 |
|
|
139 |
0 |
if not isinstance(keys_from_remove, list): |
140 |
0 |
raise ClientException("Keys should be provided in a list fashion") |
141 |
|
|
142 |
0 |
for key in keys_from_remove: |
143 |
0 |
if not isinstance(key, str): |
144 |
0 |
raise ClientException("Individual keys should be strings") |
145 |
0 |
new_role_obj["permissions"][key] = None |
146 |
|
|
147 |
0 |
if add: |
148 |
0 |
add_roles = yaml.safe_load(add) |
149 |
|
|
150 |
0 |
if not isinstance(add_roles, dict): |
151 |
0 |
raise ClientException( |
152 |
|
"Add should be provided in a key-value fashion" |
153 |
|
) |
154 |
|
|
155 |
0 |
for key, value in add_roles.items(): |
156 |
0 |
if not isinstance(value, bool): |
157 |
0 |
raise ClientException( |
158 |
|
"Value '{}' in a role permissions should be boolean".format( |
159 |
|
key |
160 |
|
) |
161 |
|
) |
162 |
|
|
163 |
0 |
new_role_obj["permissions"][key] = value |
164 |
0 |
if not new_role_obj["permissions"]: |
165 |
0 |
del new_role_obj["permissions"] |
166 |
|
|
167 |
0 |
http_code, resp = self._http.patch_cmd( |
168 |
|
endpoint="{}/{}".format(self._apiBase, role_obj["_id"]), |
169 |
|
postfields_dict=new_role_obj, |
170 |
|
skip_query_admin=True, |
171 |
|
) |
172 |
|
# print('HTTP CODE: {}'.format(http_code)) |
173 |
|
# print('RESP: {}'.format(resp)) |
174 |
0 |
if http_code in (200, 201, 202): |
175 |
0 |
if resp: |
176 |
0 |
resp = json.loads(resp) |
177 |
0 |
if not resp or "id" not in resp: |
178 |
0 |
raise ClientException( |
179 |
|
"Unexpected response from server - {}".format(resp) |
180 |
|
) |
181 |
0 |
print(resp["id"]) |
182 |
0 |
elif http_code == 204: |
183 |
0 |
print("Updated") |
184 |
|
# else: |
185 |
|
# msg = "" |
186 |
|
# if resp: |
187 |
|
# try: |
188 |
|
# msg = json.loads(resp) |
189 |
|
# except ValueError: |
190 |
|
# msg = resp |
191 |
|
# raise ClientException("Failed to update role {} - {}".format(name, msg)) |
192 |
|
|
193 |
1 |
def delete(self, name, force=False): |
194 |
|
""" |
195 |
|
Deletes an OSM role identified by name. |
196 |
|
|
197 |
|
:param name: |
198 |
|
:param force: |
199 |
|
:raises ClientException: when fails to delete a role. |
200 |
|
""" |
201 |
0 |
self._logger.debug("") |
202 |
0 |
self._client.get_token() |
203 |
0 |
role = self.get(name) |
204 |
0 |
querystring = "" |
205 |
0 |
if force: |
206 |
0 |
querystring = "?FORCE=True" |
207 |
0 |
http_code, resp = self._http.delete_cmd( |
208 |
|
"{}/{}{}".format(self._apiBase, role["_id"], querystring), |
209 |
|
skip_query_admin=True, |
210 |
|
) |
211 |
|
# print('HTTP CODE: {}'.format(http_code)) |
212 |
|
# print('RESP: {}'.format(resp)) |
213 |
0 |
if http_code == 202: |
214 |
0 |
print("Deletion in progress") |
215 |
0 |
elif http_code == 204: |
216 |
0 |
print("Deleted") |
217 |
0 |
elif resp and "result" in resp: |
218 |
0 |
print("Deleted") |
219 |
|
else: |
220 |
0 |
msg = resp or "" |
221 |
|
# if resp: |
222 |
|
# try: |
223 |
|
# msg = json.loads(resp) |
224 |
|
# except ValueError: |
225 |
|
# msg = resp |
226 |
0 |
raise ClientException("Failed to delete role {} - {}".format(name, msg)) |
227 |
|
|
228 |
1 |
def list(self, filter=None): |
229 |
|
""" |
230 |
|
Returns the list of OSM role. |
231 |
|
|
232 |
|
:param filter: |
233 |
|
:returns: |
234 |
|
""" |
235 |
0 |
self._logger.debug("") |
236 |
0 |
self._client.get_token() |
237 |
0 |
filter_string = "" |
238 |
0 |
if filter: |
239 |
0 |
filter_string = "?{}".format(filter) |
240 |
0 |
_, resp = self._http.get2_cmd( |
241 |
|
"{}{}".format(self._apiBase, filter_string), skip_query_admin=True |
242 |
|
) |
243 |
|
# print('RESP: {}'.format(resp)) |
244 |
0 |
if resp: |
245 |
0 |
return json.loads(resp) |
246 |
0 |
return list() |
247 |
|
|
248 |
1 |
def get(self, name): |
249 |
|
""" |
250 |
|
Returns a specific OSM role based on name or id. |
251 |
|
|
252 |
|
:param name: |
253 |
|
:raises NotFound: when the role is not found. |
254 |
|
:returns: the specified role. |
255 |
|
""" |
256 |
0 |
self._logger.debug("") |
257 |
0 |
self._client.get_token() |
258 |
0 |
if utils.validate_uuid4(name): |
259 |
0 |
for role in self.list(): |
260 |
0 |
if name == role["_id"]: |
261 |
0 |
return role |
262 |
|
else: |
263 |
0 |
for role in self.list(): |
264 |
0 |
if name == role["name"]: |
265 |
0 |
return role |
266 |
0 |
raise NotFound("Role {} not found".format(name)) |