From 8ecbde6b05fc38ea5fe0908b2052de8d08399bec Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Thu, 5 Dec 2024 11:27:05 +0100 Subject: [PATCH] Split metadata into independent services Handling metadata in single module becomes not manageable. Split it into separate service-related modules. Change-Id: I7551b4a96cf030c57bfb0a7b9eb868cd760a6de5 --- codegenerator/metadata.py | 1256 ------------------ codegenerator/metadata/__init__.py | 669 ++++++++++ codegenerator/metadata/baremetal.py | 33 + codegenerator/metadata/base.py | 31 + codegenerator/metadata/block_storage.py | 135 ++ codegenerator/metadata/compute.py | 195 +++ codegenerator/metadata/dns.py | 48 + codegenerator/metadata/identity.py | 143 ++ codegenerator/metadata/image.py | 106 ++ codegenerator/metadata/load_balancer.py | 43 + codegenerator/metadata/network.py | 89 ++ codegenerator/metadata/object_store.py | 91 ++ codegenerator/metadata/placement.py | 69 + codegenerator/metadata/shared_file_system.py | 33 + metadata/block-storage_metadata.yaml | 682 +++++----- metadata/identity_metadata.yaml | 122 +- metadata/image_metadata.yaml | 24 + metadata/load-balancer_metadata.yaml | 18 +- 18 files changed, 2119 insertions(+), 1668 deletions(-) delete mode 100644 codegenerator/metadata.py create mode 100644 codegenerator/metadata/__init__.py create mode 100644 codegenerator/metadata/baremetal.py create mode 100644 codegenerator/metadata/base.py create mode 100644 codegenerator/metadata/block_storage.py create mode 100644 codegenerator/metadata/compute.py create mode 100644 codegenerator/metadata/dns.py create mode 100644 codegenerator/metadata/identity.py create mode 100644 codegenerator/metadata/image.py create mode 100644 codegenerator/metadata/load_balancer.py create mode 100644 codegenerator/metadata/network.py create mode 100644 codegenerator/metadata/object_store.py create mode 100644 codegenerator/metadata/placement.py create mode 100644 codegenerator/metadata/shared_file_system.py diff --git a/codegenerator/metadata.py b/codegenerator/metadata.py deleted file mode 100644 index 8047f53..0000000 --- a/codegenerator/metadata.py +++ /dev/null @@ -1,1256 +0,0 @@ -# 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 pathlib import Path -import logging -import re - -import jsonref -from ruamel.yaml import YAML - -from codegenerator.base import BaseGenerator -from codegenerator import common -from codegenerator.common.schema import SpecSchema -from codegenerator.types import Metadata -from codegenerator.types import OperationModel -from codegenerator.types import OperationTargetParams -from codegenerator.types import ResourceModel - -OPERATION_ID_BLACKLIST: set[str] = { - # # BlockStorage - # ## Host put has no schema - "project_id/os-hosts:put", - "os-hosts:put", - "project_id/os-hosts/id:put", - "os-hosts/id:put", -} - - -class MetadataGenerator(BaseGenerator): - """Generate metadata from OpenAPI spec""" - - def load_openapi(self, path): - """Load existing OpenAPI spec from the file""" - if not path.exists(): - return - yaml = YAML(typ="safe") - with open(path) as fp: - spec = jsonref.replace_refs(yaml.load(fp)) - - return SpecSchema(**spec) - - def generate( - self, res, target_dir, openapi_spec=None, operation_id=None, args=None - ): - """Generate Json Schema definition file for Resource""" - logging.debug("Generating OpenAPI schema data") - # We do not import generators since due to the use of Singletons in the - # code importing glance, nova, cinder at the same time crashes - # dramatically - spec_path = Path(args.openapi_yaml_spec) - metadata_path = Path(target_dir, args.service_type + "_metadata.yaml") - - schema = self.load_openapi(spec_path) - openapi_spec = common.get_openapi_spec(spec_path) - metadata = self.build_metadata( - schema, openapi_spec, args.service_type, spec_path.as_posix() - ) - - yaml = YAML() - yaml.preserve_quotes = True - yaml.default_flow_style = False - yaml.indent(mapping=2, sequence=4, offset=2) - metadata_path.parent.mkdir(exist_ok=True, parents=True) - with open(metadata_path, "w") as fp: - yaml.dump( - metadata.model_dump( - exclude_none=True, exclude_defaults=True, by_alias=True - ), - fp, - ) - - @staticmethod - def build_metadata( - schema, openapi_spec, service_type: str, spec_path: str - ) -> Metadata: - metadata = Metadata(resources={}) - api_ver = "v" + schema.info["version"].split(".")[0] - for path, spec in schema.paths.items(): - path_elements: list[str] = path.split("/") - resource_name = "/".join( - list(common.get_resource_names_from_url(path)) - ) - if service_type == "object-store": - if path == "/v1/{account}": - resource_name = "account" - elif path == "/v1/{account}/{container}": - resource_name = "container" - if path == "/v1/{account}/{object}": - resource_name = "object" - - if service_type == "compute" and resource_name in [ - "agent", - "baremetal_node", - "cell", - "cell/capacity", - "cell/info", - "cell/sync_instance", - "certificate", - "cloudpipe", - "fping", - "fixed_ip", - "floating_ip_dns", - "floating_ip_dns/entry", - "floating_ip_pool", - "floating_ip_bulk", - "host", - "host/reboot", - "host/shutdown", - "host/startup", - "image", - "image/metadata", - "network", - "security_group_default_rule", - "security_group_rule", - "security_group", - "server/console", - "server/virtual_interface", - "snapshot", - "tenant_network", - "volume", - "volumes_boot", - ]: - # We do not need to produce anything for deprecated APIs - continue - resource_model = metadata.resources.setdefault( - f"{service_type}.{resource_name}", - ResourceModel( - api_version=api_ver, spec_file=spec_path, operations={} - ), - ) - for method in [ - "head", - "get", - "put", - "post", - "delete", - "options", - "patch", - ]: - operation = getattr(spec, method, None) - if operation: - if not operation.operationId: - # Every operation must have operationId - continue - if operation.operationId in OPERATION_ID_BLACKLIST: - # For blacklisted operationIds were are not producing anything - continue - - op_model = OperationModel( - operation_id=operation.operationId, targets={} - ) - operation_key: str | None = None - - response_schema: dict | None = None - for code, rsp in operation.responses.items(): - if code.startswith("2"): - response_schema = ( - rsp.get("content", {}) - .get("application/json", {}) - .get("schema", {}) - ) - break - if path.endswith("}"): - if method == "get": - operation_key = "show" - elif method == "head": - operation_key = "check" - elif method == "put": - operation_key = "update" - elif method == "patch": - if "application/json" in operation.requestBody.get( - "content", {} - ): - operation_key = "update" - else: - operation_key = "patch" - elif method == "post": - operation_key = "create" - elif method == "delete": - operation_key = "delete" - elif ( - path.endswith("/detail") - and resource_name != "quota_set" - ): - if method == "get": - operation_key = "list_detailed" - # elif path.endswith("/default"): - # operation_key = "default" - elif path == "/v2/images/{image_id}/file": - if method == "put": - operation_key = "upload" - elif method == "get": - operation_key = "download" - else: - raise NotImplementedError - elif path == "/v3/users/{user_id}/password": - if method == "post": - operation_key = "update" - elif ( - service_type == "compute" - and resource_name == "flavor/flavor_access" - and method == "get" - ): - operation_key = "list" - elif ( - service_type == "compute" - and resource_name == "aggregate/image" - and method == "post" - ): - operation_key = "action" - elif ( - service_type == "compute" - and resource_name == "server/security_group" - and method == "get" - ): - operation_key = "list" - elif ( - service_type == "compute" - and resource_name == "server/topology" - and method == "get" - ): - operation_key = "list" - elif ( - service_type == "compute" - and resource_name == "quota_set" - and path.endswith("defaults") - ): - operation_key = "defaults" - elif ( - service_type == "compute" - and resource_name == "quota_set" - and path.endswith("detail") - ): - # normalize "details" name - operation_key = "details" - elif ( - service_type == "load-balancer" - and len(path_elements) > 1 - and path_elements[-1] - in ["stats", "status", "failover", "config"] - ): - operation_key = path_elements[-1] - - elif response_schema and ( - method == "get" - and ( - response_schema.get("type", "") == "array" - or ( - response_schema.get("type", "") == "object" - and "properties" in response_schema - and len(path_elements) > 1 - and path_elements[-1] - in response_schema.get("properties", {}) - ) - ) - ): - # Response looks clearly like a list - operation_key = "list" - elif path.endswith("/action"): - # Action - operation_key = "action" - elif service_type == "image" and path.endswith( - "/actions/deactivate" - ): - operation_key = "deactivate" - elif service_type == "image" and path.endswith( - "/actions/reactivate" - ): - operation_key = "reactivate" - elif ( - service_type == "block-storage" - and "volume-transfer" in path - and path.endswith("/accept") - ): - operation_key = "accept" - elif ( - service_type == "block-storage" - and "qos-specs" in path - and path_elements[-1] - in [ - "associate", - "disassociate", - "disassociate_all", - "delete_keys", - ] - ): - operation_key = path_elements[-1] - elif ( - service_type == "network" - and "quota" in path - and path.endswith("/default") - ): - # normalize "defaults" name - operation_key = "defaults" - elif ( - service_type == "network" - and "quota" in path - and path.endswith("/details") - ): - operation_key = "details" - elif ( - service_type == "placement" - and resource_name == "allocation_candidate" - and method == "get" - ): - operation_key = "list" - elif ( - service_type == "placement" - and resource_name - in [ - "resource_provider/aggregate", - "resource_provider/trait", - ] - and method == "put" - ): - operation_key = "update" - elif ( - len( - [ - x - for x in schema.paths.keys() - if x.startswith(path + "/{") - ] - ) - > 0 - ): - # if we are at i.e. /v2/servers and there is - # /v2/servers/{ most likely we are at the collection - # level - if method == "get": - operation_key = "list" - elif method == "head": - operation_key = "check" - elif method == "patch": - if "application/json" in operation.requestBody.get( - "content", {} - ): - operation_key = "update" - else: - operation_key = "patch" - elif method == "post": - operation_key = "create" - elif method == "put": - operation_key = "replace" - elif method == "delete": - operation_key = "delete_all" - elif method == "head": - operation_key = "check" - elif method == "get": - operation_key = "get" - elif method == "post": - operation_key = "create" - elif method == "put": - operation_key = path.split("/")[-1] - elif method == "patch": - if "application/json" in operation.requestBody.get( - "content", {} - ): - operation_key = "update" - else: - operation_key = "patch" - elif method == "delete": - operation_key = "delete" - if not operation_key: - logging.warning( - f"Cannot identify op name for {path}:{method}" - ) - - # Next hacks - if service_type == "identity" and resource_name in [ - "OS_FEDERATION/identity_provider", - "OS_FEDERATION/identity_provider/protocol", - "OS_FEDERATION/mapping", - "OS_FEDERATION/service_provider", - ]: - if method == "put": - operation_key = "create" - elif method == "patch": - operation_key = "update" - if ( - service_type == "identity" - and resource_name - in [ - "domain/config", - "domain/config/group", - "domain/config/group/option", - ] - and path.endswith("/default") - and method == "get" - ): - operation_key = "default" - - if ( - service_type == "identity" - and resource_name - in [ - "domain/config", - "domain/config/group", - "domain/config/group/option", - ] - and path.endswith("/default") - and method == "head" - ): - # No need in HEAD defaults - continue - if service_type == "object-store": - if resource_name == "object": - mapping_obj: dict[str, str] = { - "head": "head", - "get": "get", - "delete": "delete", - "put": "put", - "post": "update", - } - operation_key = mapping_obj[method] - elif resource_name == "container": - mapping_cont: dict[str, str] = { - "head": "head", - "get": "get", - "delete": "delete", - "put": "create", - "post": "update", - } - operation_key = mapping_cont[method] - elif resource_name == "account": - mapping_account: dict[str, str] = { - "head": "head", - "get": "get", - "delete": "delete", - "put": "create", - "post": "update", - } - operation_key = mapping_account[method] - if service_type == "dns": - if resource_name == "zone/task": - if path == "/v2/zones/{zone_id}/tasks/xfr": - operation_key = "xfr" - if path == "/v2/zones/{zone_id}/tasks/abandon": - operation_key = "abandon" - if path == "/v2/zones/{zone_id}/tasks/pool_move": - operation_key = "pool_move" - elif resource_name == "quota" and path == "/v2/quotas": - # /quotas return quota for current project and not - # a "list" for all projects. As such it has no - # difference to show - continue - - if operation_key in resource_model: - raise RuntimeError("Operation name conflict") - else: - if operation_key == "action" and service_type in [ - "compute", - "block-storage", - "shared-file-system", - ]: - # For action we actually have multiple independent operations - try: - body_schema = operation.requestBody["content"][ - "application/json" - ]["schema"] - bodies = body_schema.get( - "oneOf", [body_schema] - ) - if len(bodies) > 1: - discriminator = body_schema.get( - "x-openstack", {} - ).get("discriminator") - if discriminator != "action": - raise RuntimeError( - f"Cannot generate metadata for {path} since request body is not having action discriminator" - ) - for body in bodies: - action_name = body.get( - "x-openstack", {} - ).get("action-name") - if not action_name: - action_name = list( - body["properties"].keys() - )[0] - # Hardcode fixes - if ( - resource_name == "flavor" - and action_name - in ["update", "create", "delete"] - ): - # Flavor update/create/delete - # operations are exposed ALSO as wsgi - # actions. This is wrong and useless. - logging.warning( - "Skipping generating %s:%s action", - resource_name, - action_name, - ) - continue - - operation_name = "-".join( - x.lower() - for x in re.split( - common.SPLIT_NAME_RE, action_name - ) - ).lower() - rust_sdk_params = ( - get_rust_sdk_operation_args( - "action", - operation_name=action_name, - module_name=get_module_name( - action_name - ), - ) - ) - rust_cli_params = ( - get_rust_cli_operation_args( - "action", - operation_name=action_name, - module_name=get_module_name( - action_name - ), - resource_name=resource_name, - ) - ) - - op_model = OperationModel( - operation_id=operation.operationId, - targets={}, - ) - op_model.operation_type = "action" - - op_model.targets["rust-sdk"] = ( - rust_sdk_params - ) - op_model.targets["rust-cli"] = ( - rust_cli_params - ) - - op_model = post_process_operation( - service_type, - resource_name, - operation_name, - op_model, - ) - - resource_model.operations[ - operation_name - ] = op_model - - except KeyError: - raise RuntimeError( - f"Cannot get bodies for {path}" - ) - else: - if not operation_key: - raise NotImplementedError - operation_type = get_operation_type_by_key( - operation_key - ) - op_model.operation_type = operation_type - # NOTE: sdk gets operation_key and not operation_type - rust_sdk_params = get_rust_sdk_operation_args( - operation_key - ) - rust_cli_params = get_rust_cli_operation_args( - operation_key, resource_name=resource_name - ) - - op_model.targets["rust-sdk"] = rust_sdk_params - if rust_cli_params and not ( - service_type == "identity" - and operation_key == "check" - ): - op_model.targets["rust-cli"] = rust_cli_params - - op_model = post_process_operation( - service_type, - resource_name, - operation_key, - op_model, - ) - - resource_model.operations[operation_key] = op_model - for res_name, res_data in metadata.resources.items(): - # Sanitize produced metadata - list_op = res_data.operations.get("list") - list_detailed_op = res_data.operations.get("list_detailed") - if list_op and list_detailed_op: - # There are both plain list and list with details operation. - # For the certain generator backend it makes no sense to have - # then both so we should disable generation of certain backends - # for the non detailed endpoint - list_op.targets.pop("rust-cli") - - # Prepare `find` operation data - if (list_op or list_detailed_op) and res_data.operations.get( - "show" - ): - show_op = res_data.operations["show"] - - (path, _, spec) = common.find_openapi_operation( - openapi_spec, show_op.operation_id - ) - mod_path = common.get_rust_sdk_mod_path( - service_type, res_data.api_version or "", path - ) - response_schema = None - for code, rspec in spec.get("responses", {}).items(): - if not code.startswith("2"): - continue - content = rspec.get("content", {}) - if "application/json" in content: - try: - (response_schema, _) = common.find_resource_schema( - content["application/json"].get("schema", {}), - None, - ) - except Exception as ex: - logging.exception( - "Cannot process response of %s operation: %s", - show_op.operation_id, - ex, - ) - - if not response_schema: - # Show does not have a suitable - # response. We can't have find - # for such - continue - if "id" not in response_schema.get("properties", {}).keys(): - # Resource has no ID in show method => find impossible - continue - elif ( - "name" not in response_schema.get("properties", {}).keys() - and res_name != "floatingip" - ): - # Resource has no NAME => find useless - continue - - list_op_ = list_detailed_op or list_op - if not list_op_: - continue - (_, _, list_spec) = common.find_openapi_operation( - openapi_spec, list_op_.operation_id - ) - name_field: str = "name" - for fqan, alias in common.FQAN_ALIAS_MAP.items(): - if fqan.startswith(res_name) and alias == "name": - name_field = fqan.split(".")[-1] - name_filter_supported: bool = False - if name_field in [ - x.get("name") - for x in list(list_spec.get("parameters", [])) - ]: - name_filter_supported = True - - sdk_params = OperationTargetParams( - module_name="find", - name_field=name_field, - name_filter_supported=name_filter_supported, - sdk_mod_path="::".join(mod_path), - list_mod="list_detailed" if list_detailed_op else "list", - ) - res_data.operations["find"] = OperationModel( - operation_id=list_op_.operation_id, - operation_type="find", - targets={"rust-sdk": sdk_params}, - ) - - # Let other operations know of `find` presence - for op_name, op_data in res_data.operations.items(): - if op_name not in ["find", "list", "create"]: - for ( - target_name, - target_params, - ) in op_data.targets.items(): - if target_name in ["rust-cli"]: - target_params.find_implemented_by_sdk = True - - return metadata - - -def get_operation_type_by_key(operation_key): - if operation_key in ["list", "list_detailed"]: - return "list" - elif operation_key in ["get", "stats", "status"]: - return "get" - elif operation_key == "check": - return "get" - elif operation_key == "show": - return "show" - elif operation_key in ["update", "replace"]: - return "set" - elif operation_key in ["delete", "delete_all"]: - return "delete" - elif operation_key in ["create"]: - return "create" - elif operation_key == "patch": - return "set" - elif operation_key == "default": - return "show" - elif operation_key == "defaults": - return "show" - elif operation_key == "details": - return "show" - elif operation_key == "download": - return "download" - elif operation_key == "upload": - return "upload" - elif operation_key in ["associate", "disassociate", "disassociate_all"]: - # Cinder XXXssociate are GET actions not accepting body - crazy - return "get" - else: - return "action" - - -def get_rust_sdk_operation_args( - operation_key: str, - operation_name: str | None = None, - module_name: str | None = None, -): - """Construct proper Rust SDK parameters for operation by type""" - sdk_params = OperationTargetParams() - sdk_params.module_name = module_name - if operation_key == "show": - sdk_params.module_name = "get" - elif operation_key == "list_detailed": - sdk_params.module_name = "list_detailed" - # elif operation_key == "action" and not module_name: - # sdk_params.module_name = operation_name if operation_name else operation_key - else: - sdk_params.module_name = module_name or get_module_name( - # get_operation_type_by_key(operation_key) - operation_key - ) - sdk_params.operation_name = operation_name - - return sdk_params - - -def get_rust_cli_operation_args( - operation_key: str, - operation_name: str | None = None, - module_name: str | None = None, - resource_name: str | None = None, -): - """Construct proper Rust CLI parameters for operation by type""" - # Get SDK params to connect things with each other - # operation_type = get_operation_type_by_key(operation_key) - sdk_params = get_rust_sdk_operation_args( - operation_key, operation_name=operation_name, module_name=module_name - ) - cli_params = OperationTargetParams() - cli_params.sdk_mod_name = sdk_params.module_name - cli_params.module_name = module_name or get_module_name(operation_key) - cli_params.operation_name = operation_name - if resource_name: - op_name = cli_params.module_name - if op_name.startswith("os_") or op_name.startswith("os-"): - op_name = op_name[3:] - op_name = op_name.replace("_", "-") - - cli_params.cli_full_command = ( - " ".join(x for x in resource_name.split("/")).replace("_", "-") - + " " - + op_name - ) - - return cli_params - - -def get_module_name(name): - if name in ["list", "list_detailed"]: - return "list" - elif name == "get": - return "get" - elif name == "show": - return "show" - elif name == "check": - return "head" - elif name == "update": - return "set" - elif name == "replace": - return "replace" - elif name == "delete": - return "delete" - elif name == "delete_all": - return "delete_all" - elif name in ["create"]: - return "create" - elif name in ["default"]: - return "default" - return "_".join(x.lower() for x in re.split(common.SPLIT_NAME_RE, name)) - - -def post_process_operation( - service_type: str, resource_name: str, operation_name: str, operation -): - if service_type == "compute": - operation = post_process_compute_operation( - resource_name, operation_name, operation - ) - elif service_type == "dns": - operation = post_process_dns_operation( - resource_name, operation_name, operation - ) - elif service_type == "identity": - operation = post_process_identity_operation( - resource_name, operation_name, operation - ) - elif service_type == "image": - operation = post_process_image_operation( - resource_name, operation_name, operation - ) - elif service_type in ["block-storage", "volume"]: - operation = post_process_block_storage_operation( - resource_name, operation_name, operation - ) - elif service_type == "network": - operation = post_process_network_operation( - resource_name, operation_name, operation - ) - elif service_type == "object-store": - operation = post_process_object_store_operation( - resource_name, operation_name, operation - ) - elif service_type == "placement": - operation = post_process_placement_operation( - resource_name, operation_name, operation - ) - return operation - - -def post_process_compute_operation( - resource_name: str, operation_name: str, operation -): - if resource_name == "aggregate": - if operation_name in ["set-metadata", "add-host", "remove-host"]: - operation.targets["rust-sdk"].response_key = "aggregate" - operation.targets["rust-cli"].response_key = "aggregate" - elif resource_name == "availability_zone": - if operation_name == "get": - operation.operation_type = "list" - operation.targets["rust-sdk"].operation_name = "list" - operation.targets["rust-sdk"].response_key = "availabilityZoneInfo" - operation.targets["rust-sdk"].module_name = "list" - operation.targets["rust-cli"].response_key = "availabilityZoneInfo" - operation.targets["rust-cli"].module_name = "list" - operation.targets["rust-cli"].sdk_mod_name = "list" - operation.targets["rust-cli"].operation_name = "list" - operation.targets["rust-sdk"].response_key = "availabilityZoneInfo" - operation.targets[ - "rust-cli" - ].cli_full_command = "availability-zone list" - elif operation_name == "list_detailed": - operation.operation_type = "list" - operation.targets["rust-sdk"].operation_name = "list_detail" - operation.targets["rust-sdk"].response_key = "availabilityZoneInfo" - operation.targets["rust-sdk"].module_name = "list_detail" - operation.targets["rust-cli"].response_key = "availabilityZoneInfo" - operation.targets["rust-cli"].operation_name = "list" - operation.targets["rust-cli"].module_name = "list_detail" - operation.targets["rust-cli"].sdk_mod_name = "list_detail" - operation.targets[ - "rust-cli" - ].cli_full_command = "availability-zone list-detail" - - elif resource_name == "keypair": - if operation_name == "list": - operation.targets["rust-sdk"].response_list_item_key = "keypair" - - elif resource_name == "server": - if "migrate-live" in operation_name: - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("migrate-live", "live-migrate") - elif resource_name == "server/instance_action": - if operation_name == "list": - operation.targets["rust-sdk"].response_key = "instanceActions" - operation.targets["rust-cli"].response_key = "instanceActions" - else: - operation.targets["rust-sdk"].response_key = "instanceAction" - operation.targets["rust-cli"].response_key = "instanceAction" - elif resource_name == "server/topology": - if operation_name == "list": - operation.targets["rust-sdk"].response_key = "nodes" - operation.targets["rust-cli"].response_key = "nodes" - elif resource_name == "server/volume_attachment": - if operation_name == "list": - operation.targets["rust-sdk"].response_key = "volumeAttachments" - operation.targets["rust-cli"].response_key = "volumeAttachments" - elif operation_name in ["create", "show", "update"]: - operation.targets["rust-sdk"].response_key = "volumeAttachment" - operation.targets["rust-cli"].response_key = "volumeAttachment" - elif resource_name == "server/server_password": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("server-password", "password") - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("get", "show") - elif resource_name == "server/security_group": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("security-group list", "security-groups") - if operation_name == "get": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("get", "show") - - elif resource_name == "flavor": - if operation_name == "add-tenant-access": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("add-tenant-access", "access add") - elif operation_name == "list-tenant-access": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("list-tenant-access", "access list") - elif operation_name == "remove-tenant-access": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("remove-tenant-access", "access remove") - - if resource_name == "limit": - # Limits API return object and not a list - operation.targets["rust-cli"].operation_type = "show" - operation.targets["rust-cli"].response_key = "limits" - operation.targets["rust-sdk"].response_key = "limits" - - if "/tag" in resource_name: - if operation_name == "update": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("set", "add") - elif operation_name == "show": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("show", "check") - - if operation_name == "delete_all": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("delete-all", "purge") - - return operation - - -def post_process_identity_operation( - resource_name: str, operation_name: str, operation -): - if resource_name == "role/imply": - if operation_name == "list": - operation.targets["rust-cli"].response_key = "role_inference" - operation.targets["rust-sdk"].response_key = "role_inference" - if resource_name == "role_inference": - if operation_name == "list": - operation.targets["rust-cli"].response_key = "role_inferences" - operation.targets["rust-sdk"].response_key = "role_inferences" - - if resource_name == "domain/config/group": - operation.targets["rust-sdk"].response_key = "config" - if "rust-cli" in operation.targets: - operation.targets["rust-cli"].response_key = "config" - elif resource_name == "domain/config/group/option": - operation.targets["rust-sdk"].response_key = "config" - if "rust-cli" in operation.targets: - operation.targets["rust-cli"].response_key = "config" - - if "rust-cli" in operation.targets: - if "OS_FEDERATION" in resource_name: - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("OS-FEDERATION", "federation") - elif resource_name == "user/project": - operation.targets["rust-cli"].cli_full_command = "user projects" - elif resource_name == "user/group": - operation.targets["rust-cli"].cli_full_command = "user groups" - elif resource_name == "user/access_rule": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("user access-rule", "access-rule") - elif resource_name == "user/application_credential": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace( - "user application-credential", "application-credential" - ) - - if "/tag" in resource_name: - if operation_name == "update": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("set", "add") - elif operation_name == "show": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("show", "check") - - if operation_name == "delete_all": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("delete-all", "purge") - - return operation - - -def post_process_image_operation( - resource_name: str, operation_name: str, operation -): - if resource_name.startswith("schema"): - # Image schemas are a JSON operation - operation.targets["rust-cli"].operation_type = "json" - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("get", "show") - elif resource_name == "metadef/namespace" and operation_name != "list": - operation.targets["rust-sdk"].response_key = "null" - operation.targets["rust-cli"].response_key = "null" - elif ( - resource_name == "metadef/namespace/property" - and operation_name == "list" - ): - operation.targets["rust-cli"].operation_type = "list_from_struct" - operation.targets["rust-cli"].response_key = "properties" - operation.targets["rust-sdk"].response_key = "properties" - elif resource_name == "metadef/namespace/resource_type": - operation.targets[ - "rust-cli" - ].response_key = "resource_type_associations" - operation.targets[ - "rust-sdk" - ].response_key = "resource_type_associations" - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace( - "resource-type", "resource-type-association" - ) - elif resource_name == "image": - if operation_name == "patch": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("patch", "set") - elif resource_name == "image/file": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("file ", "") - - if "/tag" in resource_name: - if operation_name == "update": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("set", "add") - elif operation_name == "show": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("show", "check") - - if operation_name == "delete_all": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("delete-all", "purge") - - return operation - - -def post_process_block_storage_operation( - resource_name: str, operation_name: str, operation -): - if resource_name == "type": - if operation_name == "list": - operation.targets["rust-cli"].response_key = "volume_types" - operation.targets["rust-sdk"].response_key = "volume_types" - elif operation_name in ["create", "show", "update"]: - operation.targets["rust-cli"].response_key = "volume_type" - operation.targets["rust-sdk"].response_key = "volume_type" - elif resource_name == "type/volume_type_access": - operation.targets["rust-cli"].response_key = "volume_type_access" - operation.targets["rust-sdk"].response_key = "volume_type_access" - - if "/tag" in resource_name: - if operation_name == "update": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("set", "add") - elif operation_name == "show": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("show", "check") - - if resource_name == "snapshot": - if "update-snapshot-status" in operation_name: - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace( - "update-snapshot-status", "update-status" - ) - - if resource_name in ["os_volume_transfer", "volume_transfer"]: - if operation_name in ["list", "list_detailed"]: - operation.targets["rust-cli"].response_key = "transfers" - operation.targets["rust-sdk"].response_key = "transfers" - elif operation_name in ["accept", "create", "show"]: - operation.targets["rust-cli"].response_key = "transfer" - - if resource_name == "availability_zone": - if operation_name == "get": - operation.operation_type = "list" - operation.targets["rust-sdk"].operation_name = "list" - operation.targets["rust-sdk"].response_key = "availabilityZoneInfo" - operation.targets["rust-sdk"].module_name = "list" - operation.targets["rust-cli"].response_key = "availabilityZoneInfo" - operation.targets["rust-cli"].module_name = "list" - operation.targets["rust-cli"].sdk_mod_name = "list" - operation.targets["rust-cli"].operation_name = "list" - operation.targets["rust-sdk"].response_key = "availabilityZoneInfo" - operation.targets[ - "rust-cli" - ].cli_full_command = "availability-zone list" - if resource_name == "qos_spec/association": - operation.operation_type = "list" - operation.targets["rust-sdk"].operation_name = "list" - operation.targets["rust-sdk"].module_name = "list" - operation.targets["rust-cli"].operation_name = "list" - operation.targets["rust-cli"].module_name = "list" - operation.targets["rust-cli"].sdk_mod_name = "list" - operation.targets["rust-sdk"].response_key = "qos_associations" - operation.targets["rust-cli"].response_key = "qos_associations" - operation.targets[ - "rust-cli" - ].cli_full_command = "qos-spec association list" - - if resource_name == "limit" and operation_name == "list": - # Limits API return object and not a list - operation.targets["rust-cli"].operation_type = "show" - - if operation_name == "delete_all": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("delete-all", "purge") - - return operation - - -def post_process_network_operation( - resource_name: str, operation_name: str, operation -): - if resource_name.startswith("floatingip"): - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("floatingip", "floating-ip") - - if resource_name == "router": - if "external_gateways" in operation_name: - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("external-gateways", "external-gateway") - elif "extraroutes" in operation_name: - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("extraroutes", "extraroute") - - if resource_name == "address_group": - if "addresses" in operation_name: - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("addresses", "address") - - if "/tag" in resource_name: - if operation_name == "update": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("set", "add") - elif operation_name == "show": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("show", "check") - - if operation_name == "delete_all": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("delete-all", "purge") - - return operation - - -def post_process_object_store_operation( - resource_name: str, operation_name: str, operation -): - if resource_name == "account": - if operation_name == "get": - operation.targets["rust-cli"].cli_full_command = "container list" - elif operation_name == "head": - operation.targets["rust-cli"].cli_full_command = "account show" - elif resource_name == "container": - if operation_name == "get": - operation.targets["rust-cli"].cli_full_command = "object list" - elif operation_name == "head": - operation.targets["rust-cli"].cli_full_command = "container show" - elif resource_name == "object": - if operation_name == "get": - operation.targets["rust-cli"].cli_full_command = "object download" - operation.operation_type = "download" - elif operation_name == "head": - operation.targets["rust-cli"].cli_full_command = "object show" - elif operation_name == "put": - operation.targets["rust-cli"].cli_full_command = "object upload" - operation.operation_type = "upload" - - return operation - - -def post_process_dns_operation( - resource_name: str, operation_name: str, operation -): - # if resource_name == "zone/xfr": - # if operation_name == "create": - # operation.targets["rust-cli"].cli_full_command = "xfr" - # if resource_name == "zone/xfr": - # if operation_name == "create": - # operation.targets["rust-cli"].cli_full_command = "xfr" - - return operation - - -def post_process_placement_operation( - resource_name: str, operation_name: str, operation -): - if resource_name == "allocation_candidate": - if operation_name == "list": - operation.operation_type = "show" - elif resource_name == "resource_provider/aggregate": - if operation_name == "list": - operation.operation_type = "show" - elif resource_name == "resource_provider/allocation": - if operation_name == "list": - operation.operation_type = "show" - elif resource_name == "resource_provider/trait": - if operation_name == "list": - operation.operation_type = "show" - elif resource_name == "usages": - if operation_name == "list": - operation.operation_type = "list_from_struct" - operation.targets["rust-cli"].response_key = "usages" - operation.targets["rust-sdk"].response_key = "usages" - if operation_name == "delete_all": - operation.targets["rust-cli"].cli_full_command = operation.targets[ - "rust-cli" - ].cli_full_command.replace("delete-all", "purge") - - return operation diff --git a/codegenerator/metadata/__init__.py b/codegenerator/metadata/__init__.py new file mode 100644 index 0000000..5ba9e5f --- /dev/null +++ b/codegenerator/metadata/__init__.py @@ -0,0 +1,669 @@ +# 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 abc import ABC, abstractmethod +from pathlib import Path +import logging +import re +import typing as ty + +import jsonref +from ruamel.yaml import YAML + +from codegenerator.base import BaseGenerator +from codegenerator import common +from codegenerator.common.schema import SpecSchema +from codegenerator.metadata.base import MetadataBase +from codegenerator.metadata.baremetal import BaremetalMetadata +from codegenerator.metadata.block_storage import BlockStorageMetadata +from codegenerator.metadata.compute import ComputeMetadata +from codegenerator.metadata.dns import DnsMetadata +from codegenerator.metadata.identity import IdentityMetadata +from codegenerator.metadata.image import ImageMetadata +from codegenerator.metadata.load_balancer import LoadBalancerMetadata +from codegenerator.metadata.network import NetworkMetadata +from codegenerator.metadata.object_store import ObjectStorageMetadata +from codegenerator.metadata.placement import PlacementMetadata +from codegenerator.metadata.shared_file_system import SharedFileSystemMetadata +from codegenerator.types import Metadata +from codegenerator.types import OperationModel +from codegenerator.types import OperationTargetParams +from codegenerator.types import ResourceModel + + +OPERATION_ID_BLACKLIST: set[str] = { + # # BlockStorage + # ## Host put has no schema + "project_id/os-hosts:put", + "os-hosts:put", + "project_id/os-hosts/id:put", + "os-hosts/id:put", +} + +SERVICE_METADATA_MAP: dict[str, ty.Type[MetadataBase]] = { + "baremetal": BaremetalMetadata, + "block-storage": BlockStorageMetadata, + "volume": BlockStorageMetadata, + "compute": ComputeMetadata, + "dns": DnsMetadata, + "identity": IdentityMetadata, + "image": ImageMetadata, + "load-balancer": LoadBalancerMetadata, + "network": NetworkMetadata, + "object-store": ObjectStorageMetadata, + "placement": PlacementMetadata, + "shared-file-system": SharedFileSystemMetadata, +} + + +class MetadataGenerator(BaseGenerator): + """Generate metadata from OpenAPI spec""" + + def load_openapi(self, path): + """Load existing OpenAPI spec from the file""" + if not path.exists(): + return + yaml = YAML(typ="safe") + with open(path) as fp: + spec = jsonref.replace_refs(yaml.load(fp)) + + return SpecSchema(**spec) + + def generate( + self, res, target_dir, openapi_spec=None, operation_id=None, args=None + ): + """Generate Json Schema definition file for Resource""" + logging.debug("Generating OpenAPI schema data") + # We do not import generators since due to the use of Singletons in the + # code importing glance, nova, cinder at the same time crashes + # dramatically + spec_path = Path(args.openapi_yaml_spec) + metadata_path = Path(target_dir, args.service_type + "_metadata.yaml") + + schema = self.load_openapi(spec_path) + openapi_spec = common.get_openapi_spec(spec_path) + metadata = self.build_metadata( + schema, openapi_spec, args.service_type, spec_path.as_posix() + ) + + yaml = YAML() + yaml.preserve_quotes = True + yaml.default_flow_style = False + yaml.indent(mapping=2, sequence=4, offset=2) + metadata_path.parent.mkdir(exist_ok=True, parents=True) + with open(metadata_path, "w") as fp: + yaml.dump( + metadata.model_dump( + exclude_none=True, exclude_defaults=True, by_alias=True + ), + fp, + ) + + @staticmethod + def build_metadata( + schema, openapi_spec, service_type: str, spec_path: str + ) -> Metadata: + metadata = Metadata(resources={}) + api_ver = "v" + schema.info["version"].split(".")[0] + service_metadata: ty.Type[MetadataBase] = SERVICE_METADATA_MAP[ + service_type + ] + + for path, spec in schema.paths.items(): + path_elements: list[str] = path.split("/") + resource_name = "/".join( + list(common.get_resource_names_from_url(path)) + ) + if service_type == "object-store": + if path == "/v1/{account}": + resource_name = "account" + elif path == "/v1/{account}/{container}": + resource_name = "container" + if path == "/v1/{account}/{object}": + resource_name = "object" + + if service_type == "compute" and resource_name in [ + "agent", + "baremetal_node", + "cell", + "cell/capacity", + "cell/info", + "cell/sync_instance", + "certificate", + "cloudpipe", + "fping", + "fixed_ip", + "floating_ip_dns", + "floating_ip_dns/entry", + "floating_ip_pool", + "floating_ip_bulk", + "host", + "host/reboot", + "host/shutdown", + "host/startup", + "image", + "image/metadata", + "network", + "security_group_default_rule", + "security_group_rule", + "security_group", + "server/console", + "server/virtual_interface", + "snapshot", + "tenant_network", + "volume", + "volumes_boot", + ]: + # We do not need to produce anything for deprecated APIs + continue + resource_model = metadata.resources.setdefault( + f"{service_type}.{resource_name}", + ResourceModel( + api_version=api_ver, spec_file=spec_path, operations={} + ), + ) + for method in [ + "head", + "get", + "put", + "post", + "delete", + "options", + "patch", + ]: + operation = getattr(spec, method, None) + if operation: + if not operation.operationId: + # Every operation must have operationId + continue + if operation.operationId in OPERATION_ID_BLACKLIST: + # For blacklisted operationIds were are not producing anything + continue + + op_model = OperationModel( + operation_id=operation.operationId, targets={} + ) + operation_key: str | None = None + + response_schema: dict | None = None + for code, rsp in operation.responses.items(): + if code.startswith("2"): + response_schema = ( + rsp.get("content", {}) + .get("application/json", {}) + .get("schema", {}) + ) + break + # Try to get overrides from service metadata + operation_key, skip = service_metadata.get_operation_key( + operation, path, method, resource_name + ) + if skip: + continue + logging.debug( + f"Got {operation_key} as a key for {operation.operationId} as an override from service metadata processor" + ) + + if not operation_key: + if path.endswith("}"): + if method == "get": + operation_key = "show" + elif method == "head": + operation_key = "check" + elif method == "put": + operation_key = "update" + elif method == "patch": + if ( + "application/json" + in operation.requestBody.get("content", {}) + ): + operation_key = "update" + else: + operation_key = "patch" + elif method == "post": + operation_key = "create" + elif method == "delete": + operation_key = "delete" + elif ( + path.endswith("/detail") + and resource_name != "quota_set" + ): + if method == "get": + operation_key = "list_detailed" + elif response_schema and ( + method == "get" + and ( + response_schema.get("type", "") == "array" + or ( + response_schema.get("type", "") == "object" + and "properties" in response_schema + and len(path_elements) > 1 + and path_elements[-1] + in response_schema.get("properties", {}) + and response_schema.get("properties", {}) + .get(path_elements[-1], {}) + .get("type") + == "array" + ) + ) + ): + # Response looks clearly like a list + operation_key = "list" + elif path.endswith("/action"): + # Action + operation_key = "action" + elif ( + len( + [ + x + for x in schema.paths.keys() + if x.startswith(path + "/{") + ] + ) + > 0 + ): + # if we are at i.e. /v2/servers and there is + # /v2/servers/{ most likely we are at the collection + # level + if method == "get": + operation_key = "list" + elif method == "head": + operation_key = "check" + elif method == "patch": + if ( + "application/json" + in operation.requestBody.get("content", {}) + ): + operation_key = "update" + else: + operation_key = "patch" + elif method == "post": + operation_key = "create" + elif method == "put": + operation_key = "replace" + elif method == "delete": + operation_key = "delete_all" + elif method == "head": + operation_key = "check" + elif method == "get": + operation_key = "get" + elif method == "post": + operation_key = "create" + elif method == "put": + operation_key = path.split("/")[-1] + elif method == "patch": + if "application/json" in operation.requestBody.get( + "content", {} + ): + operation_key = "update" + else: + operation_key = "patch" + elif method == "delete": + operation_key = "delete" + if not operation_key: + logging.warning( + f"Cannot identify op name for {path}:{method}" + ) + + if operation_key in resource_model: + raise RuntimeError("Operation name conflict") + else: + if operation_key == "action" and service_type in [ + "compute", + "block-storage", + "shared-file-system", + ]: + # For action we actually have multiple independent operations + try: + body_schema = operation.requestBody["content"][ + "application/json" + ]["schema"] + bodies = body_schema.get( + "oneOf", [body_schema] + ) + if len(bodies) > 1: + discriminator = body_schema.get( + "x-openstack", {} + ).get("discriminator") + if discriminator != "action": + raise RuntimeError( + f"Cannot generate metadata for {path} since request body is not having action discriminator" + ) + for body in bodies: + action_name = body.get( + "x-openstack", {} + ).get("action-name") + if not action_name: + action_name = list( + body["properties"].keys() + )[0] + # Hardcode fixes + if ( + resource_name == "flavor" + and action_name + in ["update", "create", "delete"] + ): + # Flavor update/create/delete + # operations are exposed ALSO as wsgi + # actions. This is wrong and useless. + logging.warning( + "Skipping generating %s:%s action", + resource_name, + action_name, + ) + continue + + operation_name = "-".join( + x.lower() + for x in re.split( + common.SPLIT_NAME_RE, action_name + ) + ).lower() + rust_sdk_params = ( + get_rust_sdk_operation_args( + "action", + operation_name=action_name, + module_name=get_module_name( + action_name + ), + ) + ) + rust_cli_params = ( + get_rust_cli_operation_args( + "action", + operation_name=action_name, + module_name=get_module_name( + action_name + ), + resource_name=resource_name, + ) + ) + + op_model = OperationModel( + operation_id=operation.operationId, + targets={}, + ) + op_model.operation_type = "action" + + op_model.targets["rust-sdk"] = ( + rust_sdk_params + ) + op_model.targets["rust-cli"] = ( + rust_cli_params + ) + + op_model = post_process_operation( + service_type, + resource_name, + operation_name, + op_model, + ) + + resource_model.operations[ + operation_name + ] = op_model + + except KeyError: + raise RuntimeError( + f"Cannot get bodies for {path}" + ) + else: + if not operation_key: + raise NotImplementedError + operation_type = get_operation_type_by_key( + operation_key + ) + op_model.operation_type = operation_type + # NOTE: sdk gets operation_key and not operation_type + rust_sdk_params = get_rust_sdk_operation_args( + operation_key + ) + rust_cli_params = get_rust_cli_operation_args( + operation_key, resource_name=resource_name + ) + + op_model.targets["rust-sdk"] = rust_sdk_params + if rust_cli_params and not ( + service_type == "identity" + and operation_key == "check" + ): + op_model.targets["rust-cli"] = rust_cli_params + + op_model = post_process_operation( + service_type, + resource_name, + operation_key, + op_model, + ) + + resource_model.operations[operation_key] = op_model + for res_name, res_data in metadata.resources.items(): + # Sanitize produced metadata + list_op = res_data.operations.get("list") + list_detailed_op = res_data.operations.get("list_detailed") + if list_op and list_detailed_op: + # There are both plain list and list with details operation. + # For the certain generator backend it makes no sense to have + # then both so we should disable generation of certain backends + # for the non detailed endpoint + list_op.targets.pop("rust-cli") + + # Prepare `find` operation data + if (list_op or list_detailed_op) and res_data.operations.get( + "show" + ): + show_op = res_data.operations["show"] + + (path, _, spec) = common.find_openapi_operation( + openapi_spec, show_op.operation_id + ) + mod_path = common.get_rust_sdk_mod_path( + service_type, res_data.api_version or "", path + ) + response_schema = None + for code, rspec in spec.get("responses", {}).items(): + if not code.startswith("2"): + continue + content = rspec.get("content", {}) + if "application/json" in content: + try: + (response_schema, _) = common.find_resource_schema( + content["application/json"].get("schema", {}), + None, + ) + except Exception as ex: + logging.exception( + "Cannot process response of %s operation: %s", + show_op.operation_id, + ex, + ) + + if not response_schema: + # Show does not have a suitable + # response. We can't have find + # for such + continue + if "id" not in response_schema.get("properties", {}).keys(): + # Resource has no ID in show method => find impossible + continue + elif ( + "name" not in response_schema.get("properties", {}).keys() + and res_name != "floatingip" + ): + # Resource has no NAME => find useless + continue + + list_op_ = list_detailed_op or list_op + if not list_op_: + continue + (_, _, list_spec) = common.find_openapi_operation( + openapi_spec, list_op_.operation_id + ) + name_field: str = "name" + for fqan, alias in common.FQAN_ALIAS_MAP.items(): + if fqan.startswith(res_name) and alias == "name": + name_field = fqan.split(".")[-1] + name_filter_supported: bool = False + if name_field in [ + x.get("name") + for x in list(list_spec.get("parameters", [])) + ]: + name_filter_supported = True + + sdk_params = OperationTargetParams( + module_name="find", + name_field=name_field, + name_filter_supported=name_filter_supported, + sdk_mod_path="::".join(mod_path), + list_mod="list_detailed" if list_detailed_op else "list", + ) + res_data.operations["find"] = OperationModel( + operation_id=list_op_.operation_id, + operation_type="find", + targets={"rust-sdk": sdk_params}, + ) + + # Let other operations know of `find` presence + for op_name, op_data in res_data.operations.items(): + if op_name not in ["find", "list", "create"]: + for ( + target_name, + target_params, + ) in op_data.targets.items(): + if target_name in ["rust-cli"]: + target_params.find_implemented_by_sdk = True + + return metadata + + +def get_operation_type_by_key(operation_key): + if operation_key in ["list", "list_detailed"]: + return "list" + elif operation_key in ["get", "stats", "status"]: + return "get" + elif operation_key == "check": + return "get" + elif operation_key == "show": + return "show" + elif operation_key in ["update", "replace"]: + return "set" + elif operation_key in ["delete", "delete_all"]: + return "delete" + elif operation_key in ["create"]: + return "create" + elif operation_key == "patch": + return "set" + elif operation_key == "default": + return "show" + elif operation_key == "defaults": + return "show" + elif operation_key == "details": + return "show" + elif operation_key == "download": + return "download" + elif operation_key == "upload": + return "upload" + elif operation_key in ["associate", "disassociate", "disassociate_all"]: + # Cinder XXXssociate are GET actions not accepting body - crazy + return "get" + else: + return "action" + + +def get_rust_sdk_operation_args( + operation_key: str, + operation_name: str | None = None, + module_name: str | None = None, +): + """Construct proper Rust SDK parameters for operation by type""" + sdk_params = OperationTargetParams() + sdk_params.module_name = module_name + if operation_key == "show": + sdk_params.module_name = "get" + elif operation_key == "list_detailed": + sdk_params.module_name = "list_detailed" + # elif operation_key == "action" and not module_name: + # sdk_params.module_name = operation_name if operation_name else operation_key + else: + sdk_params.module_name = module_name or get_module_name( + # get_operation_type_by_key(operation_key) + operation_key + ) + sdk_params.operation_name = operation_name + + return sdk_params + + +def get_rust_cli_operation_args( + operation_key: str, + operation_name: str | None = None, + module_name: str | None = None, + resource_name: str | None = None, +): + """Construct proper Rust CLI parameters for operation by type""" + # Get SDK params to connect things with each other + # operation_type = get_operation_type_by_key(operation_key) + sdk_params = get_rust_sdk_operation_args( + operation_key, operation_name=operation_name, module_name=module_name + ) + cli_params = OperationTargetParams() + cli_params.sdk_mod_name = sdk_params.module_name + cli_params.module_name = module_name or get_module_name(operation_key) + cli_params.operation_name = operation_name + if resource_name: + op_name = cli_params.module_name + if op_name.startswith("os_") or op_name.startswith("os-"): + op_name = op_name[3:] + op_name = op_name.replace("_", "-") + + cli_params.cli_full_command = ( + " ".join(x for x in resource_name.split("/")).replace("_", "-") + + " " + + op_name + ) + + return cli_params + + +def get_module_name(name): + if name in ["list", "list_detailed"]: + return "list" + elif name == "get": + return "get" + elif name == "show": + return "show" + elif name == "check": + return "head" + elif name == "update": + return "set" + elif name == "replace": + return "replace" + elif name == "delete": + return "delete" + elif name == "delete_all": + return "delete_all" + elif name in ["create"]: + return "create" + elif name in ["default"]: + return "default" + return "_".join(x.lower() for x in re.split(common.SPLIT_NAME_RE, name)) + + +def post_process_operation( + service_type: str, resource_name: str, operation_name: str, operation +): + service_metadata = SERVICE_METADATA_MAP[service_type] + operation = service_metadata.post_process_operation( + resource_name, operation_name, operation + ) + + return operation diff --git a/codegenerator/metadata/baremetal.py b/codegenerator/metadata/baremetal.py new file mode 100644 index 0000000..2e8bcde --- /dev/null +++ b/codegenerator/metadata/baremetal.py @@ -0,0 +1,33 @@ +# 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. +# +import typing as ty + +from codegenerator.types import OperationModel +from codegenerator.metadata.base import MetadataBase + + +class BaremetalMetadata(MetadataBase): + @staticmethod + def get_operation_key( + operation, path: str, method: str, resource_name: str + ) -> ty.Tuple[str | None, bool]: + skip: bool = False + operation_key: str | None = None + + return (operation_key, skip) + + @staticmethod + def post_process_operation( + resource_name: str, operation_name: str, operation + ): + return operation diff --git a/codegenerator/metadata/base.py b/codegenerator/metadata/base.py new file mode 100644 index 0000000..65b24e6 --- /dev/null +++ b/codegenerator/metadata/base.py @@ -0,0 +1,31 @@ +# 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 abc import ABC, abstractmethod +import typing as ty + +from codegenerator.types import OperationModel + + +class MetadataBase(ABC): + @staticmethod + @abstractmethod + def get_operation_key( + operation, path: str, method: str, resource_name: str + ) -> ty.Tuple[str | None, bool]: ... + + @staticmethod + @abstractmethod + def post_process_operation( + resource_name: str, operation_name: str, operation: OperationModel + ) -> OperationModel: ... diff --git a/codegenerator/metadata/block_storage.py b/codegenerator/metadata/block_storage.py new file mode 100644 index 0000000..87de21f --- /dev/null +++ b/codegenerator/metadata/block_storage.py @@ -0,0 +1,135 @@ +# 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. +# +import typing as ty + +from codegenerator.types import OperationModel +from codegenerator.metadata.base import MetadataBase + + +class BlockStorageMetadata(MetadataBase): + @staticmethod + def get_operation_key( + operation, path: str, method: str, resource_name: str + ) -> ty.Tuple[str | None, bool]: + skip: bool = False + operation_key: str | None = None + path_elements: list[str] = path.split("/") + + if "volume-transfer" in path and path.endswith("/accept"): + operation_key = "accept" + elif "qos-specs" in path and path_elements[-1] in [ + "associate", + "disassociate", + "disassociate_all", + "delete_keys", + ]: + operation_key = path_elements[-1] + elif resource_name == "quota_set" and path.endswith("defaults"): + operation_key = "defaults" + + elif resource_name == "limit" and method == "get": + operation_key = "list" + + return (operation_key, skip) + + @staticmethod + def post_process_operation( + resource_name: str, operation_name: str, operation + ): + if resource_name == "type": + if operation_name == "list": + operation.targets["rust-cli"].response_key = "volume_types" + operation.targets["rust-sdk"].response_key = "volume_types" + elif operation_name in ["create", "show", "update"]: + operation.targets["rust-cli"].response_key = "volume_type" + operation.targets["rust-sdk"].response_key = "volume_type" + elif resource_name == "type/volume_type_access": + operation.targets["rust-cli"].response_key = "volume_type_access" + operation.targets["rust-sdk"].response_key = "volume_type_access" + + if "/tag" in resource_name: + if operation_name == "update": + operation.targets[ + "rust-cli" + ].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("set", "add") + elif operation_name == "show": + operation.targets[ + "rust-cli" + ].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("show", "check") + + if resource_name == "snapshot": + if "update-snapshot-status" in operation_name: + operation.targets[ + "rust-cli" + ].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace( + "update-snapshot-status", "update-status" + ) + + if resource_name in ["os_volume_transfer", "volume_transfer"]: + if operation_name in ["list", "list_detailed"]: + operation.targets["rust-cli"].response_key = "transfers" + operation.targets["rust-sdk"].response_key = "transfers" + elif operation_name in ["accept", "create", "show"]: + operation.targets["rust-cli"].response_key = "transfer" + + if resource_name == "availability_zone": + if operation_name == "get": + operation.operation_type = "list" + operation.targets["rust-sdk"].operation_name = "list" + operation.targets[ + "rust-sdk" + ].response_key = "availabilityZoneInfo" + operation.targets["rust-sdk"].module_name = "list" + operation.targets[ + "rust-cli" + ].response_key = "availabilityZoneInfo" + operation.targets["rust-cli"].module_name = "list" + operation.targets["rust-cli"].sdk_mod_name = "list" + operation.targets["rust-cli"].operation_name = "list" + operation.targets[ + "rust-sdk" + ].response_key = "availabilityZoneInfo" + operation.targets[ + "rust-cli" + ].cli_full_command = "availability-zone list" + if resource_name == "qos_spec/association": + operation.operation_type = "list" + operation.targets["rust-sdk"].operation_name = "list" + operation.targets["rust-sdk"].module_name = "list" + operation.targets["rust-cli"].operation_name = "list" + operation.targets["rust-cli"].module_name = "list" + operation.targets["rust-cli"].sdk_mod_name = "list" + operation.targets["rust-sdk"].response_key = "qos_associations" + operation.targets["rust-cli"].response_key = "qos_associations" + operation.targets[ + "rust-cli" + ].cli_full_command = "qos-spec association list" + + if resource_name == "limit": + # Limits API return object and not a list + operation.targets["rust-cli"].operation_type = "show" + operation.targets["rust-cli"].response_key = "limits" + operation.targets["rust-sdk"].response_key = "limits" + + if operation_name == "delete_all": + operation.targets["rust-cli"].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("delete-all", "purge") + + return operation diff --git a/codegenerator/metadata/compute.py b/codegenerator/metadata/compute.py new file mode 100644 index 0000000..66ed12e --- /dev/null +++ b/codegenerator/metadata/compute.py @@ -0,0 +1,195 @@ +# 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. +# + +import typing as ty + +from codegenerator.types import OperationModel +from codegenerator.metadata.base import MetadataBase + + +class ComputeMetadata(MetadataBase): + @staticmethod + def get_operation_key( + operation, path: str, method: str, resource_name: str + ) -> ty.Tuple[str | None, bool]: + skip: bool = False + operation_key: str | None = None + path_elements: list[str] = path.split("/") + + if resource_name == "flavor/flavor_access" and method == "get": + operation_key = "list" + elif resource_name == "aggregate/image" and method == "post": + operation_key = "action" + elif resource_name == "server/security_group" and method == "get": + operation_key = "list" + elif resource_name == "server/topology" and method == "get": + operation_key = "list" + elif resource_name == "quota_set" and path.endswith("defaults"): + operation_key = "defaults" + elif resource_name == "quota_set" and path.endswith("detail"): + # normalize "details" name + operation_key = "details" + + elif resource_name == "limit" and method == "get": + operation_key = "list" + + return (operation_key, skip) + + @staticmethod + def post_process_operation( + resource_name: str, operation_name: str, operation + ): + if resource_name == "aggregate": + if operation_name in ["set-metadata", "add-host", "remove-host"]: + operation.targets["rust-sdk"].response_key = "aggregate" + operation.targets["rust-cli"].response_key = "aggregate" + elif resource_name == "availability_zone": + if operation_name == "get": + operation.operation_type = "list" + operation.targets["rust-sdk"].operation_name = "list" + operation.targets[ + "rust-sdk" + ].response_key = "availabilityZoneInfo" + operation.targets["rust-sdk"].module_name = "list" + operation.targets[ + "rust-cli" + ].response_key = "availabilityZoneInfo" + operation.targets["rust-cli"].module_name = "list" + operation.targets["rust-cli"].sdk_mod_name = "list" + operation.targets["rust-cli"].operation_name = "list" + operation.targets[ + "rust-sdk" + ].response_key = "availabilityZoneInfo" + operation.targets[ + "rust-cli" + ].cli_full_command = "availability-zone list" + elif operation_name == "list_detailed": + operation.operation_type = "list" + operation.targets["rust-sdk"].operation_name = "list_detail" + operation.targets[ + "rust-sdk" + ].response_key = "availabilityZoneInfo" + operation.targets["rust-sdk"].module_name = "list_detail" + operation.targets[ + "rust-cli" + ].response_key = "availabilityZoneInfo" + operation.targets["rust-cli"].operation_name = "list" + operation.targets["rust-cli"].module_name = "list_detail" + operation.targets["rust-cli"].sdk_mod_name = "list_detail" + operation.targets[ + "rust-cli" + ].cli_full_command = "availability-zone list-detail" + + elif resource_name == "keypair": + if operation_name == "list": + operation.targets[ + "rust-sdk" + ].response_list_item_key = "keypair" + + elif resource_name == "server": + if "migrate-live" in operation_name: + operation.targets[ + "rust-cli" + ].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("migrate-live", "live-migrate") + elif resource_name == "server/instance_action": + if operation_name == "list": + operation.targets["rust-sdk"].response_key = "instanceActions" + operation.targets["rust-cli"].response_key = "instanceActions" + else: + operation.targets["rust-sdk"].response_key = "instanceAction" + operation.targets["rust-cli"].response_key = "instanceAction" + elif resource_name == "server/topology": + if operation_name == "list": + operation.targets["rust-sdk"].response_key = "nodes" + operation.targets["rust-cli"].response_key = "nodes" + elif resource_name == "server/volume_attachment": + if operation_name == "list": + operation.targets[ + "rust-sdk" + ].response_key = "volumeAttachments" + operation.targets[ + "rust-cli" + ].response_key = "volumeAttachments" + elif operation_name in ["create", "show", "update"]: + operation.targets["rust-sdk"].response_key = "volumeAttachment" + operation.targets["rust-cli"].response_key = "volumeAttachment" + elif resource_name == "server/server_password": + operation.targets["rust-cli"].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("server-password", "password") + operation.targets["rust-cli"].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("get", "show") + elif resource_name == "server/security_group": + operation.targets["rust-cli"].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace( + "security-group list", "security-groups" + ) + if operation_name == "get": + operation.targets[ + "rust-cli" + ].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("get", "show") + + elif resource_name == "flavor": + if operation_name == "add-tenant-access": + operation.targets[ + "rust-cli" + ].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("add-tenant-access", "access add") + elif operation_name == "list-tenant-access": + operation.targets[ + "rust-cli" + ].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("list-tenant-access", "access list") + elif operation_name == "remove-tenant-access": + operation.targets[ + "rust-cli" + ].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace( + "remove-tenant-access", "access remove" + ) + + if resource_name == "limit": + # Limits API return object and not a list + operation.targets["rust-cli"].operation_type = "show" + operation.targets["rust-cli"].response_key = "limits" + operation.targets["rust-sdk"].response_key = "limits" + + if "/tag" in resource_name: + if operation_name == "update": + operation.targets[ + "rust-cli" + ].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("set", "add") + elif operation_name == "show": + operation.targets[ + "rust-cli" + ].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("show", "check") + + if operation_name == "delete_all": + operation.targets["rust-cli"].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("delete-all", "purge") + + return operation diff --git a/codegenerator/metadata/dns.py b/codegenerator/metadata/dns.py new file mode 100644 index 0000000..2eaed72 --- /dev/null +++ b/codegenerator/metadata/dns.py @@ -0,0 +1,48 @@ +# 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. +# + +import typing as ty + +from codegenerator.types import OperationModel +from codegenerator.metadata.base import MetadataBase + + +class DnsMetadata(MetadataBase): + @staticmethod + def get_operation_key( + operation, path: str, method: str, resource_name: str + ) -> ty.Tuple[str | None, bool]: + skip: bool = False + operation_key: str | None = None + path_elements: list[str] = path.split("/") + + if resource_name == "zone/task": + if path == "/v2/zones/{zone_id}/tasks/xfr": + operation_key = "xfr" + if path == "/v2/zones/{zone_id}/tasks/abandon": + operation_key = "abandon" + if path == "/v2/zones/{zone_id}/tasks/pool_move": + operation_key = "pool_move" + elif resource_name == "quota" and path == "/v2/quotas": + # /quotas return quota for current project and not + # a "list" for all projects. As such it has no + # difference to show + skip = True + + return (operation_key, skip) + + @staticmethod + def post_process_operation( + resource_name: str, operation_name: str, operation + ): + return operation diff --git a/codegenerator/metadata/identity.py b/codegenerator/metadata/identity.py new file mode 100644 index 0000000..0771b69 --- /dev/null +++ b/codegenerator/metadata/identity.py @@ -0,0 +1,143 @@ +# 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. +# + +import typing as ty + +from codegenerator.types import OperationModel +from codegenerator.metadata.base import MetadataBase + + +class IdentityMetadata(MetadataBase): + @staticmethod + def get_operation_key( + operation, path: str, method: str, resource_name: str + ) -> ty.Tuple[str | None, bool]: + skip: bool = False + operation_key: str | None = None + + if path == "/v3/users/{user_id}/password": + if method == "post": + operation_key = "update" + + if resource_name in [ + "OS_FEDERATION/identity_provider", + "OS_FEDERATION/identity_provider/protocol", + "OS_FEDERATION/mapping", + "OS_FEDERATION/service_provider", + ]: + if method == "put": + operation_key = "create" + elif method == "patch": + operation_key = "update" + if ( + resource_name + in [ + "domain/config", + "domain/config/group", + "domain/config/group/option", + ] + and path.endswith("/default") + and method == "get" + ): + operation_key = "default" + + if ( + resource_name + in [ + "domain/config", + "domain/config/group", + "domain/config/group/option", + ] + and path.endswith("/default") + and method == "head" + ): + # No need in HEAD defaults + skip = True + return (operation_key, skip) + + @staticmethod + def post_process_operation( + resource_name: str, operation_name: str, operation + ): + if resource_name == "role/imply": + if operation_name == "list": + operation.targets["rust-cli"].response_key = "role_inference" + operation.targets["rust-sdk"].response_key = "role_inference" + if resource_name == "role_inference": + if operation_name == "list": + operation.targets["rust-cli"].response_key = "role_inferences" + operation.targets["rust-sdk"].response_key = "role_inferences" + + if resource_name == "domain/config/group": + operation.targets["rust-sdk"].response_key = "config" + if "rust-cli" in operation.targets: + operation.targets["rust-cli"].response_key = "config" + elif resource_name == "domain/config/group/option": + operation.targets["rust-sdk"].response_key = "config" + if "rust-cli" in operation.targets: + operation.targets["rust-cli"].response_key = "config" + + if "rust-cli" in operation.targets: + if "OS_FEDERATION" in resource_name: + operation.targets[ + "rust-cli" + ].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("OS-FEDERATION", "federation") + if "OS_EP_FILTER" in resource_name: + operation.targets[ + "rust-cli" + ].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("OS-EP-FILTER", "endpoint-filter") + elif resource_name == "user/project": + operation.targets[ + "rust-cli" + ].cli_full_command = "user projects" + elif resource_name == "user/group": + operation.targets["rust-cli"].cli_full_command = "user groups" + elif resource_name == "user/access_rule": + operation.targets[ + "rust-cli" + ].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("user access-rule", "access-rule") + elif resource_name == "user/application_credential": + operation.targets[ + "rust-cli" + ].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace( + "user application-credential", "application-credential" + ) + + if "/tag" in resource_name: + if operation_name == "update": + operation.targets[ + "rust-cli" + ].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("set", "add") + elif operation_name == "show": + operation.targets[ + "rust-cli" + ].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("show", "check") + + if operation_name == "delete_all": + operation.targets["rust-cli"].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("delete-all", "purge") + + return operation diff --git a/codegenerator/metadata/image.py b/codegenerator/metadata/image.py new file mode 100644 index 0000000..c5618e3 --- /dev/null +++ b/codegenerator/metadata/image.py @@ -0,0 +1,106 @@ +# 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. +# + +import typing as ty + +from codegenerator.types import OperationModel +from codegenerator.metadata.base import MetadataBase + + +class ImageMetadata(MetadataBase): + @staticmethod + def get_operation_key( + operation, path: str, method: str, resource_name: str + ) -> ty.Tuple[str | None, bool]: + skip: bool = False + operation_key: str | None = None + path_elements: list[str] = path.split("/") + + if path == "/v2/images/{image_id}/file": + if method == "put": + operation_key = "upload" + elif method == "get": + operation_key = "download" + else: + raise NotImplementedError + elif path.endswith("/actions/deactivate"): + operation_key = "deactivate" + elif path.endswith("/actions/reactivate"): + operation_key = "reactivate" + + return (operation_key, skip) + + @staticmethod + def post_process_operation( + resource_name: str, operation_name: str, operation + ): + if resource_name.startswith("schema"): + # Image schemas are a JSON operation + operation.targets["rust-cli"].operation_type = "json" + operation.targets["rust-cli"].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("get", "show") + elif resource_name == "metadef/namespace" and operation_name != "list": + operation.targets["rust-sdk"].response_key = "null" + operation.targets["rust-cli"].response_key = "null" + elif ( + resource_name == "metadef/namespace/property" + and operation_name == "list" + ): + operation.targets["rust-cli"].operation_type = "list_from_struct" + operation.targets["rust-cli"].response_key = "properties" + operation.targets["rust-sdk"].response_key = "properties" + elif resource_name == "metadef/namespace/resource_type": + operation.targets[ + "rust-cli" + ].response_key = "resource_type_associations" + operation.targets[ + "rust-sdk" + ].response_key = "resource_type_associations" + operation.targets["rust-cli"].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace( + "resource-type", "resource-type-association" + ) + elif resource_name == "image": + if operation_name == "patch": + operation.targets[ + "rust-cli" + ].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("patch", "set") + elif resource_name == "image/file": + operation.targets["rust-cli"].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("file ", "") + + if "/tag" in resource_name: + if operation_name == "update": + operation.targets[ + "rust-cli" + ].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("set", "add") + elif operation_name == "show": + operation.targets[ + "rust-cli" + ].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("show", "check") + + if operation_name == "delete_all": + operation.targets["rust-cli"].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("delete-all", "purge") + + return operation diff --git a/codegenerator/metadata/load_balancer.py b/codegenerator/metadata/load_balancer.py new file mode 100644 index 0000000..26df9d6 --- /dev/null +++ b/codegenerator/metadata/load_balancer.py @@ -0,0 +1,43 @@ +# 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. +# + +import typing as ty + +from codegenerator.types import OperationModel +from codegenerator.metadata.base import MetadataBase + + +class LoadBalancerMetadata(MetadataBase): + @staticmethod + def get_operation_key( + operation, path: str, method: str, resource_name: str + ) -> ty.Tuple[str | None, bool]: + skip: bool = False + operation_key: str | None = None + path_elements: list[str] = path.split("/") + + if len(path_elements) > 1 and path_elements[-1] in [ + "stats", + "status", + "failover", + "config", + ]: + operation_key = path_elements[-1] + + return (operation_key, skip) + + @staticmethod + def post_process_operation( + resource_name: str, operation_name: str, operation + ): + return operation diff --git a/codegenerator/metadata/network.py b/codegenerator/metadata/network.py new file mode 100644 index 0000000..279077b --- /dev/null +++ b/codegenerator/metadata/network.py @@ -0,0 +1,89 @@ +# 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. +# + +import typing as ty + +from codegenerator.types import OperationModel +from codegenerator.metadata.base import MetadataBase + + +class NetworkMetadata(MetadataBase): + @staticmethod + def get_operation_key( + operation, path: str, method: str, resource_name: str + ) -> ty.Tuple[str | None, bool]: + skip: bool = False + operation_key: str | None = None + path_elements: list[str] = path.split("/") + + if "quota" in path and path.endswith("/default"): + # normalize "defaults" name + operation_key = "defaults" + elif "quota" in path and path.endswith("/details"): + operation_key = "details" + + return (operation_key, skip) + + @staticmethod + def post_process_operation( + resource_name: str, operation_name: str, operation + ): + if resource_name.startswith("floatingip"): + operation.targets["rust-cli"].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("floatingip", "floating-ip") + + if resource_name == "router": + if "external_gateways" in operation_name: + operation.targets[ + "rust-cli" + ].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace( + "external-gateways", "external-gateway" + ) + elif "extraroutes" in operation_name: + operation.targets[ + "rust-cli" + ].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("extraroutes", "extraroute") + + if resource_name == "address_group": + if "addresses" in operation_name: + operation.targets[ + "rust-cli" + ].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("addresses", "address") + + if "/tag" in resource_name: + if operation_name == "update": + operation.targets[ + "rust-cli" + ].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("set", "add") + elif operation_name == "show": + operation.targets[ + "rust-cli" + ].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("show", "check") + + if operation_name == "delete_all": + operation.targets["rust-cli"].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("delete-all", "purge") + + return operation diff --git a/codegenerator/metadata/object_store.py b/codegenerator/metadata/object_store.py new file mode 100644 index 0000000..368bdb7 --- /dev/null +++ b/codegenerator/metadata/object_store.py @@ -0,0 +1,91 @@ +# 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. +# + +import typing as ty + +from codegenerator.types import OperationModel +from codegenerator.metadata.base import MetadataBase + + +class ObjectStorageMetadata(MetadataBase): + @staticmethod + def get_operation_key( + operation, path: str, method: str, resource_name: str + ) -> ty.Tuple[str | None, bool]: + skip: bool = False + operation_key: str | None = None + path_elements: list[str] = path.split("/") + + if resource_name == "object": + mapping_obj: dict[str, str] = { + "head": "head", + "get": "get", + "delete": "delete", + "put": "put", + "post": "update", + } + operation_key = mapping_obj[method] + elif resource_name == "container": + mapping_cont: dict[str, str] = { + "head": "head", + "get": "get", + "delete": "delete", + "put": "create", + "post": "update", + } + operation_key = mapping_cont[method] + elif resource_name == "account": + mapping_account: dict[str, str] = { + "head": "head", + "get": "get", + "delete": "delete", + "put": "create", + "post": "update", + } + operation_key = mapping_account[method] + + return (operation_key, skip) + + @staticmethod + def post_process_operation( + resource_name: str, operation_name: str, operation + ): + if resource_name == "account": + if operation_name == "get": + operation.targets[ + "rust-cli" + ].cli_full_command = "container list" + elif operation_name == "head": + operation.targets["rust-cli"].cli_full_command = "account show" + elif resource_name == "container": + if operation_name == "get": + operation.targets["rust-cli"].cli_full_command = "object list" + elif operation_name == "head": + operation.targets[ + "rust-cli" + ].cli_full_command = "container show" + elif resource_name == "object": + if operation_name == "get": + operation.targets[ + "rust-cli" + ].cli_full_command = "object download" + operation.operation_type = "download" + elif operation_name == "head": + operation.targets["rust-cli"].cli_full_command = "object show" + elif operation_name == "put": + operation.targets[ + "rust-cli" + ].cli_full_command = "object upload" + operation.operation_type = "upload" + + return operation diff --git a/codegenerator/metadata/placement.py b/codegenerator/metadata/placement.py new file mode 100644 index 0000000..8c98821 --- /dev/null +++ b/codegenerator/metadata/placement.py @@ -0,0 +1,69 @@ +# 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. +# + +import typing as ty + +from codegenerator.types import OperationModel +from codegenerator.metadata.base import MetadataBase + + +class PlacementMetadata(MetadataBase): + @staticmethod + def get_operation_key( + operation, path: str, method: str, resource_name: str + ) -> ty.Tuple[str | None, bool]: + skip: bool = False + operation_key: str | None = None + path_elements: list[str] = path.split("/") + + if ( + resource_name + in [ + "allocation_candidate", + "resource_provider/allocation", + "usage", + ] + and method == "get" + ): + operation_key = "list" + elif ( + resource_name + in ["resource_provider/aggregate", "resource_provider/trait"] + and method == "put" + ): + operation_key = "update" + + return (operation_key, skip) + + @staticmethod + def post_process_operation( + resource_name: str, operation_name: str, operation + ): + if resource_name == "allocation_candidate": + if operation_name == "list": + operation.operation_type = "show" + elif resource_name == "resource_provider/aggregate": + if operation_name == "list": + operation.operation_type = "show" + elif resource_name == "resource_provider/allocation": + if operation_name == "list": + operation.operation_type = "show" + elif resource_name == "resource_provider/trait": + if operation_name == "list": + operation.operation_type = "show" + if operation_name == "delete_all": + operation.targets["rust-cli"].cli_full_command = operation.targets[ + "rust-cli" + ].cli_full_command.replace("delete-all", "purge") + + return operation diff --git a/codegenerator/metadata/shared_file_system.py b/codegenerator/metadata/shared_file_system.py new file mode 100644 index 0000000..26aa32f --- /dev/null +++ b/codegenerator/metadata/shared_file_system.py @@ -0,0 +1,33 @@ +# 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. +# +import typing as ty + +from codegenerator.types import OperationModel +from codegenerator.metadata.base import MetadataBase + + +class SharedFileSystemMetadata(MetadataBase): + @staticmethod + def get_operation_key( + operation, path: str, method: str, resource_name: str + ) -> ty.Tuple[str | None, bool]: + skip: bool = False + operation_key: str | None = None + + return (operation_key, skip) + + @staticmethod + def post_process_operation( + resource_name: str, operation_name: str, operation + ): + return operation diff --git a/metadata/block-storage_metadata.yaml b/metadata/block-storage_metadata.yaml index ef14dc9..71eeb4d 100644 --- a/metadata/block-storage_metadata.yaml +++ b/metadata/block-storage_metadata.yaml @@ -135,19 +135,6 @@ resources: operation_name: os-extend_volume_completion find_implemented_by_sdk: true cli_full_command: volume extend-volume-completion - os-unmanage: - operation_id: volumes/id/action:post - operation_type: action - targets: - rust-sdk: - module_name: os_unmanage - operation_name: os-unmanage - rust-cli: - module_name: os_unmanage - sdk_mod_name: os_unmanage - operation_name: os-unmanage - find_implemented_by_sdk: true - cli_full_command: volume unmanage os-attach: operation_id: volumes/id/action:post operation_type: action @@ -330,6 +317,19 @@ resources: operation_name: os-reimage find_implemented_by_sdk: true cli_full_command: volume reimage + os-unmanage: + operation_id: volumes/id/action:post + operation_type: action + targets: + rust-sdk: + module_name: os_unmanage + operation_name: os-unmanage + rust-cli: + module_name: os_unmanage + sdk_mod_name: os_unmanage + operation_name: os-unmanage + find_implemented_by_sdk: true + cli_full_command: volume unmanage os-set-image-metadata: operation_id: volumes/id/action:post operation_type: action @@ -985,19 +985,6 @@ resources: module_name: create sdk_mod_name: create cli_full_command: snapshot create - os-update-snapshot-status: - operation_id: snapshots/id/action:post - operation_type: action - targets: - rust-sdk: - module_name: os_update_snapshot_status - operation_name: os-update_snapshot_status - rust-cli: - module_name: os_update_snapshot_status - sdk_mod_name: os_update_snapshot_status - operation_name: os-update_snapshot_status - find_implemented_by_sdk: true - cli_full_command: snapshot update-status os-reset-status: operation_id: snapshots/id/action:post operation_type: action @@ -1037,6 +1024,19 @@ resources: operation_name: os-unmanage find_implemented_by_sdk: true cli_full_command: snapshot unmanage + os-update-snapshot-status: + operation_id: snapshots/id/action:post + operation_type: action + targets: + rust-sdk: + module_name: os_update_snapshot_status + operation_name: os-update_snapshot_status + rust-cli: + module_name: os_update_snapshot_status + sdk_mod_name: os_update_snapshot_status + operation_name: os-update_snapshot_status + find_implemented_by_sdk: true + cli_full_command: snapshot update-status show: operation_id: snapshots/id:get operation_type: show @@ -1090,10 +1090,12 @@ resources: targets: rust-sdk: module_name: list + response_key: limits rust-cli: module_name: list sdk_mod_name: list operation_type: show + response_key: limits cli_full_command: limit list block-storage.snapshot/metadata: spec_file: wrk/openapi_specs/block-storage/v3.yaml @@ -1689,6 +1691,216 @@ resources: module_name: list sdk_mod_name: list cli_full_command: extension list + block-storage.capability: + spec_file: wrk/openapi_specs/block-storage/v3.yaml + api_version: v3 + operations: + show: + operation_id: capabilities/id:get + operation_type: show + targets: + rust-sdk: + module_name: get + rust-cli: + module_name: show + sdk_mod_name: get + cli_full_command: capability show + block-storage.type/extra_spec: + spec_file: wrk/openapi_specs/block-storage/v3.yaml + api_version: v3 + operations: + list: + operation_id: types/type_id/extra_specs:get + operation_type: list + targets: + rust-sdk: + module_name: list + rust-cli: + module_name: list + sdk_mod_name: list + cli_full_command: type extra-spec list + create: + operation_id: types/type_id/extra_specs:post + operation_type: create + targets: + rust-sdk: + module_name: create + rust-cli: + module_name: create + sdk_mod_name: create + cli_full_command: type extra-spec create + show: + operation_id: types/type_id/extra_specs/id:get + operation_type: show + targets: + rust-sdk: + module_name: get + rust-cli: + module_name: show + sdk_mod_name: get + cli_full_command: type extra-spec show + update: + operation_id: types/type_id/extra_specs/id:put + operation_type: set + targets: + rust-sdk: + module_name: set + rust-cli: + module_name: set + sdk_mod_name: set + cli_full_command: type extra-spec set + delete: + operation_id: types/type_id/extra_specs/id:delete + operation_type: delete + targets: + rust-sdk: + module_name: delete + rust-cli: + module_name: delete + sdk_mod_name: delete + cli_full_command: type extra-spec delete + block-storage.availability_zone: + spec_file: wrk/openapi_specs/block-storage/v3.yaml + api_version: v3 + operations: + get: + operation_id: os-availability-zone:get + operation_type: list + targets: + rust-sdk: + module_name: list + operation_name: list + response_key: availabilityZoneInfo + rust-cli: + module_name: list + sdk_mod_name: list + operation_name: list + response_key: availabilityZoneInfo + cli_full_command: availability-zone list + block-storage.volume_manage: + spec_file: wrk/openapi_specs/block-storage/v3.yaml + api_version: v3 + operations: + list_detailed: + operation_id: os-volume-manage/detail:get + operation_type: list + targets: + rust-sdk: + module_name: list_detailed + rust-cli: + module_name: list + sdk_mod_name: list_detailed + cli_full_command: volume-manage list + get: + operation_id: os-volume-manage:get + operation_type: get + targets: + rust-sdk: + module_name: get + rust-cli: + module_name: get + sdk_mod_name: get + cli_full_command: volume-manage get + create: + operation_id: os-volume-manage:post + operation_type: create + targets: + rust-sdk: + module_name: create + rust-cli: + module_name: create + sdk_mod_name: create + cli_full_command: volume-manage create + block-storage.cgsnapshot: + spec_file: wrk/openapi_specs/block-storage/v3.yaml + api_version: v3 + operations: + list_detailed: + operation_id: cgsnapshots/detail:get + operation_type: list + targets: + rust-sdk: + module_name: list_detailed + rust-cli: + module_name: list + sdk_mod_name: list_detailed + cli_full_command: cgsnapshot list + list: + operation_id: cgsnapshots:get + operation_type: list + targets: + rust-sdk: + module_name: list + create: + operation_id: cgsnapshots:post + operation_type: create + targets: + rust-sdk: + module_name: create + rust-cli: + module_name: create + sdk_mod_name: create + cli_full_command: cgsnapshot create + show: + operation_id: cgsnapshots/id:get + operation_type: show + targets: + rust-sdk: + module_name: get + rust-cli: + module_name: show + sdk_mod_name: get + cli_full_command: cgsnapshot show + delete: + operation_id: cgsnapshots/id:delete + operation_type: delete + targets: + rust-sdk: + module_name: delete + rust-cli: + module_name: delete + sdk_mod_name: delete + cli_full_command: cgsnapshot delete + block-storage.type/volume_type_access: + spec_file: wrk/openapi_specs/block-storage/v3.yaml + api_version: v3 + operations: + get: + operation_id: types/type_id/os-volume-type-access:get + operation_type: get + targets: + rust-sdk: + module_name: get + response_key: volume_type_access + rust-cli: + module_name: get + sdk_mod_name: get + response_key: volume_type_access + cli_full_command: type volume-type-access get + block-storage.service: + spec_file: wrk/openapi_specs/block-storage/v3.yaml + api_version: v3 + operations: + list: + operation_id: os-services:get + operation_type: list + targets: + rust-sdk: + module_name: list + rust-cli: + module_name: list + sdk_mod_name: list + cli_full_command: service list + update: + operation_id: os-services/id:put + operation_type: set + targets: + rust-sdk: + module_name: set + rust-cli: + module_name: set + sdk_mod_name: set + cli_full_command: service set block-storage.type/encryption: spec_file: wrk/openapi_specs/block-storage/v3.yaml api_version: v3 @@ -1743,40 +1955,74 @@ resources: module_name: delete sdk_mod_name: delete cli_full_command: type encryption delete - block-storage.snapshot_manage: + block-storage.quota_class_set: spec_file: wrk/openapi_specs/block-storage/v3.yaml api_version: v3 operations: - list_detailed: - operation_id: os-snapshot-manage/detail:get - operation_type: list - targets: - rust-sdk: - module_name: list_detailed - rust-cli: - module_name: list - sdk_mod_name: list_detailed - cli_full_command: snapshot-manage list - get: - operation_id: os-snapshot-manage:get - operation_type: get + show: + operation_id: os-quota-class-sets/id:get + operation_type: show targets: rust-sdk: module_name: get rust-cli: - module_name: get + module_name: show sdk_mod_name: get - cli_full_command: snapshot-manage get - create: - operation_id: os-snapshot-manage:post - operation_type: create + cli_full_command: quota-class-set show + update: + operation_id: os-quota-class-sets/id:put + operation_type: set targets: rust-sdk: - module_name: create + module_name: set rust-cli: - module_name: create - sdk_mod_name: create - cli_full_command: snapshot-manage create + module_name: set + sdk_mod_name: set + cli_full_command: quota-class-set set + block-storage.quota_set: + spec_file: wrk/openapi_specs/block-storage/v3.yaml + api_version: v3 + operations: + defaults: + operation_id: os-quota-sets/id/defaults:get + operation_type: show + targets: + rust-sdk: + module_name: defaults + rust-cli: + module_name: defaults + sdk_mod_name: defaults + cli_full_command: quota-set defaults + show: + operation_id: os-quota-sets/id:get + operation_type: show + targets: + rust-sdk: + module_name: get + rust-cli: + module_name: show + sdk_mod_name: get + cli_full_command: quota-set show + update: + operation_id: os-quota-sets/id:put + operation_type: set + targets: + rust-sdk: + module_name: set + rust-cli: + module_name: set + sdk_mod_name: set + cli_full_command: quota-set set + delete: + operation_id: os-quota-sets/id:delete + operation_type: delete + targets: + rust-sdk: + module_name: delete + rust-cli: + module_name: delete + sdk_mod_name: delete + cli_full_command: quota-set delete block-storage.qos_spec: spec_file: wrk/openapi_specs/block-storage/v3.yaml api_version: v3 @@ -1906,12 +2152,12 @@ resources: operation_name: list response_key: qos_associations cli_full_command: qos-spec association list - block-storage.cgsnapshot: + block-storage.snapshot_manage: spec_file: wrk/openapi_specs/block-storage/v3.yaml api_version: v3 operations: list_detailed: - operation_id: cgsnapshots/detail:get + operation_id: os-snapshot-manage/detail:get operation_type: list targets: rust-sdk: @@ -1919,15 +2165,19 @@ resources: rust-cli: module_name: list sdk_mod_name: list_detailed - cli_full_command: cgsnapshot list - list: - operation_id: cgsnapshots:get - operation_type: list + cli_full_command: snapshot-manage list + get: + operation_id: os-snapshot-manage:get + operation_type: get targets: rust-sdk: - module_name: list + module_name: get + rust-cli: + module_name: get + sdk_mod_name: get + cli_full_command: snapshot-manage get create: - operation_id: cgsnapshots:post + operation_id: os-snapshot-manage:post operation_type: create targets: rust-sdk: @@ -1935,49 +2185,13 @@ resources: rust-cli: module_name: create sdk_mod_name: create - cli_full_command: cgsnapshot create - show: - operation_id: cgsnapshots/id:get - operation_type: show - targets: - rust-sdk: - module_name: get - rust-cli: - module_name: show - sdk_mod_name: get - cli_full_command: cgsnapshot show - delete: - operation_id: cgsnapshots/id:delete - operation_type: delete - targets: - rust-sdk: - module_name: delete - rust-cli: - module_name: delete - sdk_mod_name: delete - cli_full_command: cgsnapshot delete - block-storage.type/volume_type_access: - spec_file: wrk/openapi_specs/block-storage/v3.yaml - api_version: v3 - operations: - get: - operation_id: types/type_id/os-volume-type-access:get - operation_type: get - targets: - rust-sdk: - module_name: get - response_key: volume_type_access - rust-cli: - module_name: get - sdk_mod_name: get - response_key: volume_type_access - cli_full_command: type volume-type-access get - block-storage.type/extra_spec: + cli_full_command: snapshot-manage create + block-storage.host: spec_file: wrk/openapi_specs/block-storage/v3.yaml api_version: v3 operations: list: - operation_id: types/type_id/extra_specs:get + operation_id: os-hosts:get operation_type: list targets: rust-sdk: @@ -1985,19 +2199,9 @@ resources: rust-cli: module_name: list sdk_mod_name: list - cli_full_command: type extra-spec list - create: - operation_id: types/type_id/extra_specs:post - operation_type: create - targets: - rust-sdk: - module_name: create - rust-cli: - module_name: create - sdk_mod_name: create - cli_full_command: type extra-spec create + cli_full_command: host list show: - operation_id: types/type_id/extra_specs/id:get + operation_id: os-hosts/id:get operation_type: show targets: rust-sdk: @@ -2005,27 +2209,7 @@ resources: rust-cli: module_name: show sdk_mod_name: get - cli_full_command: type extra-spec show - update: - operation_id: types/type_id/extra_specs/id:put - operation_type: set - targets: - rust-sdk: - module_name: set - rust-cli: - module_name: set - sdk_mod_name: set - cli_full_command: type extra-spec set - delete: - operation_id: types/type_id/extra_specs/id:delete - operation_type: delete - targets: - rust-sdk: - module_name: delete - rust-cli: - module_name: delete - sdk_mod_name: delete - cli_full_command: type extra-spec delete + cli_full_command: host show block-storage.backup/import_record: spec_file: wrk/openapi_specs/block-storage/v3.yaml api_version: v3 @@ -2068,40 +2252,6 @@ resources: module_name: get sdk_mod_name: get cli_full_command: backup export-record get - block-storage.volume_manage: - spec_file: wrk/openapi_specs/block-storage/v3.yaml - api_version: v3 - operations: - list_detailed: - operation_id: os-volume-manage/detail:get - operation_type: list - targets: - rust-sdk: - module_name: list_detailed - rust-cli: - module_name: list - sdk_mod_name: list_detailed - cli_full_command: volume-manage list - get: - operation_id: os-volume-manage:get - operation_type: get - targets: - rust-sdk: - module_name: get - rust-cli: - module_name: get - sdk_mod_name: get - cli_full_command: volume-manage get - create: - operation_id: os-volume-manage:post - operation_type: create - targets: - rust-sdk: - module_name: create - rust-cli: - module_name: create - sdk_mod_name: create - cli_full_command: volume-manage create block-storage.os_volume_transfer: spec_file: wrk/openapi_specs/block-storage/v3.yaml api_version: v3 @@ -2182,24 +2332,44 @@ resources: name_field: name name_filter_supported: false list_mod: list_detailed - block-storage.availability_zone: + block-storage.scheduler_stat/get_pool: spec_file: wrk/openapi_specs/block-storage/v3.yaml api_version: v3 operations: get: - operation_id: os-availability-zone:get + operation_id: scheduler-stats/get_pools:get + operation_type: get + targets: + rust-sdk: + module_name: get + rust-cli: + module_name: get + sdk_mod_name: get + cli_full_command: scheduler-stat get-pool get + block-storage.volume/encryption: + spec_file: wrk/openapi_specs/block-storage/v3.yaml + api_version: v3 + operations: + list: + operation_id: volumes/volume_id/encryption:get operation_type: list targets: rust-sdk: module_name: list - operation_name: list - response_key: availabilityZoneInfo rust-cli: module_name: list sdk_mod_name: list - operation_name: list - response_key: availabilityZoneInfo - cli_full_command: availability-zone list + cli_full_command: volume encryption list + show: + operation_id: volumes/volume_id/encryption/id:get + operation_type: show + targets: + rust-sdk: + module_name: get + rust-cli: + module_name: show + sdk_mod_name: get + cli_full_command: volume encryption show block-storage.consistencygroup/create_from_src: spec_file: wrk/openapi_specs/block-storage/v3.yaml api_version: v3 @@ -2228,175 +2398,3 @@ resources: module_name: create sdk_mod_name: create cli_full_command: consistencygroup delete create - block-storage.quota_class_set: - spec_file: wrk/openapi_specs/block-storage/v3.yaml - api_version: v3 - operations: - show: - operation_id: os-quota-class-sets/id:get - operation_type: show - targets: - rust-sdk: - module_name: get - rust-cli: - module_name: show - sdk_mod_name: get - cli_full_command: quota-class-set show - update: - operation_id: os-quota-class-sets/id:put - operation_type: set - targets: - rust-sdk: - module_name: set - rust-cli: - module_name: set - sdk_mod_name: set - cli_full_command: quota-class-set set - block-storage.host: - spec_file: wrk/openapi_specs/block-storage/v3.yaml - api_version: v3 - operations: - list: - operation_id: os-hosts:get - operation_type: list - targets: - rust-sdk: - module_name: list - rust-cli: - module_name: list - sdk_mod_name: list - cli_full_command: host list - show: - operation_id: os-hosts/id:get - operation_type: show - targets: - rust-sdk: - module_name: get - rust-cli: - module_name: show - sdk_mod_name: get - cli_full_command: host show - block-storage.volume/encryption: - spec_file: wrk/openapi_specs/block-storage/v3.yaml - api_version: v3 - operations: - list: - operation_id: volumes/volume_id/encryption:get - operation_type: list - targets: - rust-sdk: - module_name: list - rust-cli: - module_name: list - sdk_mod_name: list - cli_full_command: volume encryption list - show: - operation_id: volumes/volume_id/encryption/id:get - operation_type: show - targets: - rust-sdk: - module_name: get - rust-cli: - module_name: show - sdk_mod_name: get - cli_full_command: volume encryption show - block-storage.capability: - spec_file: wrk/openapi_specs/block-storage/v3.yaml - api_version: v3 - operations: - show: - operation_id: capabilities/id:get - operation_type: show - targets: - rust-sdk: - module_name: get - rust-cli: - module_name: show - sdk_mod_name: get - cli_full_command: capability show - block-storage.scheduler_stat/get_pool: - spec_file: wrk/openapi_specs/block-storage/v3.yaml - api_version: v3 - operations: - get: - operation_id: scheduler-stats/get_pools:get - operation_type: get - targets: - rust-sdk: - module_name: get - rust-cli: - module_name: get - sdk_mod_name: get - cli_full_command: scheduler-stat get-pool get - block-storage.service: - spec_file: wrk/openapi_specs/block-storage/v3.yaml - api_version: v3 - operations: - list: - operation_id: os-services:get - operation_type: list - targets: - rust-sdk: - module_name: list - rust-cli: - module_name: list - sdk_mod_name: list - cli_full_command: service list - update: - operation_id: os-services/id:put - operation_type: set - targets: - rust-sdk: - module_name: set - rust-cli: - module_name: set - sdk_mod_name: set - cli_full_command: service set - block-storage.quota_set/default: - spec_file: wrk/openapi_specs/block-storage/v3.yaml - api_version: v3 - operations: - get: - operation_id: os-quota-sets/id/defaults:get - operation_type: get - targets: - rust-sdk: - module_name: get - rust-cli: - module_name: get - sdk_mod_name: get - cli_full_command: quota-set default get - block-storage.quota_set: - spec_file: wrk/openapi_specs/block-storage/v3.yaml - api_version: v3 - operations: - show: - operation_id: os-quota-sets/id:get - operation_type: show - targets: - rust-sdk: - module_name: get - rust-cli: - module_name: show - sdk_mod_name: get - cli_full_command: quota-set show - update: - operation_id: os-quota-sets/id:put - operation_type: set - targets: - rust-sdk: - module_name: set - rust-cli: - module_name: set - sdk_mod_name: set - cli_full_command: quota-set set - delete: - operation_id: os-quota-sets/id:delete - operation_type: delete - targets: - rust-sdk: - module_name: delete - rust-cli: - module_name: delete - sdk_mod_name: delete - cli_full_command: quota-set delete diff --git a/metadata/identity_metadata.yaml b/metadata/identity_metadata.yaml index 0be7586..dfe65a6 100644 --- a/metadata/identity_metadata.yaml +++ b/metadata/identity_metadata.yaml @@ -504,7 +504,7 @@ resources: cli_full_command: domain config set default: operation_id: domains/config/default:get - operation_type: get + operation_type: show targets: rust-sdk: module_name: default @@ -561,7 +561,7 @@ resources: cli_full_command: domain config group set default: operation_id: domains/config/group/default:get - operation_type: get + operation_type: show targets: rust-sdk: module_name: default @@ -620,7 +620,7 @@ resources: cli_full_command: domain config group option set default: operation_id: domains/config/group/option/default:get - operation_type: get + operation_type: show targets: rust-sdk: module_name: default @@ -968,11 +968,31 @@ resources: api_version: v3 operations: check: - operation_id: limits:head + operation_id: limits/limit_id:head operation_type: get targets: rust-sdk: module_name: head + list: + operation_id: limits:get + operation_type: list + targets: + rust-sdk: + module_name: list + rust-cli: + module_name: list + sdk_mod_name: list + cli_full_command: limit list + create: + operation_id: limits:post + operation_type: create + targets: + rust-sdk: + module_name: create + rust-cli: + module_name: create + sdk_mod_name: create + cli_full_command: limit create show: operation_id: limits/limit_id:get operation_type: show @@ -1003,26 +1023,6 @@ resources: module_name: set sdk_mod_name: set cli_full_command: limit set - list: - operation_id: limits:get - operation_type: list - targets: - rust-sdk: - module_name: list - rust-cli: - module_name: list - sdk_mod_name: list - cli_full_command: limit list - create: - operation_id: limits:post - operation_type: create - targets: - rust-sdk: - module_name: create - rust-cli: - module_name: create - sdk_mod_name: create - cli_full_command: limit create identity.limit/model: spec_file: wrk/openapi_specs/identity/v3.yaml api_version: v3 @@ -1062,7 +1062,7 @@ resources: rust-cli: module_name: show sdk_mod_name: get - cli_full_command: OS-EP-FILTER endpoint-group show + cli_full_command: endpoint-filter endpoint-group show delete: operation_id: OS-EP-FILTER/endpoint_groups/endpoint_group_id:delete operation_type: delete @@ -1072,7 +1072,7 @@ resources: rust-cli: module_name: delete sdk_mod_name: delete - cli_full_command: OS-EP-FILTER endpoint-group delete + cli_full_command: endpoint-filter endpoint-group delete update: operation_id: OS-EP-FILTER/endpoint_groups/endpoint_group_id:patch operation_type: set @@ -1082,7 +1082,7 @@ resources: rust-cli: module_name: set sdk_mod_name: set - cli_full_command: OS-EP-FILTER endpoint-group set + cli_full_command: endpoint-filter endpoint-group set list: operation_id: OS-EP-FILTER/endpoint_groups:get operation_type: list @@ -1092,7 +1092,7 @@ resources: rust-cli: module_name: list sdk_mod_name: list - cli_full_command: OS-EP-FILTER endpoint-group list + cli_full_command: endpoint-filter endpoint-group list create: operation_id: OS-EP-FILTER/endpoint_groups:post operation_type: create @@ -1102,7 +1102,7 @@ resources: rust-cli: module_name: create sdk_mod_name: create - cli_full_command: OS-EP-FILTER endpoint-group create + cli_full_command: endpoint-filter endpoint-group create identity.OS_EP_FILTER/endpoint/project: spec_file: wrk/openapi_specs/identity/v3.yaml api_version: v3 @@ -1122,7 +1122,7 @@ resources: rust-cli: module_name: get sdk_mod_name: get - cli_full_command: OS-EP-FILTER endpoint project get + cli_full_command: endpoint-filter endpoint project get identity.OS_EP_FILTER/project/endpoint: spec_file: wrk/openapi_specs/identity/v3.yaml api_version: v3 @@ -1142,7 +1142,7 @@ resources: rust-cli: module_name: show sdk_mod_name: get - cli_full_command: OS-EP-FILTER project endpoint show + cli_full_command: endpoint-filter project endpoint show update: operation_id: OS-EP-FILTER/projects/project_id/endpoints/endpoint_id:put operation_type: set @@ -1152,7 +1152,7 @@ resources: rust-cli: module_name: set sdk_mod_name: set - cli_full_command: OS-EP-FILTER project endpoint set + cli_full_command: endpoint-filter project endpoint set delete: operation_id: OS-EP-FILTER/projects/project_id/endpoints/endpoint_id:delete operation_type: delete @@ -1162,7 +1162,7 @@ resources: rust-cli: module_name: delete sdk_mod_name: delete - cli_full_command: OS-EP-FILTER project endpoint delete + cli_full_command: endpoint-filter project endpoint delete list: operation_id: OS-EP-FILTER/projects/project_id/endpoints:get operation_type: list @@ -1172,7 +1172,7 @@ resources: rust-cli: module_name: list sdk_mod_name: list - cli_full_command: OS-EP-FILTER project endpoint list + cli_full_command: endpoint-filter project endpoint list identity.OS_EP_FILTER/project/endpoint_group: spec_file: wrk/openapi_specs/identity/v3.yaml api_version: v3 @@ -1192,7 +1192,7 @@ resources: rust-cli: module_name: get sdk_mod_name: get - cli_full_command: OS-EP-FILTER project endpoint-group get + cli_full_command: endpoint-filter project endpoint-group get identity.OS_EP_FILTER/endpoint_group/endpoint: spec_file: wrk/openapi_specs/identity/v3.yaml api_version: v3 @@ -1212,7 +1212,7 @@ resources: rust-cli: module_name: get sdk_mod_name: get - cli_full_command: OS-EP-FILTER endpoint-group endpoint get + cli_full_command: endpoint-filter endpoint-group endpoint get identity.OS_EP_FILTER/endpoint_group/project: spec_file: wrk/openapi_specs/identity/v3.yaml api_version: v3 @@ -1232,7 +1232,7 @@ resources: rust-cli: module_name: list sdk_mod_name: list - cli_full_command: OS-EP-FILTER endpoint-group project list + cli_full_command: endpoint-filter endpoint-group project list show: operation_id: OS-EP-FILTER/endpoint_groups/endpoint_group_id/projects/project_id:get operation_type: show @@ -1242,7 +1242,7 @@ resources: rust-cli: module_name: show sdk_mod_name: get - cli_full_command: OS-EP-FILTER endpoint-group project show + cli_full_command: endpoint-filter endpoint-group project show update: operation_id: OS-EP-FILTER/endpoint_groups/endpoint_group_id/projects/project_id:put operation_type: set @@ -1252,7 +1252,7 @@ resources: rust-cli: module_name: set sdk_mod_name: set - cli_full_command: OS-EP-FILTER endpoint-group project set + cli_full_command: endpoint-filter endpoint-group project set delete: operation_id: OS-EP-FILTER/endpoint_groups/endpoint_group_id/projects/project_id:delete operation_type: delete @@ -1262,7 +1262,7 @@ resources: rust-cli: module_name: delete sdk_mod_name: delete - cli_full_command: OS-EP-FILTER endpoint-group project delete + cli_full_command: endpoint-filter endpoint-group project delete identity.OS_FEDERATION/saml2/metadata: spec_file: wrk/openapi_specs/identity/v3.yaml api_version: v3 @@ -2175,11 +2175,31 @@ resources: api_version: v3 operations: check: - operation_id: projects:head + operation_id: projects/project_id:head operation_type: get targets: rust-sdk: module_name: head + list: + operation_id: projects:get + operation_type: list + targets: + rust-sdk: + module_name: list + rust-cli: + module_name: list + sdk_mod_name: list + cli_full_command: project list + create: + operation_id: projects:post + operation_type: create + targets: + rust-sdk: + module_name: create + rust-cli: + module_name: create + sdk_mod_name: create + cli_full_command: project create show: operation_id: projects/project_id:get operation_type: show @@ -2213,26 +2233,6 @@ resources: sdk_mod_name: set find_implemented_by_sdk: true cli_full_command: project set - list: - operation_id: projects:get - operation_type: list - targets: - rust-sdk: - module_name: list - rust-cli: - module_name: list - sdk_mod_name: list - cli_full_command: project list - create: - operation_id: projects:post - operation_type: create - targets: - rust-sdk: - module_name: create - rust-cli: - module_name: create - sdk_mod_name: create - cli_full_command: project create find: operation_id: projects:get operation_type: find diff --git a/metadata/image_metadata.yaml b/metadata/image_metadata.yaml index 1f560ce..10fb415 100644 --- a/metadata/image_metadata.yaml +++ b/metadata/image_metadata.yaml @@ -719,6 +719,30 @@ resources: module_name: stage sdk_mod_name: stage cli_full_command: image stage stage + image.image/location: + spec_file: wrk/openapi_specs/image/v2.yaml + api_version: v2 + operations: + list: + operation_id: images/image_id/locations:get + operation_type: list + targets: + rust-sdk: + module_name: list + rust-cli: + module_name: list + sdk_mod_name: list + cli_full_command: image location list + create: + operation_id: images/image_id/locations:post + operation_type: create + targets: + rust-sdk: + module_name: create + rust-cli: + module_name: create + sdk_mod_name: create + cli_full_command: image location create image.image/tag: spec_file: wrk/openapi_specs/image/v2.yaml api_version: v2 diff --git a/metadata/load-balancer_metadata.yaml b/metadata/load-balancer_metadata.yaml index 509be77..26c4161 100644 --- a/metadata/load-balancer_metadata.yaml +++ b/metadata/load-balancer_metadata.yaml @@ -111,7 +111,7 @@ resources: module_name: find sdk_mod_path: load_balancer::v2::loadbalancer name_field: name - name_filter_supported: false + name_filter_supported: true list_mod: list load-balancer.listener: spec_file: wrk/openapi_specs/load-balancer/v2.yaml @@ -189,7 +189,7 @@ resources: module_name: find sdk_mod_path: load_balancer::v2::listener name_field: name - name_filter_supported: false + name_filter_supported: true list_mod: list load-balancer.pool: spec_file: wrk/openapi_specs/load-balancer/v2.yaml @@ -256,7 +256,7 @@ resources: module_name: find sdk_mod_path: load_balancer::v2::pool name_field: name - name_filter_supported: false + name_filter_supported: true list_mod: list load-balancer.l7policy: spec_file: wrk/openapi_specs/load-balancer/v2.yaml @@ -323,7 +323,7 @@ resources: module_name: find sdk_mod_path: load_balancer::v2::l7policy name_field: name - name_filter_supported: false + name_filter_supported: true list_mod: list load-balancer.healthmonitor: spec_file: wrk/openapi_specs/load-balancer/v2.yaml @@ -390,7 +390,7 @@ resources: module_name: find sdk_mod_path: load_balancer::v2::healthmonitor name_field: name - name_filter_supported: false + name_filter_supported: true list_mod: list load-balancer.quota: spec_file: wrk/openapi_specs/load-balancer/v2.yaml @@ -515,7 +515,7 @@ resources: module_name: find sdk_mod_path: load_balancer::v2::flavor name_field: name - name_filter_supported: false + name_filter_supported: true list_mod: list load-balancer.flavor_profile: spec_file: wrk/openapi_specs/load-balancer/v2.yaml @@ -582,7 +582,7 @@ resources: module_name: find sdk_mod_path: load_balancer::v2::flavor_profile name_field: name - name_filter_supported: false + name_filter_supported: true list_mod: list load-balancer.availability_zone: spec_file: wrk/openapi_specs/load-balancer/v2.yaml @@ -703,7 +703,7 @@ resources: module_name: find sdk_mod_path: load_balancer::v2::availability_zone_profile name_field: name - name_filter_supported: false + name_filter_supported: true list_mod: list load-balancer.octavia: spec_file: wrk/openapi_specs/load-balancer/v2.yaml @@ -941,5 +941,5 @@ resources: module_name: find sdk_mod_path: load_balancer::v2::pool::member name_field: name - name_filter_supported: false + name_filter_supported: true list_mod: list