From deaf5e022f15dfe3480c4cb1739031a0c09ad9d0 Mon Sep 17 00:00:00 2001 From: Olivier DRAGHI Date: Sat, 1 Oct 2022 00:53:09 +0200 Subject: [PATCH 01/15] New feature for 'search' command. Allow to show only some fields. Signed-off-by: Olivier DRAGHI --- docs/vcd_search.md | 3 +++ vcd_cli/search.py | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/vcd_search.md b/docs/vcd_search.md index 5aa9024d..123d9203 100644 --- a/docs/vcd_search.md +++ b/docs/vcd_search.md @@ -44,6 +44,9 @@ Usage: vcd search [OPTIONS] [resource-type]  vcd search vm Search for virtual machines. +  + vcd search vm --fields 'name,vdcName,status' + Search for virtual machines and show only some fields. Options: diff --git a/vcd_cli/search.py b/vcd_cli/search.py index 1fb8f026..30131b59 100644 --- a/vcd_cli/search.py +++ b/vcd_cli/search.py @@ -36,7 +36,14 @@ required=False, metavar='[query-filter]', help='query filter') -def search(ctx, resource_type, query_filter): +@click.option( + '-t', + '--fields', + 'fields', + required=False, + metavar='[fields]', + help='fields to show') +def search(ctx, resource_type, query_filter,fields): """Search for resources in vCloud Director. \b @@ -81,6 +88,9 @@ def search(ctx, resource_type, query_filter): \b vcd search vm Search for virtual machines. +\b + vcd search vm --fields 'name,vdcName,status' + Search for virtual machines and show only some fields. """ try: @@ -96,7 +106,8 @@ def search(ctx, resource_type, query_filter): q = client.get_typed_query( resource_type_cc, query_result_format=QueryResultFormat.ID_RECORDS, - qfilter=query_filter) + qfilter=query_filter, + fields=fields) records = list(q.execute()) if len(records) == 0: result = 'not found' From 87a92637bac419e4ffe753709c44dbc0c71fed5c Mon Sep 17 00:00:00 2001 From: Olivier DRAGHI Date: Sat, 1 Oct 2022 03:02:31 +0200 Subject: [PATCH 02/15] New feature for 'search' command. Allow to hide id with '--do-not-show-id'. Allow to call _search() function from other click commands. ( useful to implement 'vm list' ) Signed-off-by: Olivier DRAGHI --- vcd_cli/search.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/vcd_cli/search.py b/vcd_cli/search.py index 30131b59..12545eea 100644 --- a/vcd_cli/search.py +++ b/vcd_cli/search.py @@ -43,7 +43,15 @@ required=False, metavar='[fields]', help='fields to show') -def search(ctx, resource_type, query_filter,fields): +@click.option( + '--show-id/--do-not-show-id', + 'show_id', + required=False, + is_flag=True, + show_default=True, + default=True, + help='show id') +def search(ctx, resource_type, query_filter, fields, show_id): """Search for resources in vCloud Director. \b @@ -92,7 +100,9 @@ def search(ctx, resource_type, query_filter,fields): vcd search vm --fields 'name,vdcName,status' Search for virtual machines and show only some fields. """ + return _search(ctx, resource_type, query_filter, fields, show_id) +def _search(ctx, resource_type, query_filter, fields, show_id): try: if resource_type is None: click.secho(ctx.get_help()) @@ -114,6 +124,6 @@ def search(ctx, resource_type, query_filter,fields): else: for r in records: result.append(to_dict(r, resource_type=resource_type_cc)) - stdout(result, ctx, show_id=True) + stdout(result, ctx, show_id=show_id) except Exception as e: stderr(e, ctx) From be9ad5c2a590ee8711166e38dc26f6bf88cf99c5 Mon Sep 17 00:00:00 2001 From: Olivier DRAGHI Date: Sun, 2 Oct 2022 11:17:09 +0200 Subject: [PATCH 03/15] Rename 'search' option --do-not-show-id to --hide-id Signed-off-by: Olivier DRAGHI --- vcd_cli/search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vcd_cli/search.py b/vcd_cli/search.py index 12545eea..76a29970 100644 --- a/vcd_cli/search.py +++ b/vcd_cli/search.py @@ -44,7 +44,7 @@ metavar='[fields]', help='fields to show') @click.option( - '--show-id/--do-not-show-id', + '--show-id/--hide-id', 'show_id', required=False, is_flag=True, From 8e7a978522af0e74bb34fa3279ff457f1123b068 Mon Sep 17 00:00:00 2001 From: Olivier DRAGHI Date: Mon, 3 Oct 2022 23:51:27 +0200 Subject: [PATCH 04/15] Add sort options in search command. Add sort-asc and sort-desc to 'vcd search'. Signed-off-by: Olivier DRAGHI --- docs/vcd_search.md | 8 +++++++- vcd_cli/search.py | 30 +++++++++++++++++++++++++----- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/docs/vcd_search.md b/docs/vcd_search.md index 123d9203..fb7280b3 100644 --- a/docs/vcd_search.md +++ b/docs/vcd_search.md @@ -7,7 +7,7 @@ Usage: vcd search [OPTIONS] [resource-type] Search for resources of the provided type. Resource type is not case sensitive. When invoked without a resource type, list the available types to search for. Admin types are only allowed when the user is - the system administrator. + the system administrator. By default result is order by id.  Filters can be applied to the search.  @@ -47,6 +47,12 @@ Usage: vcd search [OPTIONS] [resource-type]  vcd search vm --fields 'name,vdcName,status' Search for virtual machines and show only some fields. +  + vcd search vm --fields 'name,vdcName,status' --hide-id --sort-asc vdcName + Search for virtual machines and show only some fields order y vdcName. +  + vcd search adminOrgVdc --fields 'name,orgName,providerVdcName' --hide-id --sort-asc name + Search all vdc and show only some fields order y name. Options: diff --git a/vcd_cli/search.py b/vcd_cli/search.py index 76a29970..9ec5651f 100644 --- a/vcd_cli/search.py +++ b/vcd_cli/search.py @@ -51,7 +51,19 @@ show_default=True, default=True, help='show id') -def search(ctx, resource_type, query_filter, fields, show_id): +@click.option( + '--sort-asc', + 'sort_asc', + required=False, + metavar='[field]', + help='sort in ascending order on a field') +@click.option( + '--sort-desc', + 'sort_desc', + required=False, + metavar='[field]', + help='sort in descending order on a field') +def search(ctx, resource_type, query_filter, fields, show_id, sort_asc, sort_desc): """Search for resources in vCloud Director. \b @@ -59,7 +71,7 @@ def search(ctx, resource_type, query_filter, fields, show_id): Search for resources of the provided type. Resource type is not case sensitive. When invoked without a resource type, list the available types to search for. Admin types are only allowed when the user is - the system administrator. + the system administrator. By default result is order by id. \b Filters can be applied to the search. \b @@ -99,10 +111,16 @@ def search(ctx, resource_type, query_filter, fields, show_id): \b vcd search vm --fields 'name,vdcName,status' Search for virtual machines and show only some fields. +\b + vcd search vm --fields 'name,vdcName,status' --hide-id --sort-asc vdcName + Search for virtual machines and show only some fields order y vdcName. +\b + vcd search adminOrgVdc --fields 'name,orgName,providerVdcName' --hide-id --sort-asc name + Search all vdc and show only some fields order y name. """ - return _search(ctx, resource_type, query_filter, fields, show_id) + return _search(ctx, resource_type, query_filter, fields, show_id, sort_asc, sort_desc) -def _search(ctx, resource_type, query_filter, fields, show_id): +def _search(ctx, resource_type, query_filter, fields, show_id, sort_asc, sort_desc): try: if resource_type is None: click.secho(ctx.get_help()) @@ -117,7 +135,9 @@ def _search(ctx, resource_type, query_filter, fields, show_id): resource_type_cc, query_result_format=QueryResultFormat.ID_RECORDS, qfilter=query_filter, - fields=fields) + fields=fields, + sort_asc=sort_asc, + sort_desc=sort_desc) records = list(q.execute()) if len(records) == 0: result = 'not found' From a401eba0fe70f3a70b6d659a57c7cca2adcd0a48 Mon Sep 17 00:00:00 2001 From: Olivier DRAGHI Date: Wed, 5 Oct 2022 02:25:18 +0200 Subject: [PATCH 05/15] [Fix] rename function _search by query. Rename function _search by query to be proprely called by other script Signed-off-by: Olivier DRAGHI --- vcd_cli/search.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vcd_cli/search.py b/vcd_cli/search.py index 9ec5651f..b66ad3c1 100644 --- a/vcd_cli/search.py +++ b/vcd_cli/search.py @@ -118,9 +118,9 @@ def search(ctx, resource_type, query_filter, fields, show_id, sort_asc, sort_des vcd search adminOrgVdc --fields 'name,orgName,providerVdcName' --hide-id --sort-asc name Search all vdc and show only some fields order y name. """ - return _search(ctx, resource_type, query_filter, fields, show_id, sort_asc, sort_desc) + return query(ctx, resource_type, query_filter, fields, show_id, sort_asc, sort_desc) -def _search(ctx, resource_type, query_filter, fields, show_id, sort_asc, sort_desc): +def query(ctx, resource_type=None, query_filter=None, fields=None, show_id=True, sort_asc=None, sort_desc=None): try: if resource_type is None: click.secho(ctx.get_help()) From 1a477be035444672073ea19f1e6d0bbac3478061 Mon Sep 17 00:00:00 2001 From: Olivier DRAGHI Date: Thu, 6 Oct 2022 21:58:33 +0200 Subject: [PATCH 06/15] Allow to customize field name in search In --fields option, you can now specify custom name with 'as'. exemple: vcd search vm --fields 'name,ownerName as owner,isAutoNature as standalone' name owner standalone ------------ ------- ------------ web1 johndoe flase web2 johndoe flase bdd johndoe true Signed-off-by: Olivier DRAGHI --- docs/vcd_search.md | 5 ++++- vcd_cli/search.py | 23 ++++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/docs/vcd_search.md b/docs/vcd_search.md index fb7280b3..06490e68 100644 --- a/docs/vcd_search.md +++ b/docs/vcd_search.md @@ -53,7 +53,10 @@ Usage: vcd search [OPTIONS] [resource-type]  vcd search adminOrgVdc --fields 'name,orgName,providerVdcName' --hide-id --sort-asc name Search all vdc and show only some fields order y name. - +  + vcd search vm --fields 'containerName as containerName(vapp),name,ownerName as owner,isAutoNature as standalone' \ + --sort-asc containerName --filter 'isVAppTemplate==false' --hide-id + Search for virtual machines, show only some fields, use 'as' to customize field name Options: -f, --filter [query-filter] query filter diff --git a/vcd_cli/search.py b/vcd_cli/search.py index b66ad3c1..1107ba22 100644 --- a/vcd_cli/search.py +++ b/vcd_cli/search.py @@ -117,9 +117,14 @@ def search(ctx, resource_type, query_filter, fields, show_id, sort_asc, sort_des \b vcd search adminOrgVdc --fields 'name,orgName,providerVdcName' --hide-id --sort-asc name Search all vdc and show only some fields order y name. +\b + vcd search vm --fields 'containerName as containerName(vapp),name,ownerName as owner,isAutoNature as standalone' \\ + --sort-asc containerName --filter 'isVAppTemplate==false' --hide-id + Search for virtual machines, show only some fields, use 'as' to customize field name. """ return query(ctx, resource_type, query_filter, fields, show_id, sort_asc, sort_desc) + def query(ctx, resource_type=None, query_filter=None, fields=None, show_id=True, sort_asc=None, sort_desc=None): try: if resource_type is None: @@ -131,6 +136,17 @@ def query(ctx, resource_type=None, query_filter=None, fields=None, show_id=True, client = ctx.obj['client'] result = [] resource_type_cc = to_camel_case(resource_type, RESOURCE_TYPES) + custom_fields = {} + if fields: + keys = [] + for f in fields.split(','): + key, *label = f.split(' as ') + keys.append(key) + if label: + label = label.pop() + if key != label: + custom_fields[key] = label + fields = ','.join(keys) q = client.get_typed_query( resource_type_cc, query_result_format=QueryResultFormat.ID_RECORDS, @@ -143,7 +159,12 @@ def query(ctx, resource_type=None, query_filter=None, fields=None, show_id=True, result = 'not found' else: for r in records: - result.append(to_dict(r, resource_type=resource_type_cc)) + d = to_dict(r, resource_type=resource_type_cc) + for field, label in custom_fields.items(): + if field in d: + d[label] = d[field] + d.pop(field) + result.append(d) stdout(result, ctx, show_id=show_id) except Exception as e: stderr(e, ctx) From 6f1eb0b11b91ad76b626d76b2533c9343dcedcc7 Mon Sep 17 00:00:00 2001 From: Olivier DRAGHI Date: Sat, 8 Oct 2022 00:00:12 +0200 Subject: [PATCH 07/15] [Fix] utils as_table when sort_headers=False AttributeError: 'dict_keys' object has no attribute 'remove' When sort_headers=False headers need to be a list to call remove() Signed-off-by: Olivier DRAGHI --- vcd_cli/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vcd_cli/utils.py b/vcd_cli/utils.py index 95f93f83..ab9b6f60 100644 --- a/vcd_cli/utils.py +++ b/vcd_cli/utils.py @@ -59,7 +59,7 @@ def as_table(obj_list, if sort_headers: headers = sorted(obj_list[0].keys()) else: - headers = obj_list[0].keys() + headers = list(obj_list[0].keys()) if not show_id and 'id' in headers: headers.remove('id') for field in hide_fields: From 26067a3cfbb81a41be8a2f1a57b33648f2181016 Mon Sep 17 00:00:00 2001 From: Olivier DRAGHI Date: Sat, 8 Oct 2022 03:38:33 +0200 Subject: [PATCH 08/15] Search - respect the fields declaration order Signed-off-by: Olivier DRAGHI --- vcd_cli/search.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/vcd_cli/search.py b/vcd_cli/search.py index 1107ba22..dc79b37c 100644 --- a/vcd_cli/search.py +++ b/vcd_cli/search.py @@ -136,17 +136,15 @@ def query(ctx, resource_type=None, query_filter=None, fields=None, show_id=True, client = ctx.obj['client'] result = [] resource_type_cc = to_camel_case(resource_type, RESOURCE_TYPES) - custom_fields = {} + headers={} if fields: - keys = [] for f in fields.split(','): - key, *label = f.split(' as ') - keys.append(key) + field, *label = f.split(' as ') + headers[field] = field if label: label = label.pop() - if key != label: - custom_fields[key] = label - fields = ','.join(keys) + headers[field] = label + fields = ','.join( headers.keys() ) q = client.get_typed_query( resource_type_cc, query_result_format=QueryResultFormat.ID_RECORDS, @@ -160,11 +158,12 @@ def query(ctx, resource_type=None, query_filter=None, fields=None, show_id=True, else: for r in records: d = to_dict(r, resource_type=resource_type_cc) - for field, label in custom_fields.items(): - if field in d: - d[label] = d[field] - d.pop(field) + if headers: + d_with_custom_header = { 'id': d.pop('id') } + for field, label in headers.items(): + d_with_custom_header[label] = d.pop(field) + d = d_with_custom_header result.append(d) - stdout(result, ctx, show_id=show_id) + stdout(result, ctx, show_id=show_id, sort_headers=False) except Exception as e: stderr(e, ctx) From f5c007992e191e36168e27f29071922adf19bcfe Mon Sep 17 00:00:00 2001 From: Olivier DRAGHI Date: Sun, 9 Oct 2022 17:03:28 +0200 Subject: [PATCH 09/15] Search - prevent error if a field has no value Init field with a default of None. For exemple query api 'vm' doesn't return ipAddress if the vm do not have newtork interface. Signed-off-by: Olivier DRAGHI --- vcd_cli/search.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vcd_cli/search.py b/vcd_cli/search.py index dc79b37c..e638b6ea 100644 --- a/vcd_cli/search.py +++ b/vcd_cli/search.py @@ -161,7 +161,9 @@ def query(ctx, resource_type=None, query_filter=None, fields=None, show_id=True, if headers: d_with_custom_header = { 'id': d.pop('id') } for field, label in headers.items(): - d_with_custom_header[label] = d.pop(field) + d_with_custom_header[label] = None + if field in d: + d_with_custom_header[label] = d.pop(field) d = d_with_custom_header result.append(d) stdout(result, ctx, show_id=show_id, sort_headers=False) From ab00c7a777c5cff0fd81e1d11caec91d5972bcfd Mon Sep 17 00:00:00 2001 From: Olivier DRAGHI Date: Sun, 9 Oct 2022 23:46:05 +0200 Subject: [PATCH 10/15] Search - sort on a 2nd field Add option --sort-next to add sort on a second field. This option must be used with --sort-asc or --sort-desc Signed-off-by: Olivier DRAGHI --- vcd_cli/search.py | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/vcd_cli/search.py b/vcd_cli/search.py index e638b6ea..a1050fcb 100644 --- a/vcd_cli/search.py +++ b/vcd_cli/search.py @@ -63,7 +63,13 @@ required=False, metavar='[field]', help='sort in descending order on a field') -def search(ctx, resource_type, query_filter, fields, show_id, sort_asc, sort_desc): +@click.option( + '--sort-next', + 'sort_next', + required=False, + metavar='[field]', + help='--sort_asc or --sort-desc on a second field') +def search(ctx, resource_type, query_filter, fields, show_id, sort_asc, sort_desc, sort_next): """Search for resources in vCloud Director. \b @@ -121,11 +127,14 @@ def search(ctx, resource_type, query_filter, fields, show_id, sort_asc, sort_des vcd search vm --fields 'containerName as containerName(vapp),name,ownerName as owner,isAutoNature as standalone' \\ --sort-asc containerName --filter 'isVAppTemplate==false' --hide-id Search for virtual machines, show only some fields, use 'as' to customize field name. +\b + vcd search vm --fields 'containerName as vapp,name' --sort-asc containerName --sort-next name --hide-id + Search for virtual machines and show only some fields. """ - return query(ctx, resource_type, query_filter, fields, show_id, sort_asc, sort_desc) + return query(ctx, resource_type, query_filter, fields, show_id, sort_asc, sort_desc, sort_next) -def query(ctx, resource_type=None, query_filter=None, fields=None, show_id=True, sort_asc=None, sort_desc=None): +def query(ctx, resource_type=None, query_filter=None, fields=None, show_id=True, sort_asc=None, sort_desc=None, sort_next=None): try: if resource_type is None: click.secho(ctx.get_help()) @@ -144,7 +153,7 @@ def query(ctx, resource_type=None, query_filter=None, fields=None, show_id=True, if label: label = label.pop() headers[field] = label - fields = ','.join( headers.keys() ) + fields = ','.join(headers.keys()) q = client.get_typed_query( resource_type_cc, query_result_format=QueryResultFormat.ID_RECORDS, @@ -166,6 +175,26 @@ def query(ctx, resource_type=None, query_filter=None, fields=None, show_id=True, d_with_custom_header[label] = d.pop(field) d = d_with_custom_header result.append(d) + if sort_next and (sort_asc or sort_desc): + if sort_asc: + reverse=False + sort_key1 = sort_asc + if sort_desc: + reverse=True + sort_key1 = sort_desc + sort_key2=sort_next + if sort_key1 in headers: + sort_key1 = headers[sort_key1] + if sort_key2 in headers: + sort_key2 = headers[sort_key2] + keys = list(result[0].keys()) + if sort_key1 not in keys: + raise Exception('sort key \'%s\' not in %s' % (sort_key1, keys)) + if sort_key2 not in keys: + raise Exception('sort_next \'%s\' not in %s' % (sort_key2, keys)) + result=sorted(result, key=lambda d: (d[sort_key1], d[sort_key2]), reverse=reverse) + elif sort_next: + raise Exception('sort_next must be used with sort_asc or sort_desc') stdout(result, ctx, show_id=show_id, sort_headers=False) except Exception as e: stderr(e, ctx) From 81485c135e83115b47614293f8b25c724f71bcdc Mon Sep 17 00:00:00 2001 From: Olivier DRAGHI Date: Sun, 9 Oct 2022 23:53:03 +0200 Subject: [PATCH 11/15] Search - Change query function return Change in query(): - Remove param show_id - Don't show the result but return it to the caller This add flexibility to the caller. Signed-off-by: Olivier DRAGHI --- vcd_cli/search.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vcd_cli/search.py b/vcd_cli/search.py index a1050fcb..a77f45ff 100644 --- a/vcd_cli/search.py +++ b/vcd_cli/search.py @@ -131,10 +131,11 @@ def search(ctx, resource_type, query_filter, fields, show_id, sort_asc, sort_des vcd search vm --fields 'containerName as vapp,name' --sort-asc containerName --sort-next name --hide-id Search for virtual machines and show only some fields. """ - return query(ctx, resource_type, query_filter, fields, show_id, sort_asc, sort_desc, sort_next) + result = query(ctx, resource_type, query_filter, fields, sort_asc, sort_desc, sort_next) + stdout(result, ctx, show_id=show_id, sort_headers=False) -def query(ctx, resource_type=None, query_filter=None, fields=None, show_id=True, sort_asc=None, sort_desc=None, sort_next=None): +def query(ctx, resource_type=None, query_filter=None, fields=None, sort_asc=None, sort_desc=None, sort_next=None): try: if resource_type is None: click.secho(ctx.get_help()) @@ -195,6 +196,6 @@ def query(ctx, resource_type=None, query_filter=None, fields=None, show_id=True, result=sorted(result, key=lambda d: (d[sort_key1], d[sort_key2]), reverse=reverse) elif sort_next: raise Exception('sort_next must be used with sort_asc or sort_desc') - stdout(result, ctx, show_id=show_id, sort_headers=False) + return result except Exception as e: stderr(e, ctx) From 63c87558765ca7b29d9d12c990dd239c754abe67 Mon Sep 17 00:00:00 2001 From: Olivier DRAGHI Date: Wed, 12 Oct 2022 22:40:06 +0200 Subject: [PATCH 12/15] [Fix] Search launched without resource_type Signed-off-by: Olivier DRAGHI --- vcd_cli/search.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/vcd_cli/search.py b/vcd_cli/search.py index a77f45ff..820b2faf 100644 --- a/vcd_cli/search.py +++ b/vcd_cli/search.py @@ -131,17 +131,21 @@ def search(ctx, resource_type, query_filter, fields, show_id, sort_asc, sort_des vcd search vm --fields 'containerName as vapp,name' --sort-asc containerName --sort-next name --hide-id Search for virtual machines and show only some fields. """ - result = query(ctx, resource_type, query_filter, fields, sort_asc, sort_desc, sort_next) - stdout(result, ctx, show_id=show_id, sort_headers=False) - - -def query(ctx, resource_type=None, query_filter=None, fields=None, sort_asc=None, sort_desc=None, sort_next=None): try: if resource_type is None: click.secho(ctx.get_help()) click.echo('\nAvailable resource types:') click.echo(tabulate(tabulate_names(RESOURCE_TYPES, 4))) return + result = query(ctx, resource_type, query_filter, fields, sort_asc, sort_desc, sort_next) + stdout(result, ctx, show_id=show_id, sort_headers=False) + except Exception as e: + stderr(e, ctx) + +def query(ctx, resource_type=None, query_filter=None, fields=None, sort_asc=None, sort_desc=None, sort_next=None): + try: + if resource_type is None: + raise Exception('resource_type can\'t be None') restore_session(ctx) client = ctx.obj['client'] result = [] From c36727d005c382f4a3ad08534e55389fbd2e69cd Mon Sep 17 00:00:00 2001 From: Olivier DRAGHI Date: Wed, 12 Oct 2022 23:32:13 +0200 Subject: [PATCH 13/15] [Fix] Search when result is no found Signed-off-by: Olivier DRAGHI --- vcd_cli/search.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/vcd_cli/search.py b/vcd_cli/search.py index 820b2faf..a98c8cb8 100644 --- a/vcd_cli/search.py +++ b/vcd_cli/search.py @@ -180,26 +180,26 @@ def query(ctx, resource_type=None, query_filter=None, fields=None, sort_asc=None d_with_custom_header[label] = d.pop(field) d = d_with_custom_header result.append(d) - if sort_next and (sort_asc or sort_desc): - if sort_asc: - reverse=False - sort_key1 = sort_asc - if sort_desc: - reverse=True - sort_key1 = sort_desc - sort_key2=sort_next - if sort_key1 in headers: - sort_key1 = headers[sort_key1] - if sort_key2 in headers: - sort_key2 = headers[sort_key2] - keys = list(result[0].keys()) - if sort_key1 not in keys: - raise Exception('sort key \'%s\' not in %s' % (sort_key1, keys)) - if sort_key2 not in keys: - raise Exception('sort_next \'%s\' not in %s' % (sort_key2, keys)) - result=sorted(result, key=lambda d: (d[sort_key1], d[sort_key2]), reverse=reverse) - elif sort_next: - raise Exception('sort_next must be used with sort_asc or sort_desc') + if sort_next and (sort_asc or sort_desc): + if sort_asc: + reverse=False + sort_key1 = sort_asc + if sort_desc: + reverse=True + sort_key1 = sort_desc + sort_key2=sort_next + if sort_key1 in headers: + sort_key1 = headers[sort_key1] + if sort_key2 in headers: + sort_key2 = headers[sort_key2] + keys = list(result[0].keys()) + if sort_key1 not in keys: + raise Exception('sort key \'%s\' not in %s' % (sort_key1, keys)) + if sort_key2 not in keys: + raise Exception('sort_next \'%s\' not in %s' % (sort_key2, keys)) + result=sorted(result, key=lambda d: (d[sort_key1], d[sort_key2]), reverse=reverse) + elif sort_next: + raise Exception('sort_next must be used with sort_asc or sort_desc') return result except Exception as e: stderr(e, ctx) From ee2c561c234939ac4a0c47a9e48ecb2482759c5c Mon Sep 17 00:00:00 2001 From: Olivier DRAGHI Date: Wed, 12 Oct 2022 23:55:26 +0200 Subject: [PATCH 14/15] [Fix] Search, relocate is not found check out of query function Signed-off-by: Olivier DRAGHI --- vcd_cli/search.py | 65 ++++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/vcd_cli/search.py b/vcd_cli/search.py index a98c8cb8..6b3f5508 100644 --- a/vcd_cli/search.py +++ b/vcd_cli/search.py @@ -138,6 +138,8 @@ def search(ctx, resource_type, query_filter, fields, show_id, sort_asc, sort_des click.echo(tabulate(tabulate_names(RESOURCE_TYPES, 4))) return result = query(ctx, resource_type, query_filter, fields, sort_asc, sort_desc, sort_next) + if not result: + result = 'not found' stdout(result, ctx, show_id=show_id, sort_headers=False) except Exception as e: stderr(e, ctx) @@ -168,38 +170,37 @@ def query(ctx, resource_type=None, query_filter=None, fields=None, sort_asc=None sort_desc=sort_desc) records = list(q.execute()) if len(records) == 0: - result = 'not found' - else: - for r in records: - d = to_dict(r, resource_type=resource_type_cc) - if headers: - d_with_custom_header = { 'id': d.pop('id') } - for field, label in headers.items(): - d_with_custom_header[label] = None - if field in d: - d_with_custom_header[label] = d.pop(field) - d = d_with_custom_header - result.append(d) - if sort_next and (sort_asc or sort_desc): - if sort_asc: - reverse=False - sort_key1 = sort_asc - if sort_desc: - reverse=True - sort_key1 = sort_desc - sort_key2=sort_next - if sort_key1 in headers: - sort_key1 = headers[sort_key1] - if sort_key2 in headers: - sort_key2 = headers[sort_key2] - keys = list(result[0].keys()) - if sort_key1 not in keys: - raise Exception('sort key \'%s\' not in %s' % (sort_key1, keys)) - if sort_key2 not in keys: - raise Exception('sort_next \'%s\' not in %s' % (sort_key2, keys)) - result=sorted(result, key=lambda d: (d[sort_key1], d[sort_key2]), reverse=reverse) - elif sort_next: - raise Exception('sort_next must be used with sort_asc or sort_desc') + return [] + for r in records: + d = to_dict(r, resource_type=resource_type_cc) + if headers: + d_with_custom_header = { 'id': d.pop('id') } + for field, label in headers.items(): + d_with_custom_header[label] = None + if field in d: + d_with_custom_header[label] = d.pop(field) + d = d_with_custom_header + result.append(d) + if sort_next and (sort_asc or sort_desc): + if sort_asc: + reverse=False + sort_key1 = sort_asc + if sort_desc: + reverse=True + sort_key1 = sort_desc + sort_key2=sort_next + if sort_key1 in headers: + sort_key1 = headers[sort_key1] + if sort_key2 in headers: + sort_key2 = headers[sort_key2] + keys = list(result[0].keys()) + if sort_key1 not in keys: + raise Exception('sort key \'%s\' not in %s' % (sort_key1, keys)) + if sort_key2 not in keys: + raise Exception('sort_next \'%s\' not in %s' % (sort_key2, keys)) + result=sorted(result, key=lambda d: (d[sort_key1], d[sort_key2]), reverse=reverse) + elif sort_next: + raise Exception('sort_next must be used with sort_asc or sort_desc') return result except Exception as e: stderr(e, ctx) From 09c311d5fc1a16ac01171d891bd852128c190027 Mon Sep 17 00:00:00 2001 From: Olivier DRAGHI Date: Fri, 14 Oct 2022 02:26:43 +0200 Subject: [PATCH 15/15] Search - Unit tests Signed-off-by: Olivier DRAGHI --- system_tests/search_tests.py | 244 +++++++++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 system_tests/search_tests.py diff --git a/system_tests/search_tests.py b/system_tests/search_tests.py new file mode 100644 index 00000000..093bf470 --- /dev/null +++ b/system_tests/search_tests.py @@ -0,0 +1,244 @@ +# VMware vCloud Director vCD CLI +# Copyright (c) 2018 VMware, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from click.testing import CliRunner + +import os +import yaml + +from pyvcloud.system_test_framework.base_test import BaseTestCase +from pyvcloud.system_test_framework.environment import Environment +from vcd_cli.login import login, logout +from vcd_cli.search import search + + +class SearchTest(BaseTestCase): + """Test search-related commands + + Tests cases in this module do not have ordering dependencies, + so setup is accomplished using Python unittest setUp and tearDown + methods. + + Be aware that this test will delete existing vcd-cli sessions. + """ + + _sys_admin_id = None + + @classmethod + def setUpClass(cls): + if 'VCD_TEST_BASE_CONFIG_FILE' in os.environ: + cls._config_file = os.environ['VCD_TEST_BASE_CONFIG_FILE'] + with open(cls._config_file, 'r') as f: + cls._config_yaml = yaml.safe_load(f) + + Environment.init(cls._config_yaml) + # We Don't need to setup further our Cloud Director so we skip attach vc, create pvcd ... + + @classmethod + def tearDownClass(cls): + Environment.cleanup() + + def setUp(self): + """Load configuration , get sys_admin ID and create a click runner to invoke CLI.""" + self._config = Environment.get_config() + self._logger = Environment.get_default_logger() + + client = Environment.get_sys_admin_client() + org = client.get_org() + org_href = org.get('href') + admin_user = self._config['vcd']['sys_admin_username'] + user = client.get_user_in_org(admin_user, org_href) + urn = user.get('id') + self._sys_admin_id = urn.split(":").pop() + self._logger.debug("sys_admin id: {0}".format(self._sys_admin_id)) + client.logout() + + self._runner = CliRunner() + self._login() + + def tearDown(self): + """Logout ignoring any errors to ensure test session is gone.""" + self._logout() + + def _login(self): + """Logs in using admin credentials""" + host = self._config['vcd']['host'] + org = self._config['vcd']['sys_org_name'] + admin_user = self._config['vcd']['sys_admin_username'] + admin_pass = self._config['vcd']['sys_admin_pass'] + login_args = [ + host, org, admin_user, "-i", "-w", + "--password={0}".format(admin_pass) + ] + result = self._runner.invoke(login, args=login_args) + self.assertEqual(0, result.exit_code) + self.assertTrue("logged in" in result.output) + + def _logout(self): + """Logs out current session, ignoring errors""" + self._runner.invoke(logout) + + def test_0010_search_without_arg(self): + """Search command is going to output help and valid resources type + """ + result = self._runner.invoke(search) + self._logger.debug("vcd search: {0}".format(result.output)) + self.assertEqual(0, result.exit_code) + self.assertTrue("user" in result.output) + self.assertTrue("cell" in result.output) + self.assertTrue("virtualCenter" in result.output) + self.assertTrue("organization" in result.output) + self.assertTrue("adminOrgVdc" in result.output) + self.assertTrue("vApp" in result.output) + self.assertTrue("adminVM" in result.output) + + def test_0020_search_valid_resource_type(self): + """Search a valid resource (ex: user) + """ + result = self._runner.invoke(search, args=['user']) + self._logger.debug("vcd search user: {0}".format(result.output)) + self.assertEqual(0, result.exit_code) + self.assertTrue("name" in result.output) + self.assertTrue("fullName" in result.output) + self.assertTrue("numberOfDeployedVMs" in result.output) + + def test_0030_search_with_filter(self): + """Search with a filter (in 'user' ressources we are going to find our user) + """ + admin_user = self._config['vcd']['sys_admin_username'] + filter = 'name==%s' % (admin_user) + result = self._runner.invoke(search, args=['user', '--filter', filter]) + self._logger.debug( + "vcd search user --filter {1}: {0}".format(result.output, filter)) + self.assertEqual(0, result.exit_code) + self.assertTrue(self._sys_admin_id in result.output) + self.assertTrue(admin_user in result.output) + self.assertTrue("name" in result.output) + self.assertTrue("fullName" in result.output) + self.assertTrue("numberOfDeployedVMs" in result.output) + + def test_0031_search_with_filter_and_no_match(self): + """Search with a filter on an unexpected user + """ + unexpected_user = 'xxxClark_kent_is_not_zorro_but_he_is_an_hero_xxx' + filter = 'name==%s' % (unexpected_user) + result = self._runner.invoke(search, args=['user', '--filter', filter]) + self._logger.debug( + "vcd search user --filter {1}: {0}".format(result.output, filter)) + self.assertEqual(0, result.exit_code) + self.assertTrue("not found" in result.output) + + def test_0040_search_with_fields(self): + """Search with fields: name, fullName and isEnable + """ + admin_user = self._config['vcd']['sys_admin_username'] + fields = 'name,fullName,isEnabled' + result = self._runner.invoke(search, args=['user', '--fields', fields]) + self._logger.debug( + "vcd search user --fields {1}: {0}".format(result.output, fields)) + self.assertEqual(0, result.exit_code) + self.assertTrue(self._sys_admin_id in result.output) + self.assertTrue(admin_user in result.output) + self.assertTrue("name" in result.output) + self.assertTrue("fullName" in result.output) + self.assertTrue("isEnabled" in result.output) + self.assertFalse("numberOfDeployedVMs" in result.output) + + def test_0041_search_with_fields_as_label(self): + """Search with fields: name as username, fullName as 'the full name' and isEnable as is-enable + """ + admin_user = self._config['vcd']['sys_admin_username'] + fields = 'name as username,fullName as the full name,isEnabled as is-enabled' + result = self._runner.invoke(search, args=['user', '--fields', fields]) + self._logger.debug( + "vcd search user --fields {1}: {0}".format(result.output, fields)) + self.assertEqual(0, result.exit_code) + self.assertTrue(self._sys_admin_id in result.output) + self.assertTrue(admin_user in result.output) + self.assertTrue("username" in result.output) + self.assertTrue("the full name" in result.output) + self.assertTrue("is-enabled" in result.output) + self.assertFalse("isEnabled" in result.output) + self.assertFalse("fullName" in result.output) + self.assertFalse("numberOfDeployedVMs" in result.output) + + def test_0050_search_and_hide_id(self): + """Search and hide id. The session user id should not be in the result. + """ + admin_user = self._config['vcd']['sys_admin_username'] + result = self._runner.invoke(search, args=['user', '--hide-id']) + self._logger.debug( + "vcd search user --hide-id: {0}".format(result.output)) + self.assertEqual(0, result.exit_code) + self.assertFalse(self._sys_admin_id in result.output) + self.assertTrue("name" in result.output) + self.assertTrue("fullName" in result.output) + self.assertTrue(admin_user in result.output) + + def test_0060_search_sort_asc(self): + """Search and sort asc on isEnabled. + """ + admin_user = self._config['vcd']['sys_admin_username'] + result = self._runner.invoke( + search, args=['user', '--sort-asc', 'isEnabled']) + self._logger.debug( + "vcd search user --sort-asc isEnabled: {0}".format(result.output)) + self.assertEqual(0, result.exit_code) + self.assertTrue(self._sys_admin_id in result.output) + self.assertTrue("name" in result.output) + self.assertTrue("fullName" in result.output) + self.assertTrue(admin_user in result.output) + + def test_0061_search_sort_asc_on_two_fields(self): + """Search and sort asc on isEnabled and then on fullName. + """ + admin_user = self._config['vcd']['sys_admin_username'] + result = self._runner.invoke( + search, args=['user', '--sort-asc', 'isEnabled', '--sort-next', 'fullName']) + self._logger.debug( + "vcd search user --sort-asc isEnabled: {0}".format(result.output)) + self.assertEqual(0, result.exit_code) + self.assertTrue(self._sys_admin_id in result.output) + self.assertTrue("name" in result.output) + self.assertTrue("fullName" in result.output) + self.assertTrue(admin_user in result.output) + + def test_0070_search_sort_desc(self): + """Search and sort desc on isEnabled. + """ + admin_user = self._config['vcd']['sys_admin_username'] + result = self._runner.invoke( + search, args=['user', '--sort-desc', 'isEnabled']) + self._logger.debug( + "vcd search user --sort-desc isEnabled: {0}".format(result.output)) + self.assertEqual(0, result.exit_code) + self.assertTrue(self._sys_admin_id in result.output) + self.assertTrue("name" in result.output) + self.assertTrue("fullName" in result.output) + self.assertTrue(admin_user in result.output) + + def test_0071_search_sort_desc_on_two_fields(self): + """Search and sort desc on isEnabled and then on fullName. + """ + admin_user = self._config['vcd']['sys_admin_username'] + result = self._runner.invoke( + search, args=['user', '--sort-desc', 'isEnabled', '--sort-next', 'fullName']) + self._logger.debug( + "vcd search user --sort-desc isEnabled: {0}".format(result.output)) + self.assertEqual(0, result.exit_code) + self.assertTrue(self._sys_admin_id in result.output) + self.assertTrue("name" in result.output) + self.assertTrue("fullName" in result.output) + self.assertTrue(admin_user in result.output)