1 |
|
# Copyright ETSI Contributors and Others. |
2 |
|
# All Rights Reserved. |
3 |
|
# |
4 |
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may |
5 |
|
# not use this file except in compliance with the License. You may obtain |
6 |
|
# 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, WITHOUT |
12 |
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
13 |
|
# License for the specific language governing permissions and limitations |
14 |
|
# under the License. |
15 |
|
|
16 |
1 |
import click |
17 |
1 |
from osmclient.common.exceptions import ClientException |
18 |
1 |
from osmclient.cli_commands import utils |
19 |
1 |
from prettytable import PrettyTable |
20 |
1 |
import json |
21 |
1 |
import logging |
22 |
1 |
import time |
23 |
|
|
24 |
1 |
logger = logging.getLogger("osmclient") |
25 |
|
|
26 |
|
|
27 |
|
############################## |
28 |
|
# Role Management Operations # |
29 |
|
############################## |
30 |
|
|
31 |
|
|
32 |
1 |
@click.command(name="role-create", short_help="creates a new role") |
33 |
1 |
@click.argument("name") |
34 |
1 |
@click.option("--permissions", default=None, help="role permissions using a dictionary") |
35 |
1 |
@click.pass_context |
36 |
1 |
def role_create(ctx, name, permissions): |
37 |
|
""" |
38 |
|
Creates a new role. |
39 |
|
|
40 |
|
\b |
41 |
|
NAME: Name or ID of the role. |
42 |
|
DEFINITION: Definition of grant/denial of access to resources. |
43 |
|
""" |
44 |
0 |
logger.debug("") |
45 |
0 |
utils.check_client_version(ctx.obj, ctx.command.name) |
46 |
0 |
ctx.obj.role.create(name, permissions) |
47 |
|
|
48 |
|
|
49 |
1 |
@click.command(name="role-update", short_help="updates a role") |
50 |
1 |
@click.argument("name") |
51 |
1 |
@click.option("--set-name", default=None, help="change name of rle") |
52 |
1 |
@click.option( |
53 |
|
"--add", |
54 |
|
default=None, |
55 |
|
help="yaml format dictionary with permission: True/False to access grant/denial", |
56 |
|
) |
57 |
1 |
@click.option("--remove", default=None, help="yaml format list to remove a permission") |
58 |
1 |
@click.pass_context |
59 |
1 |
def role_update(ctx, name, set_name, add, remove): |
60 |
|
""" |
61 |
|
Updates a role. |
62 |
|
|
63 |
|
\b |
64 |
|
NAME: Name or ID of the role. |
65 |
|
DEFINITION: Definition overwrites the old definition. |
66 |
|
ADD: Grant/denial of access to resource to add. |
67 |
|
REMOVE: Grant/denial of access to resource to remove. |
68 |
|
""" |
69 |
0 |
logger.debug("") |
70 |
0 |
utils.check_client_version(ctx.obj, ctx.command.name) |
71 |
0 |
ctx.obj.role.update(name, set_name, None, add, remove) |
72 |
|
|
73 |
|
|
74 |
1 |
@click.command(name="role-delete", short_help="deletes a role") |
75 |
1 |
@click.argument("name") |
76 |
1 |
@click.pass_context |
77 |
1 |
def role_delete(ctx, name): |
78 |
|
""" |
79 |
|
Deletes a role. |
80 |
|
|
81 |
|
\b |
82 |
|
NAME: Name or ID of the role. |
83 |
|
""" |
84 |
0 |
logger.debug("") |
85 |
0 |
utils.check_client_version(ctx.obj, ctx.command.name) |
86 |
0 |
ctx.obj.role.delete(name) |
87 |
|
|
88 |
|
|
89 |
1 |
@click.command(name="role-list", short_help="list all roles") |
90 |
1 |
@click.option( |
91 |
|
"--filter", |
92 |
|
default=None, |
93 |
|
multiple=True, |
94 |
|
help="restricts the list to the projects matching the filter", |
95 |
|
) |
96 |
1 |
@click.pass_context |
97 |
1 |
def role_list(ctx, filter): |
98 |
|
""" |
99 |
|
List all roles. |
100 |
|
""" |
101 |
0 |
logger.debug("") |
102 |
0 |
utils.check_client_version(ctx.obj, ctx.command.name) |
103 |
0 |
if filter: |
104 |
0 |
filter = "&".join(filter) |
105 |
0 |
resp = ctx.obj.role.list(filter) |
106 |
0 |
table = PrettyTable(["name", "id"]) |
107 |
0 |
for role in resp: |
108 |
0 |
table.add_row([role["name"], role["_id"]]) |
109 |
0 |
table.align = "l" |
110 |
0 |
print(table) |
111 |
|
|
112 |
|
|
113 |
1 |
@click.command(name="role-show", short_help="show specific role") |
114 |
1 |
@click.argument("name") |
115 |
1 |
@click.pass_context |
116 |
1 |
def role_show(ctx, name): |
117 |
|
""" |
118 |
|
Shows the details of a role. |
119 |
|
|
120 |
|
\b |
121 |
|
NAME: Name or ID of the role. |
122 |
|
""" |
123 |
0 |
logger.debug("") |
124 |
0 |
utils.check_client_version(ctx.obj, ctx.command.name) |
125 |
0 |
resp = ctx.obj.role.get(name) |
126 |
|
|
127 |
0 |
table = PrettyTable(["key", "attribute"]) |
128 |
0 |
for k, v in resp.items(): |
129 |
0 |
table.add_row([k, json.dumps(v, indent=2)]) |
130 |
0 |
table.align = "l" |
131 |
0 |
print(table) |
132 |
|
|
133 |
|
|
134 |
|
#################### |
135 |
|
# Project mgmt operations |
136 |
|
#################### |
137 |
|
|
138 |
|
|
139 |
1 |
@click.command(name="project-create", short_help="creates a new project") |
140 |
1 |
@click.argument("name") |
141 |
|
# @click.option('--description', |
142 |
|
# default='no description', |
143 |
|
# help='human readable description') |
144 |
1 |
@click.option("--domain-name", "domain_name", default=None, help="assign to a domain") |
145 |
1 |
@click.option( |
146 |
|
"--quotas", |
147 |
|
"quotas", |
148 |
|
multiple=True, |
149 |
|
default=None, |
150 |
|
help="provide quotas. Can be used several times: 'quota1=number[,quota2=number,...]'. Quotas can be one " |
151 |
|
"of vnfds, nsds, nsts, pdus, nsrs, nsis, vim_accounts, wim_accounts, sdns, k8sclusters, k8srepos", |
152 |
|
) |
153 |
1 |
@click.pass_context |
154 |
1 |
def project_create(ctx, name, domain_name, quotas): |
155 |
|
"""Creates a new project |
156 |
|
|
157 |
|
NAME: name of the project |
158 |
|
DOMAIN_NAME: optional domain name for the project when keystone authentication is used |
159 |
|
QUOTAS: set quotas for the project |
160 |
|
""" |
161 |
0 |
logger.debug("") |
162 |
0 |
project = {"name": name} |
163 |
0 |
if domain_name: |
164 |
0 |
project["domain_name"] = domain_name |
165 |
0 |
quotas_dict = _process_project_quotas(quotas) |
166 |
0 |
if quotas_dict: |
167 |
0 |
project["quotas"] = quotas_dict |
168 |
|
|
169 |
0 |
utils.check_client_version(ctx.obj, ctx.command.name) |
170 |
0 |
ctx.obj.project.create(name, project) |
171 |
|
|
172 |
|
|
173 |
1 |
def _process_project_quotas(quota_list): |
174 |
0 |
quotas_dict = {} |
175 |
0 |
if not quota_list: |
176 |
0 |
return quotas_dict |
177 |
0 |
try: |
178 |
0 |
for quota in quota_list: |
179 |
0 |
for single_quota in quota.split(","): |
180 |
0 |
k, v = single_quota.split("=") |
181 |
0 |
quotas_dict[k] = None if v in ("None", "null", "") else int(v) |
182 |
0 |
except (ValueError, TypeError): |
183 |
0 |
raise ClientException( |
184 |
|
"invalid format for 'quotas'. Use 'k1=v1,v1=v2'. v must be a integer or null" |
185 |
|
) |
186 |
0 |
return quotas_dict |
187 |
|
|
188 |
|
|
189 |
1 |
@click.command(name="project-delete", short_help="deletes a project") |
190 |
1 |
@click.argument("name") |
191 |
1 |
@click.pass_context |
192 |
1 |
def project_delete(ctx, name): |
193 |
|
"""deletes a project |
194 |
|
|
195 |
|
NAME: name or ID of the project to be deleted |
196 |
|
""" |
197 |
0 |
logger.debug("") |
198 |
0 |
utils.check_client_version(ctx.obj, ctx.command.name) |
199 |
0 |
ctx.obj.project.delete(name) |
200 |
|
|
201 |
|
|
202 |
1 |
@click.command(name="project-list", short_help="list all projects") |
203 |
1 |
@click.option( |
204 |
|
"--filter", |
205 |
|
default=None, |
206 |
|
multiple=True, |
207 |
|
help="restricts the list to the projects matching the filter", |
208 |
|
) |
209 |
1 |
@click.pass_context |
210 |
1 |
def project_list(ctx, filter): |
211 |
|
"""list all projects""" |
212 |
0 |
logger.debug("") |
213 |
0 |
utils.check_client_version(ctx.obj, ctx.command.name) |
214 |
0 |
if filter: |
215 |
0 |
filter = "&".join(filter) |
216 |
0 |
resp = ctx.obj.project.list(filter) |
217 |
0 |
table = PrettyTable(["name", "id"]) |
218 |
0 |
for proj in resp: |
219 |
0 |
table.add_row([proj["name"], proj["_id"]]) |
220 |
0 |
table.align = "l" |
221 |
0 |
print(table) |
222 |
|
|
223 |
|
|
224 |
1 |
@click.command(name="project-show", short_help="shows the details of a project") |
225 |
1 |
@click.argument("name") |
226 |
1 |
@click.pass_context |
227 |
1 |
def project_show(ctx, name): |
228 |
|
"""shows the details of a project |
229 |
|
|
230 |
|
NAME: name or ID of the project |
231 |
|
""" |
232 |
0 |
logger.debug("") |
233 |
0 |
utils.check_client_version(ctx.obj, ctx.command.name) |
234 |
0 |
resp = ctx.obj.project.get(name) |
235 |
|
|
236 |
0 |
table = PrettyTable(["key", "attribute"]) |
237 |
0 |
for k, v in resp.items(): |
238 |
0 |
table.add_row([k, json.dumps(v, indent=2)]) |
239 |
0 |
table.align = "l" |
240 |
0 |
print(table) |
241 |
|
|
242 |
|
|
243 |
1 |
@click.command( |
244 |
|
name="project-update", short_help="updates a project (only the name can be updated)" |
245 |
|
) |
246 |
1 |
@click.argument("project") |
247 |
1 |
@click.option("--name", default=None, help="new name for the project") |
248 |
1 |
@click.option( |
249 |
|
"--quotas", |
250 |
|
"quotas", |
251 |
|
multiple=True, |
252 |
|
default=None, |
253 |
|
help="change quotas. Can be used several times: 'quota1=number|empty[,quota2=...]' " |
254 |
|
"(use empty to reset quota to default", |
255 |
|
) |
256 |
1 |
@click.pass_context |
257 |
1 |
def project_update(ctx, project, name, quotas): |
258 |
|
""" |
259 |
|
Update a project name |
260 |
|
|
261 |
|
:param ctx: |
262 |
|
:param project: id or name of the project to modify |
263 |
|
:param name: new name for the project |
264 |
|
:param quotas: change quotas of the project |
265 |
|
:return: |
266 |
|
""" |
267 |
0 |
logger.debug("") |
268 |
0 |
project_changes = {} |
269 |
0 |
if name: |
270 |
0 |
project_changes["name"] = name |
271 |
0 |
quotas_dict = _process_project_quotas(quotas) |
272 |
0 |
if quotas_dict: |
273 |
0 |
project_changes["quotas"] = quotas_dict |
274 |
|
|
275 |
0 |
utils.check_client_version(ctx.obj, ctx.command.name) |
276 |
0 |
ctx.obj.project.update(project, project_changes) |
277 |
|
|
278 |
|
|
279 |
|
#################### |
280 |
|
# User mgmt operations |
281 |
|
#################### |
282 |
|
|
283 |
|
|
284 |
1 |
@click.command(name="user-create", short_help="creates a new user") |
285 |
1 |
@click.argument("username") |
286 |
1 |
@click.option( |
287 |
|
"--password", |
288 |
|
prompt=True, |
289 |
|
hide_input=True, |
290 |
|
confirmation_prompt=True, |
291 |
|
help="user password", |
292 |
|
) |
293 |
1 |
@click.option( |
294 |
|
"--projects", |
295 |
|
# prompt="Comma separate list of projects", |
296 |
|
multiple=True, |
297 |
|
callback=lambda ctx, param, value: ( |
298 |
|
"".join(value).split(",") if all(len(x) == 1 for x in value) else value |
299 |
|
), |
300 |
|
help="list of project ids that the user belongs to", |
301 |
|
) |
302 |
1 |
@click.option( |
303 |
|
"--project-role-mappings", |
304 |
|
"project_role_mappings", |
305 |
|
default=None, |
306 |
|
multiple=True, |
307 |
|
help="assign role(s) in a project. Can be used several times: 'project,role1[,role2,...]'", |
308 |
|
) |
309 |
1 |
@click.option("--domain-name", "domain_name", default=None, help="assign to a domain") |
310 |
1 |
@click.pass_context |
311 |
1 |
def user_create(ctx, username, password, projects, project_role_mappings, domain_name): |
312 |
|
"""Creates a new user |
313 |
|
|
314 |
|
\b |
315 |
|
USERNAME: name of the user |
316 |
|
PASSWORD: password of the user |
317 |
|
PROJECTS: projects assigned to user (internal only) |
318 |
|
PROJECT_ROLE_MAPPING: roles in projects assigned to user (keystone) |
319 |
|
DOMAIN_NAME: optional domain name for the user when keystone authentication is used |
320 |
|
""" |
321 |
0 |
logger.debug("") |
322 |
0 |
user = {} |
323 |
0 |
user["username"] = username |
324 |
0 |
user["password"] = password |
325 |
0 |
user["projects"] = projects |
326 |
0 |
user["project_role_mappings"] = project_role_mappings |
327 |
0 |
if domain_name: |
328 |
0 |
user["domain_name"] = domain_name |
329 |
|
|
330 |
0 |
utils.check_client_version(ctx.obj, ctx.command.name) |
331 |
0 |
ctx.obj.user.create(username, user) |
332 |
|
|
333 |
|
|
334 |
1 |
@click.command(name="user-update", short_help="updates user information") |
335 |
1 |
@click.argument("username") |
336 |
1 |
@click.option("--set-username", "set_username", default=None, help="change username") |
337 |
1 |
@click.option( |
338 |
|
"--set-project", |
339 |
|
"set_project", |
340 |
|
default=None, |
341 |
|
multiple=True, |
342 |
|
help="create/replace the roles for this project: 'project,role1[,role2,...]'", |
343 |
|
) |
344 |
1 |
@click.option( |
345 |
|
"--remove-project", |
346 |
|
"remove_project", |
347 |
|
default=None, |
348 |
|
multiple=True, |
349 |
|
help="removes project from user: 'project'", |
350 |
|
) |
351 |
1 |
@click.option( |
352 |
|
"--add-project-role", |
353 |
|
"add_project_role", |
354 |
|
default=None, |
355 |
|
multiple=True, |
356 |
|
help="assign role(s) in a project. Can be used several times: 'project,role1[,role2,...]'", |
357 |
|
) |
358 |
1 |
@click.option( |
359 |
|
"--remove-project-role", |
360 |
|
"remove_project_role", |
361 |
|
default=None, |
362 |
|
multiple=True, |
363 |
|
help="remove role(s) in a project. Can be used several times: 'project,role1[,role2,...]'", |
364 |
|
) |
365 |
1 |
@click.option("--current-password", "current_password", help="user's current password") |
366 |
1 |
@click.option( |
367 |
|
"--new-password", |
368 |
|
"new_password", |
369 |
|
# prompt=True, |
370 |
|
# hide_input=True, |
371 |
|
# confirmation_prompt=True, |
372 |
|
help="new user password", |
373 |
|
) |
374 |
1 |
@click.option( |
375 |
|
"--unlock", |
376 |
|
is_flag=True, |
377 |
|
help="unlock user", |
378 |
|
) |
379 |
1 |
@click.option( |
380 |
|
"--renew", |
381 |
|
is_flag=True, |
382 |
|
help="renew user", |
383 |
|
) |
384 |
1 |
@click.pass_context |
385 |
1 |
def user_update( |
386 |
|
ctx, |
387 |
|
username, |
388 |
|
set_username, |
389 |
|
set_project, |
390 |
|
remove_project, |
391 |
|
add_project_role, |
392 |
|
remove_project_role, |
393 |
|
current_password, |
394 |
|
new_password, |
395 |
|
unlock, |
396 |
|
renew, |
397 |
|
): |
398 |
|
"""Update a user information |
399 |
|
|
400 |
|
\b |
401 |
|
USERNAME: name of the user |
402 |
|
SET_USERNAME: new username |
403 |
|
SET_PROJECT: creating mappings for project/role(s) |
404 |
|
REMOVE_PROJECT: deleting mappings for project/role(s) |
405 |
|
ADD_PROJECT_ROLE: adding mappings for project/role(s) |
406 |
|
REMOVE_PROJECT_ROLE: removing mappings for project/role(s) |
407 |
|
CURRENT_PASSWORD: user's current password to change |
408 |
|
NEW_PASSWORD: user's new password to be updated |
409 |
|
UNLOCK: unlock user |
410 |
|
RENEW: renew user |
411 |
|
""" |
412 |
0 |
logger.debug("") |
413 |
0 |
user = {} |
414 |
0 |
user["username"] = set_username |
415 |
0 |
user["set-project"] = set_project |
416 |
0 |
user["remove-project"] = remove_project |
417 |
0 |
user["add-project-role"] = add_project_role |
418 |
0 |
user["remove-project-role"] = remove_project_role |
419 |
0 |
user["current_password"] = current_password |
420 |
0 |
user["new_password"] = new_password |
421 |
0 |
user["unlock"] = unlock |
422 |
0 |
user["renew"] = renew |
423 |
|
|
424 |
0 |
utils.check_client_version(ctx.obj, ctx.command.name) |
425 |
0 |
if not user.get("current_password"): |
426 |
|
# In case the password is valid but the end user wants to update it |
427 |
0 |
ctx.obj.user.update(username, user) |
428 |
|
else: |
429 |
|
# In case the password has expired (also applies in first login) |
430 |
0 |
ctx.obj.user.update(username, user, pwd_change=True) |
431 |
|
|
432 |
|
|
433 |
1 |
@click.command(name="user-delete", short_help="deletes a user") |
434 |
1 |
@click.argument("name") |
435 |
|
# @click.option('--force', is_flag=True, help='forces the deletion bypassing pre-conditions') |
436 |
1 |
@click.pass_context |
437 |
1 |
def user_delete(ctx, name): |
438 |
|
"""deletes a user |
439 |
|
|
440 |
|
\b |
441 |
|
NAME: name or ID of the user to be deleted |
442 |
|
""" |
443 |
0 |
logger.debug("") |
444 |
0 |
utils.check_client_version(ctx.obj, ctx.command.name) |
445 |
0 |
ctx.obj.user.delete(name) |
446 |
|
|
447 |
|
|
448 |
1 |
@click.command(name="user-list", short_help="list all users") |
449 |
1 |
@click.option( |
450 |
|
"--filter", |
451 |
|
default=None, |
452 |
|
multiple=True, |
453 |
|
help="restricts the list to the users matching the filter", |
454 |
|
) |
455 |
1 |
@click.pass_context |
456 |
1 |
def user_list(ctx, filter): |
457 |
|
"""list all users""" |
458 |
0 |
utils.check_client_version(ctx.obj, ctx.command.name) |
459 |
0 |
if filter: |
460 |
0 |
filter = "&".join(filter) |
461 |
0 |
resp, admin_show = ctx.obj.user.list(filter) |
462 |
0 |
for user in resp: |
463 |
0 |
if user["username"] == "admin": |
464 |
0 |
user["_admin"]["account_expire_time"] = "N/A" |
465 |
0 |
if admin_show: |
466 |
0 |
table = PrettyTable(["name", "id", "user_status", "expires_in"]) |
467 |
0 |
for user in resp: |
468 |
0 |
table.add_row( |
469 |
|
[ |
470 |
|
user["username"], |
471 |
|
user["_id"], |
472 |
|
user["_admin"]["user_status"].upper(), |
473 |
|
( |
474 |
|
time.strftime( |
475 |
|
"%b-%d-%Y %X", |
476 |
|
time.gmtime(user["_admin"]["account_expire_time"]), |
477 |
|
) |
478 |
|
if not user["username"] == "admin" |
479 |
|
else user["_admin"]["account_expire_time"] |
480 |
|
), |
481 |
|
] |
482 |
|
) |
483 |
|
else: |
484 |
0 |
table = PrettyTable(["name", "id"]) |
485 |
0 |
for user in resp: |
486 |
0 |
table.add_row([user["username"], user["_id"]]) |
487 |
0 |
table.align = "l" |
488 |
0 |
print(table) |
489 |
|
|
490 |
|
|
491 |
1 |
@click.command(name="user-show", short_help="shows the details of a user") |
492 |
1 |
@click.argument("name") |
493 |
1 |
@click.pass_context |
494 |
1 |
def user_show(ctx, name): |
495 |
|
"""shows the details of a user |
496 |
|
|
497 |
|
NAME: name or ID of the user |
498 |
|
""" |
499 |
0 |
logger.debug("") |
500 |
0 |
utils.check_client_version(ctx.obj, ctx.command.name) |
501 |
0 |
resp = ctx.obj.user.get(name) |
502 |
0 |
if "password" in resp: |
503 |
0 |
resp["password"] = "********" |
504 |
|
|
505 |
0 |
table = PrettyTable(["key", "attribute"]) |
506 |
0 |
for k, v in resp.items(): |
507 |
0 |
table.add_row([k, json.dumps(v, indent=2)]) |
508 |
0 |
table.align = "l" |
509 |
0 |
print(table) |