From de6caaaebcef8b26bdc5ca7970b51820dfe7eb35 Mon Sep 17 00:00:00 2001 From: Martin Hradil Date: Sat, 21 May 2022 21:51:24 +0000 Subject: [PATCH 1/5] command: create subparsers from data --- galaxykit/command.py | 364 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 351 insertions(+), 13 deletions(-) diff --git a/galaxykit/command.py b/galaxykit/command.py index c2abb35..aa3474c 100644 --- a/galaxykit/command.py +++ b/galaxykit/command.py @@ -11,6 +11,7 @@ from . import users from . import container_images from . import registries +from . import __version__ as VERSION EXIT_OK = 0 EXIT_UNKNOWN_ERROR = 1 @@ -43,37 +44,374 @@ def report_error(resp): ) -def main(): - parser = argparse.ArgumentParser() +# galaxykit [] (args) +KIND_OPS = { + "collection": { + "help": "Collection", + "ops": { + "list": { + "help": "List all collections", + "args": None, + }, + "upload": { + "args": { + "namespace": {"nargs": "?"}, + "collection_name": {"nargs": "?"}, + "version": {"nargs": "?"}, + }, + }, + "move": { + "args": { + "namespace": {}, + "collection_name": {}, + "version": {"nargs": "?"}, + "source": {"nargs": "?"}, + "destination": {"nargs": "?"}, + }, + }, + "delete": { + "args": { + "namespace": {}, + "collection": {}, + "version": {"nargs": "?"}, + "repository": {"nargs": "?"}, + }, + }, + "download": None, + "info": { + "args": { + "repository": {"nargs": "?"}, + "namespace": {}, + "collection_name": {}, + "version": {}, + }, + }, + "sign": { + "args": { + "repository": {"nargs": "?"}, + "namespace": {}, + "collection_name": {}, + "version": {}, + }, + }, + }, + }, + "namespace": { + "help": "Collection Namespace", + "ops": { + "get": { + "args": { + "name": {}, + } + }, + "list": {"args": None}, + "list-collections": { + "args": { + "name": {}, + } + }, + "create": { + "args": { + "name": {}, + "group": {"nargs": "?"}, + } + }, + "delete": { + "args": { + "name": {}, + } + }, + "groups": None, + "addgroup": { + "args": { + "name": {}, + "group": {}, + } + }, + "removegroup": { + "args": { + "name": {}, + "group": {}, + } + }, + "addgroupperm": None, + "removegroupperm": None, + "sign": None, + }, + }, + "container": { + "help": "Execution Environment", + "ops": { + "readme": { + "args": { + "container": {}, + "readme": {"nargs": "?"}, + } + }, + "delete": { + "args": { + "name": {}, + } + }, + "create": { + "args": { + "name": {}, + "upstream_name": {}, + "registry": {}, + } + }, + }, + }, + "container-image": { + "help": "Execution Environment image", + "ops": { + "delete": { + "args": { + "container": {}, + "image": {}, + } + }, + }, + }, + "registry": { + "help": "Remote Registry", + "ops": { + "delete": { + "args": { + "name": {}, + } + }, + "create": { + "args": { + "name": {}, + "url": {}, + } + }, + }, + }, + "user": { + "help": "RBAC User", + "ops": { + "list": {"args": None}, + "create": { + "args": { + "username": {}, + "password": {}, + } + }, + "delete": { + "args": { + "username": {}, + } + }, + "group": { + "subops": { + "add": { + "args": { + "username": {}, + "groupname": {}, + } + }, + "remove": { + "args": { + "username": {}, + "groupname": {}, + } + }, + }, + }, + }, + }, + "group": { + "help": "RBAC Group", + "ops": { + "list": {"args": None}, + "create": { + "args": { + "name": {}, + } + }, + "delete": { + "args": { + "name": {}, + } + }, + "perm": { + "subops": { + "list": { + "args": { + "groupname": {}, + } + }, + "add": { + "args": { + "groupname": {}, + "perm": {}, + } + }, + "remove": { + "args": { + "groupname": {}, + "perm": {}, + } + }, + }, + }, + }, + }, + "url": { + "help": "Generic GET/POST", + "ops": { + "get": { + "args": { + "url": {}, + } + }, + "post": { + "args": { + "url": {}, + } + }, + }, + }, +} + + +def parse_args(parser, args): + for arg in args: + parser.add_argument(arg, **(args[arg])) + + +def parse_subop(subparsers, subop, subop_params): + parser = subparsers.add_parser(subop, help=subop_params.get("help")) + args = subop_params.get("args") + + if args: + parse_args(parser, args) + + +def parse_subops(parser, subops): + subparsers = parser.add_subparsers(dest="subop", required=True) + + for subop in subops: + subop_params = subops[subop] + parse_subop(subparsers, subop, subop_params) + + +def parse_op(subparsers, op, op_params): + if op_params == None: + help, args, subops = "Not implemented", None, None + else: + help, args, subops = ( + op_params.get("help"), + op_params.get("args"), + op_params.get("subops"), + ) + + parser = subparsers.add_parser(op, help=help) + + if subops: + parse_subops(parser, subops) + + if args: + parse_args(parser, args) + + +def parse_ops(parser, ops): + subparsers = parser.add_subparsers( + dest="operation", help="Operation", required=True + ) + + for op in ops: + op_params = ops[op] + parse_op(subparsers, op, op_params) + + +def parse_kind(subparsers, kind, kind_params): + parser = subparsers.add_parser(kind, help=kind_params.get("help")) + parse_ops(parser, kind_params["ops"]) + + +def parse_kinds(parser): + subparsers = parser.add_subparsers( + dest="kind", + help="Kind of API content to operate against", + required=True, + ) + + for kind in KIND_OPS: + kind_params = KIND_OPS[kind] + parse_kind(subparsers, kind, kind_params) + + +def params_main(parser): + parser.add_argument( + "-i", + "--ignore", + action="store_true", + default=False, + ) parser.add_argument( - "kind", + "-u", + "--username", + action="store", + default="admin", type=str, + ) + parser.add_argument( + "-p", + "--password", action="store", - help="Kind of API content to operate against (user, group, namespace)", + default="admin", + type=str, + ) + parser.add_argument( + "-t", + "--token", + action="store", + type=str, + ) + parser.add_argument( + "-a", + "--auth-url", + action="store", + type=str, ) - parser.add_argument("operation", type=str, action="store") - parser.add_argument("rest", type=str, action="store", nargs="*") - parser.add_argument("-i", "--ignore", default=False, action="store_true") - parser.add_argument("-u", "--username", type=str, action="store") - parser.add_argument("-p", "--password", type=str, action="store") - parser.add_argument("-t", "--token", type=str, action="store") - parser.add_argument("-a", "--auth-url", type=str, action="store") parser.add_argument( "-c", "--ignore-certs", - default=False, action="store_true", + default=False, help="Ignore invalid SSL certificates", ) parser.add_argument( "-s", "--server", - type=str, action="store", default="http://localhost:8002/api/automation-hub/", + type=str, + ) + parser.add_argument( + "--debug", + action="store_true", + default=False, ) + parser.add_argument( + "--version", + action="version", + version=("%(prog)s " + VERSION), + ) + + +def main(): + parser = argparse.ArgumentParser(prog="galaxykit") + params_main(parser) + parse_kinds(parser) args = parser.parse_args() + if args.debug: + pprint(args) + https_verify = not args.ignore_certs if args.auth_url and not args.token: From bd06c9ef7bec1a543522cb0b6c842689a81da130 Mon Sep 17 00:00:00 2001 From: Martin Hradil Date: Sun, 22 May 2022 02:04:24 +0000 Subject: [PATCH 2/5] command.py: replace args.rest with actual parsed args --- galaxykit/command.py | 195 +++++++++++++++---------------------------- 1 file changed, 68 insertions(+), 127 deletions(-) diff --git a/galaxykit/command.py b/galaxykit/command.py index aa3474c..cac1ab2 100644 --- a/galaxykit/command.py +++ b/galaxykit/command.py @@ -19,11 +19,6 @@ EXIT_DUPLICATE = 4 -def print_unknown_error(args): - print(f"Unknown {args.kind} operation '{args.operation}'") - sys.exit(EXIT_UNKNOWN_ERROR) - - def format_list(data, identifier): buffer = [] for datum in data: @@ -441,7 +436,7 @@ def main(): resp = users.get_user_list(client) print(format_list(resp["data"], "username")) elif args.operation == "create": - username, password = args.rest + username, password = args.username, args.password created, resp = users.get_or_create_user( client, username, password, None ) @@ -450,7 +445,7 @@ def main(): else: print(f"User {username} already existed") elif args.operation == "delete": - (username,) = args.rest + username = args.username try: resp = users.delete_user(client, username) except ValueError as e: @@ -458,10 +453,8 @@ def main(): print(e) sys.exit(EXIT_NOT_FOUND) elif args.operation == "group": - subop, *subopargs = args.rest - - if subop == "add": - username, groupname = subopargs + if args.subop == "add": + username, groupname = args.username, args.groupname user_data = users.get_user(client, username) group_id = groups.get_group_id(client, groupname) user_data["groups"].append( @@ -472,9 +465,8 @@ def main(): } ) resp = users.update_user(client, user_data) - - if subop == "remove": - username, groupname = subopargs + elif args.subop == "remove": + username, groupname = args.username, args.groupname user_data = users.get_user(client, username) group_id = groups.get_group_id(client, groupname) user_data["groups"] = list( @@ -483,18 +475,16 @@ def main(): ) ) resp = users.update_user(client, user_data) - else: - print_unknown_error(args) elif args.kind == "group": if args.operation == "list": resp = groups.get_group_list(client) print(format_list(resp["data"], "name")) elif args.operation == "create": - (name,) = args.rest + name = args.name resp = groups.create_group(client, name) elif args.operation == "delete": - (name,) = args.rest + name = args.name try: resp = groups.delete_group(client, name) except ValueError as e: @@ -502,47 +492,37 @@ def main(): print(e) sys.exit(EXIT_NOT_FOUND) elif args.operation == "perm": - subop, *subopargs = args.rest - if subop == "list": - (groupname,) = subopargs + if args.subop == "list": + groupname = args.groupname resp = groups.get_permissions(client, groupname) print(format_list(resp["data"], "permission")) - elif subop == "add": - groupname, perm = subopargs + elif args.subop == "add": + groupname, perm = args.groupname, args.perm perms = [ p["permission"] for p in groups.get_permissions(client, groupname)["data"] ] perms = list(set(perms) | set([perm])) resp = groups.set_permissions(client, groupname, perms) - elif subop == "remove": - groupname, perm = subopargs + elif args.subop == "remove": + groupname, perm = args.groupname, args.perm resp = groups.delete_permission(client, groupname, perm) - else: - print(f"Unknown group perm operation '{subop}'") - sys.exit(EXIT_UNKNOWN_ERROR) - else: - print_unknown_error(args) elif args.kind == "namespace": if args.operation == "get": - (name,) = args.rest + name = args.name print(json.dumps(namespaces.get_namespace(client, name))) elif args.operation == "list": print(json.dumps(namespaces.get_namespace_list(client))) elif args.operation == "list-collections": - (name,) = args.rest + name = args.name print(json.dumps(namespaces.get_namespace_collections(client, name))) elif args.operation == "create": - if len(args.rest) == 2: - name, group = args.rest - else: - (name,) = args.rest - group = None + name, group = args.name, args.group resp = namespaces.create_namespace(client, name, group) elif args.operation == "delete": - (name,) = args.rest + name = args.name try: resp = namespaces.delete_namespace(client, name) except ValueError as e: @@ -553,10 +533,10 @@ def main(): elif args.operation == "groups": raise NotImplementedError elif args.operation == "addgroup": - name, group = args.rest + name, group = args.name, args.group resp = namespaces.add_group(client, name, group) elif args.operation == "removegroup": - name, group = args.rest + name, group = args.name, args.group resp = namespaces.remove_group(client, name, group) elif args.operation == "addgroupperm": raise NotImplementedError @@ -564,24 +544,19 @@ def main(): raise NotImplementedError elif args.operation == "sign": raise NotImplementedError - else: - print_unknown_error(args) elif args.kind == "container": if args.operation == "readme": - if len(args.rest) == 1: - (container,) = args.rest + if args.readme == None: + container = args.container resp = containers.get_readme(client, container) print(resp["text"]) - elif len(args.rest) == 2: - container, readme = args.rest - resp = containers.set_readme(client, container, readme) else: - print("container readme takes either 1 or 2 parameters.") - sys.exit(EXIT_UNKNOWN_ERROR) + container, readme = args.container, args.readme + resp = containers.set_readme(client, container, readme) elif args.operation == "delete": - (name,) = args.rest + name = args.name try: resp = containers.delete_container(client, name) except ValueError as e: @@ -590,7 +565,11 @@ def main(): sys.exit(EXIT_NOT_FOUND) elif args.operation == "create": - name, upstream_name, registry = args.rest + name, upstream_name, registry = ( + args.name, + args.upstream_name, + args.registry, + ) try: resp = containers.create_container( client, name, upstream_name, registry @@ -600,24 +579,19 @@ def main(): print(e) sys.exit(EXIT_NOT_FOUND) - else: - print_unknown_error(args) - elif args.kind == "container-image": if args.operation == "delete": - container, image = args.rest + container, image = args.container, args.image try: resp = container_images.delete_container(client, container, image) except ValueError as e: if not args.ignore: print(e) sys.exit(EXIT_NOT_FOUND) - else: - print_unknown_error(args) elif args.kind == "registry": if args.operation == "delete": - (name,) = args.rest + name = args.name try: resp = registries.delete_registry(client, name) except ValueError as e: @@ -625,31 +599,23 @@ def main(): print(e) sys.exit(EXIT_NOT_FOUND) elif args.operation == "create": - name, url = args.rest + name, url = args.name, args.url try: resp = registries.create_registry(client, name, url) except ValueError as e: if not args.ignore: print(e) sys.exit(EXIT_NOT_FOUND) - else: - print_unknown_error(args) elif args.kind == "collection": if args.operation == "list": print(json.dumps(collections.get_collection_list(client))) elif args.operation == "upload": - if len(args.rest) == 0: - (namespace, collection_name, version) = ( - client.username, - None, - "1.0.0", - ) - elif len(args.rest) == 2: - (namespace, collection_name) = args.rest - version = "1.0.0" - else: - (namespace, collection_name, version) = args.rest + namespace, collection_name, version = ( + args.namespace or client.username, + args.collection_name, + args.version or "1.0.0", + ) resp = namespaces.create_namespace(client, namespace, None) artifact = collections.upload_test_collection( @@ -660,30 +626,23 @@ def main(): ) print(json.dumps(artifact)) elif args.operation == "move": - if len(args.rest) == 2: - (namespace, collection_name) = args.rest - # defaults to version = 1.0.0, source = staging, destination = published - collections.move_collection(client, namespace, collection_name) - else: - ( - namespace, - collection_name, - version, - source, - destination, - ) = args.rest - collections.move_collection( - client, namespace, collection_name, version, source, destination - ) + namespace, collection_name, version, source, destination = ( + args.namespace, + args.collection_name, + args.version or "1.0.0", + args.source or "staging", + args.destination or "published", + ) + collections.move_collection( + client, namespace, collection_name, version, source, destination + ) elif args.operation == "delete": - repository = "published" - version = None - if len(args.rest) == 4: - namespace, collection, version, repository = args.rest - if len(args.rest) == 3: - namespace, collection, version = args.rest - if len(args.rest) == 2: - namespace, collection = args.rest + namespace, collection, version, repository = ( + args.namespace, + args.collection, + args.version, + args.repository or "published", + ) try: if version == "None": version = None @@ -697,17 +656,12 @@ def main(): elif args.operation == "download": raise NotImplementedError elif args.operation == "info": - if len(args.rest) == 3: - (namespace, collection_name, version) = args.rest - repository = "published" - elif len(args.rest) == 4: - (repository, namespace, collection_name, version) = args.rest - else: - print( - "galaxykit collection info [repository] " - ) - print(args.rest) - sys.exit(EXIT_UNKNOWN_ERROR) + repository, namespace, collection_name, version = ( + args.repository or "published", + args.namespace, + args.collection_name, + args.version, + ) print( json.dumps( collections.collection_info( @@ -716,17 +670,12 @@ def main(): ) ) elif args.operation == "sign": - if len(args.rest) == 3: - (namespace, collection_name, version) = args.rest - repository = "published" - elif len(args.rest) == 4: - (repository, namespace, collection_name, version) = args.rest - else: - print( - "galaxykit collection info [repository] " - ) - print(args.rest) - sys.exit(EXIT_UNKNOWN_ERROR) + repository, namespace, collection_name, version = ( + args.repository or "published", + args.namespace, + args.collection_name, + args.version, + ) print( json.dumps( collections.collection_sign( @@ -734,23 +683,15 @@ def main(): ) ) ) - else: - print_unknown_error(args) elif args.kind == "url": if args.operation == "get": - (url,) = args.rest + url = args.url print(json.dumps(client.get(url))) elif args.operation == "post": - url = args.rest[0] + url = args.url body = sys.stdin.read() print(json.dumps(client.post(url, body))) - else: - print_unknown_error(args) - - else: - print(f"Unknown resource type '{args.kind}'") - sys.exit(EXIT_UNKNOWN_ERROR) if resp and not args.ignore: report_error(resp) From 6fcf8e74533058583c8cbde3d010f0e44cf13d27 Mon Sep 17 00:00:00 2001 From: Martin Hradil Date: Sun, 22 May 2022 20:17:14 +0000 Subject: [PATCH 3/5] commany.py: username, password - add dest= with auth_ prefix --- galaxykit/command.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/galaxykit/command.py b/galaxykit/command.py index cac1ab2..fe247f2 100644 --- a/galaxykit/command.py +++ b/galaxykit/command.py @@ -351,6 +351,7 @@ def params_main(parser): "--username", action="store", default="admin", + dest="auth_username", type=str, ) parser.add_argument( @@ -358,6 +359,7 @@ def params_main(parser): "--password", action="store", default="admin", + dest="auth_password", type=str, ) parser.add_argument( @@ -412,8 +414,8 @@ def main(): if args.auth_url and not args.token: creds = { "auth_url": args.auth_url, - "username": args.username, - "password": args.password, + "username": args.auth_username, + "password": args.auth_password, } elif args.token: creds = { @@ -423,8 +425,8 @@ def main(): creds["auth_url"] = args.auth_url else: creds = { - "username": args.username, - "password": args.password, + "username": args.auth_username, + "password": args.auth_password, } client = GalaxyClient(args.server, creds, https_verify=https_verify) From 6f9dbe6b2aad0d6d07b6a50c93c64bb63c1ed559 Mon Sep 17 00:00:00 2001 From: Martin Hradil Date: Sun, 22 May 2022 20:41:49 +0000 Subject: [PATCH 4/5] command.py: add more "help" as example --- galaxykit/command.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/galaxykit/command.py b/galaxykit/command.py index fe247f2..f98d98a 100644 --- a/galaxykit/command.py +++ b/galaxykit/command.py @@ -49,10 +49,20 @@ def report_error(resp): "args": None, }, "upload": { + "help": "Create and upload a new collection", "args": { - "namespace": {"nargs": "?"}, - "collection_name": {"nargs": "?"}, - "version": {"nargs": "?"}, + "namespace": { + "help": "Collection namespace (optional, defaults to --username)", + "nargs": "?", + }, + "collection_name": { + "help": "Collection name (optional, randomly generated)", + "nargs": "?", + }, + "version": { + "help": "Version (optional, defaults to 1.0.0)", + "nargs": "?", + }, }, }, "move": { @@ -200,18 +210,21 @@ def report_error(resp): } }, "group": { + "help": "User group membership", "subops": { "add": { + "help": "Add user to group", "args": { "username": {}, "groupname": {}, - } + }, }, "remove": { + "help": "Remove user from group", "args": { "username": {}, "groupname": {}, - } + }, }, }, }, From 9f597ec16da4383f7e01f5699c448255ddd661c0 Mon Sep 17 00:00:00 2001 From: Martin Hradil Date: Sun, 22 May 2022 21:18:48 +0000 Subject: [PATCH 5/5] command.py: add default arg values to parser code --- galaxykit/command.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/galaxykit/command.py b/galaxykit/command.py index f98d98a..bcbf5df 100644 --- a/galaxykit/command.py +++ b/galaxykit/command.py @@ -54,14 +54,17 @@ def report_error(resp): "namespace": { "help": "Collection namespace (optional, defaults to --username)", "nargs": "?", + "default": None, }, "collection_name": { "help": "Collection name (optional, randomly generated)", "nargs": "?", + "default": None, }, "version": { "help": "Version (optional, defaults to 1.0.0)", "nargs": "?", + "default": "1.0.0", }, }, }, @@ -69,23 +72,23 @@ def report_error(resp): "args": { "namespace": {}, "collection_name": {}, - "version": {"nargs": "?"}, - "source": {"nargs": "?"}, - "destination": {"nargs": "?"}, + "version": {"nargs": "?", "default": "1.0.0"}, + "source": {"nargs": "?", "default": "staging"}, + "destination": {"nargs": "?", "default": "published"}, }, }, "delete": { "args": { "namespace": {}, "collection": {}, - "version": {"nargs": "?"}, - "repository": {"nargs": "?"}, + "version": {"nargs": "?", "default": None}, + "repository": {"nargs": "?", "default": "published"}, }, }, "download": None, "info": { "args": { - "repository": {"nargs": "?"}, + "repository": {"nargs": "?", "default": "published"}, "namespace": {}, "collection_name": {}, "version": {}, @@ -93,7 +96,7 @@ def report_error(resp): }, "sign": { "args": { - "repository": {"nargs": "?"}, + "repository": {"nargs": "?", "default": "published"}, "namespace": {}, "collection_name": {}, "version": {}, @@ -118,7 +121,7 @@ def report_error(resp): "create": { "args": { "name": {}, - "group": {"nargs": "?"}, + "group": {"nargs": "?", "default": None}, } }, "delete": { @@ -150,7 +153,7 @@ def report_error(resp): "readme": { "args": { "container": {}, - "readme": {"nargs": "?"}, + "readme": {"nargs": "?", "default": None}, } }, "delete": {