diff --git a/codegenerator/cli.py b/codegenerator/cli.py index e4419ee..e32b679 100644 --- a/codegenerator/cli.py +++ b/codegenerator/cli.py @@ -32,6 +32,7 @@ from codegenerator.openapi_spec import OpenApiSchemaGenerator # from codegenerator.osc import OSCGenerator from codegenerator.rust_cli import RustCliGenerator +from codegenerator.rust_tui import RustTuiGenerator from codegenerator.rust_sdk import RustSdkGenerator from codegenerator.types import Metadata @@ -157,6 +158,7 @@ def main(): "ansible", "rust-sdk", "rust-cli", + "rust-tui", "openapi-spec", "jsonschema", "metadata", @@ -196,8 +198,9 @@ def main(): generators = { # "osc": OSCGenerator(), # "ansible": AnsibleGenerator(), - "rust-sdk": RustSdkGenerator(), "rust-cli": RustCliGenerator(), + "rust-tui": RustTuiGenerator(), + "rust-sdk": RustSdkGenerator(), "openapi-spec": OpenApiSchemaGenerator(), "jsonschema": JsonSchemaGenerator(), "metadata": MetadataGenerator(), @@ -269,7 +272,7 @@ def main(): ) ) - if args.target == "rust-sdk" and not args.resource: + if args.target in ["rust-sdk", "rust-tui"] and not args.resource: resource_results: dict[str, dict] = {} for mod_path, mod_name, path in res_mods: mn = "/".join(mod_path) @@ -300,7 +303,7 @@ def main(): x["mods"].add(mod_name) for path, gen_data in resource_results.items(): - generators["rust-sdk"].generate_mod( + generators[args.target].generate_mod( args.work_dir, path.split("/"), gen_data["mods"], diff --git a/codegenerator/metadata/block_storage.py b/codegenerator/metadata/block_storage.py index 87de21f..011ae48 100644 --- a/codegenerator/metadata/block_storage.py +++ b/codegenerator/metadata/block_storage.py @@ -13,6 +13,7 @@ import typing as ty from codegenerator.types import OperationModel +from codegenerator.types import OperationTargetParams from codegenerator.metadata.base import MetadataBase @@ -132,4 +133,20 @@ class BlockStorageMetadata(MetadataBase): "rust-cli" ].cli_full_command.replace("delete-all", "purge") + if resource_name in ["backup", "snapshot", "volume"]: + if operation_name in ["list", "delete"]: + operation.targets.setdefault( + "rust-tui", + OperationTargetParams( + module_name=operation.targets["rust-sdk"].module_name + ), + ) + if resource_name == "quota_set" and operation_name == "details": + operation.targets.setdefault( + "rust-tui", + OperationTargetParams( + module_name=operation.targets["rust-sdk"].module_name + ), + ) + return operation diff --git a/codegenerator/metadata/compute.py b/codegenerator/metadata/compute.py index 66ed12e..a2811cd 100644 --- a/codegenerator/metadata/compute.py +++ b/codegenerator/metadata/compute.py @@ -14,6 +14,7 @@ import typing as ty from codegenerator.types import OperationModel +from codegenerator.types import OperationTargetParams from codegenerator.metadata.base import MetadataBase @@ -192,4 +193,27 @@ class ComputeMetadata(MetadataBase): "rust-cli" ].cli_full_command.replace("delete-all", "purge") + if resource_name in [ + "aggregate", + "flavor", + "hypervisor", + "server", + "server/instance_action", + ]: + if operation_name in ["list", "delete", "show"]: + operation.targets.setdefault( + "rust-tui", + OperationTargetParams( + module_name=operation.targets["rust-sdk"].module_name + ), + ) + + if resource_name == "quota_set" and operation_name == "details": + operation.targets.setdefault( + "rust-tui", + OperationTargetParams( + module_name=operation.targets["rust-sdk"].module_name + ), + ) + return operation diff --git a/codegenerator/metadata/dns.py b/codegenerator/metadata/dns.py index 2eaed72..eb279fa 100644 --- a/codegenerator/metadata/dns.py +++ b/codegenerator/metadata/dns.py @@ -14,6 +14,7 @@ import typing as ty from codegenerator.types import OperationModel +from codegenerator.types import OperationTargetParams from codegenerator.metadata.base import MetadataBase @@ -45,4 +46,12 @@ class DnsMetadata(MetadataBase): def post_process_operation( resource_name: str, operation_name: str, operation ): + if resource_name in ["zone", "recordset", "zone/recordset"]: + if operation_name in ["list", "delete"]: + operation.targets.setdefault( + "rust-tui", + OperationTargetParams( + module_name=operation.targets["rust-sdk"].module_name + ), + ) return operation diff --git a/codegenerator/metadata/identity.py b/codegenerator/metadata/identity.py index 0771b69..92eb8ba 100644 --- a/codegenerator/metadata/identity.py +++ b/codegenerator/metadata/identity.py @@ -14,6 +14,7 @@ import typing as ty from codegenerator.types import OperationModel +from codegenerator.types import OperationTargetParams from codegenerator.metadata.base import MetadataBase @@ -140,4 +141,19 @@ class IdentityMetadata(MetadataBase): "rust-cli" ].cli_full_command.replace("delete-all", "purge") + if resource_name in [ + "auth/project", + "group", + "group/user", + "project", + "user", + "user/application_credential", + ]: + if operation_name in ["list", "delete"]: + operation.targets.setdefault( + "rust-tui", + OperationTargetParams( + module_name=operation.targets["rust-sdk"].module_name + ), + ) return operation diff --git a/codegenerator/metadata/image.py b/codegenerator/metadata/image.py index c5618e3..17e1698 100644 --- a/codegenerator/metadata/image.py +++ b/codegenerator/metadata/image.py @@ -14,6 +14,7 @@ import typing as ty from codegenerator.types import OperationModel +from codegenerator.types import OperationTargetParams from codegenerator.metadata.base import MetadataBase @@ -103,4 +104,12 @@ class ImageMetadata(MetadataBase): "rust-cli" ].cli_full_command.replace("delete-all", "purge") + if resource_name in ["image"]: + if operation_name in ["list", "delete"]: + operation.targets.setdefault( + "rust-tui", + OperationTargetParams( + module_name=operation.targets["rust-sdk"].module_name + ), + ) return operation diff --git a/codegenerator/metadata/load_balancer.py b/codegenerator/metadata/load_balancer.py index 26df9d6..9ff5f2f 100644 --- a/codegenerator/metadata/load_balancer.py +++ b/codegenerator/metadata/load_balancer.py @@ -14,6 +14,7 @@ import typing as ty from codegenerator.types import OperationModel +from codegenerator.types import OperationTargetParams from codegenerator.metadata.base import MetadataBase @@ -40,4 +41,19 @@ class LoadBalancerMetadata(MetadataBase): def post_process_operation( resource_name: str, operation_name: str, operation ): + if resource_name in [ + "healthmonitor", + "loadbalancer", + "listener", + "quota", + "pool", + "pool/member", + ]: + if operation_name in ["list", "delete", "show"]: + operation.targets.setdefault( + "rust-tui", + OperationTargetParams( + module_name=operation.targets["rust-sdk"].module_name + ), + ) return operation diff --git a/codegenerator/metadata/network.py b/codegenerator/metadata/network.py index 279077b..df0a8ce 100644 --- a/codegenerator/metadata/network.py +++ b/codegenerator/metadata/network.py @@ -14,6 +14,7 @@ import typing as ty from codegenerator.types import OperationModel +from codegenerator.types import OperationTargetParams from codegenerator.metadata.base import MetadataBase @@ -86,4 +87,25 @@ class NetworkMetadata(MetadataBase): "rust-cli" ].cli_full_command.replace("delete-all", "purge") + if resource_name in [ + "network", + "router", + "security_group", + "security_group_rule", + "subnet", + ]: + if operation_name in ["list", "delete"]: + operation.targets.setdefault( + "rust-tui", + OperationTargetParams( + module_name=operation.targets["rust-sdk"].module_name + ), + ) + if resource_name == "quota" and operation_name == "details": + operation.targets.setdefault( + "rust-tui", + OperationTargetParams( + module_name=operation.targets["rust-sdk"].module_name + ), + ) return operation diff --git a/codegenerator/rust_tui.py b/codegenerator/rust_tui.py new file mode 100644 index 0000000..be0eacd --- /dev/null +++ b/codegenerator/rust_tui.py @@ -0,0 +1,343 @@ +# 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 logging +from pathlib import Path +import re +import subprocess +from typing import Type, Any + +from codegenerator.base import BaseGenerator +from codegenerator import common +from codegenerator import model +from codegenerator.common import BaseCompoundType +from codegenerator.common import rust as common_rust +from codegenerator.rust_sdk import TypeManager as SdkTypeManager + + +class String(common_rust.String): + type_hint: str = "String" + + +class TypeManager(common_rust.TypeManager): + """Rust SDK type manager + + The class is responsible for converting ADT models into types suitable + for Rust (SDK). + + """ + + primitive_type_mapping: dict[Type[model.PrimitiveType], Type[Any]] = { + model.PrimitiveString: String, + model.ConstraintString: String, + } + + data_type_mapping = {model.Struct: common_rust.Struct} + + request_parameter_class: Type[common_rust.RequestParameter] = ( + common_rust.RequestParameter + ) + + def get_local_attribute_name(self, name: str) -> str: + """Get localized attribute name""" + name = name.replace(".", "_") + attr_name = "_".join( + x.lower() for x in re.split(common.SPLIT_NAME_RE, name) + ) + if attr_name in ["type", "self", "enum", "ref", "default"]: + attr_name = f"_{attr_name}" + return attr_name + + def get_remote_attribute_name(self, name: str) -> str: + """Get the attribute name on the SDK side""" + return self.get_local_attribute_name(name) + + +class RustTuiGenerator(BaseGenerator): + def __init__(self): + super().__init__() + + def _format_code(self, *args): + """Format code using Rustfmt + + :param *args: Path to the code to format + """ + for path in args: + subprocess.run(["rustfmt", "--edition", "2021", path]) + + def _render_command( + self, context: dict, impl_template: str, impl_dest: Path + ): + """Render command code""" + self._render(impl_template, context, impl_dest.parent, impl_dest.name) + + def generate( + self, res, target_dir, openapi_spec=None, operation_id=None, args=None + ): + """Generate code for the Rust openstack_tui""" + logging.debug( + "Generating Rust TUI code for %s in %s [%s]", + operation_id, + target_dir, + args, + ) + + if not openapi_spec: + openapi_spec = common.get_openapi_spec(args.openapi_yaml_spec) + if not operation_id: + operation_id = args.openapi_operation_id + (path, method, spec) = common.find_openapi_operation( + openapi_spec, operation_id + ) + + # srv_name, res_name = res.split(".") if res else (None, None) + path_resources = common.get_resource_names_from_url(path) + res_name = path_resources[-1] + + mime_type = None + openapi_parser = model.OpenAPISchemaParser() + operation_params: list[model.RequestParameter] = [] + type_manager: TypeManager | None = None + sdk_type_manager: SdkTypeManager | None = None + is_json_patch: bool = False + # Collect all operation parameters + for param in openapi_spec["paths"][path].get( + "parameters", [] + ) + spec.get("parameters", []): + if ( + ("{" + param["name"] + "}") in path and param["in"] == "path" + ) or param["in"] != "path": + # Respect path params that appear in path and not path params + param_ = openapi_parser.parse_parameter(param) + if param_.name in [ + f"{res_name}_id", + f"{res_name.replace('_', '')}_id", + ]: + path = path.replace(param_.name, "id") + # for i.e. routers/{router_id} we want local_name to be `id` and not `router_id` + param_.name = "id" + operation_params.append(param_) + + # Process body information + # List of operation variants (based on the body) + operation_variants = common.get_operation_variants( + spec, args.operation_name + ) + + api_ver_matches: re.Match | None = None + path_elements = path.lstrip("/").split("/") + api_ver: dict[str, int] = {} + ver_prefix: str | None = None + is_list_paginated = False + if path_elements: + api_ver_matches = re.match(common.VERSION_RE, path_elements[0]) + if api_ver_matches and api_ver_matches.groups(): + # Remember the version prefix to discard it in the template + ver_prefix = path_elements[0] + + for operation_variant in operation_variants: + logging.debug(f"Processing variant {operation_variant}") + # TODO(gtema): if we are in MV variants filter out unsupported query + # parameters + # TODO(gtema): previously we were ensuring `router_id` path param + # is renamed to `id` + + if api_ver_matches: + api_ver = { + "major": api_ver_matches.group(1), + "minor": api_ver_matches.group(3) or 0, + } + else: + api_ver = {} + + service_name = common.get_rust_service_type_from_str( + args.service_type + ) + class_name = f"{service_name}{res_name.title()}{args.operation_type.title()}".replace( + "_", "" + ) + operation_body = operation_variant.get("body") + type_manager = TypeManager() + sdk_type_manager = SdkTypeManager() + type_manager.set_parameters(operation_params) + sdk_type_manager.set_parameters(operation_params) + mod_name = "_".join( + x.lower() + for x in re.split( + common.SPLIT_NAME_RE, + ( + args.module_name + or args.operation_name + or args.operation_type.value + or method + ), + ) + ) + + if operation_body: + min_ver = operation_body.get("x-openstack", {}).get("min-ver") + if min_ver: + mod_name += "_" + min_ver.replace(".", "") + v = min_ver.split(".") + if not len(v) == 2: + raise RuntimeError( + "Version information is not in format MAJOR.MINOR" + ) + api_ver = {"major": v[0], "minor": v[1]} + + # There is request body. Get the ADT from jsonschema + # if args.operation_type != "action": + (_, all_types) = openapi_parser.parse( + operation_body, ignore_read_only=True + ) + # and feed them into the TypeManager + type_manager.set_models(all_types) + sdk_type_manager.set_models(all_types) + # else: + # logging.warn("Ignoring response type of action") + + if method == "patch": + # There might be multiple supported mime types. We only select ones we are aware of + mime_type = operation_variant.get("mime_type") + if not mime_type: + raise RuntimeError( + "No supported mime types for patch operation found" + ) + if mime_type != "application/json": + is_json_patch = True + + mod_path = common.get_rust_sdk_mod_path( + args.service_type, + args.api_version, + args.alternative_module_path or path, + ) + + response_key: str | None = None + if args.response_key: + response_key = ( + args.response_key if args.response_key != "null" else None + ) + else: + # Get basic information about response + if method.upper() != "HEAD": + for code, rspec in spec["responses"].items(): + if not code.startswith("2"): + continue + content = rspec.get("content", {}) + if "application/json" in content: + response_spec = content["application/json"] + try: + (_, response_key) = ( + common.find_resource_schema( + response_spec["schema"], + None, + res_name.lower(), + ) + ) + except Exception: + # Most likely we have response which is oneOf. + # For the SDK it does not really harm to ignore + # this. + pass + # response_def = (None,) + response_key = None + sdk_mod_path_base = common.get_rust_sdk_mod_path( + args.service_type, args.api_version, args.module_path or path + ) + sdk_mod_path: list[str] = sdk_mod_path_base.copy() + mod_suffix: str = "" + sdk_mod_path.append((args.sdk_mod_name or mod_name) + mod_suffix) + + additional_imports = set() + additional_imports.add( + "openstack_sdk::api::" + + "::".join(sdk_mod_path) + + "::RequestBuilder" + ) + additional_imports.add( + "openstack_sdk::{AsyncOpenStack, api::QueryAsync}" + ) + if args.operation_type == "list": + if "limit" in [ + k for (k, _) in type_manager.get_parameters("query") + ]: + is_list_paginated = True + additional_imports.add( + "openstack_sdk::api::{paged, Pagination}" + ) + elif args.operation_type == "delete": + additional_imports.add("openstack_sdk::api::ignore") + additional_imports.add( + "crate::cloud_worker::ConfirmableRequest" + ) + + context = { + "additional_imports": additional_imports, + "operation_id": operation_id, + "operation_type": spec.get( + "x-openstack-operation-type", args.operation_type + ), + "command_description": common_rust.sanitize_rust_docstrings( + common.make_ascii_string(spec.get("description")) + ), + "class_name": class_name, + "sdk_service_name": service_name, + "resource_name": res_name, + "url": path.lstrip("/").lstrip(ver_prefix).lstrip("/"), + "method": method, + "type_manager": type_manager, + "sdk_type_manager": sdk_type_manager, + "sdk_mod_path": sdk_mod_path, + "response_key": response_key, + "response_list_item_key": args.response_list_item_key, + "mime_type": mime_type, + "is_json_patch": is_json_patch, + "api_ver": api_ver, + "is_list_paginated": is_list_paginated, + } + + work_dir = Path(target_dir, "rust", "openstack_tui", "src") + impl_path = Path( + work_dir, "cloud_worker", "/".join(mod_path), f"{mod_name}.rs" + ) + + # Generate methods for the GET resource command + self._render_command(context, "rust_tui/impl.rs.j2", impl_path) + + self._format_code(impl_path) + + yield (mod_path, mod_name, path) + + def generate_mod( + self, target_dir, mod_path, mod_list, url, resource_name, service_name + ): + """Generate collection module (include individual modules)""" + work_dir = Path(target_dir, "rust", "openstack_tui", "src") + impl_path = Path( + work_dir, + "cloud_worker", + "/".join(mod_path[0:-1]), + f"{mod_path[-1]}.rs", + ) + + context = { + "mod_list": mod_list, + "mod_path": mod_path, + "url": url, + "resource_name": resource_name, + "service_name": service_name, + } + + # Generate methods for the GET resource command + self._render_command(context, "rust_sdk/mod.rs.j2", impl_path) + + self._format_code(impl_path) diff --git a/codegenerator/templates/rust_macros.j2 b/codegenerator/templates/rust_macros.j2 index a7c5ec1..0697a05 100644 --- a/codegenerator/templates/rust_macros.j2 +++ b/codegenerator/templates/rust_macros.j2 @@ -154,18 +154,18 @@ Some({{ val }}) {%- endmacro %} {#- Macros to render setting Request data from CLI input #} -{%- macro set_request_data_from_input(manager, dst_var, param, val_var) %} +{%- macro set_request_data_from_input(manager, dst_var, param, val_var, by_ref=False) %} {%- set is_nullable = param.is_nullable if param.is_nullable is defined else False %} {%- if param.type_hint in ["Option>", "Option>", "Option>"] %} - {{ dst_var }}.{{ param.remote_name }}({{ "*" + val_var }}); + {{ dst_var }}.{{ param.remote_name }}({{ ("*" + val_var) if not by_ref else (val + ".clone()") }}); {%- elif param.type_hint in ["Option", "Option", "Option", "Option", "Option"] %} {%- if param.is_optional is defined and not param.is_optional %} if let Some(val) = {{ val_var }} { - {{ dst_var }}.{{ param.remote_name }}(*val); + {{ dst_var }}.{{ param.remote_name }}({{ "*val" if not by_ref else "val.clone()" }}); } {%- else %} - {{ dst_var }}.{{ param.remote_name }}({{ "*" + val_var }}); + {{ dst_var }}.{{ param.remote_name }}({{ ("*" + val_var) if not by_ref else (val_var + ".clone()") }}); {%- endif %} {%- elif param.type_hint in ["i32", "i64", "f32", "f64", "bool"] %} @@ -247,7 +247,7 @@ Some({{ val }}) {%- elif is_nullable and param.is_optional %} {{ dst_var }}.{{ param.remote_name }}(Some({{ val_var }}.into())); {%- elif (param.is_optional is defined and param.is_optional) or (param.is_required is defined and not param.is_required) %} - {{ dst_var }}.{{ param.remote_name }}({{ val_var }}); + {{ dst_var }}.{{ param.remote_name }}({{ val_var if not by_ref else (val_var + ".clone()")}}); {%- else %} {{ dst_var }}.{{ param.remote_name }}(&{{ val_var | replace("&", "") }}); {%- endif %} diff --git a/codegenerator/templates/rust_tui/impl.rs.j2 b/codegenerator/templates/rust_tui/impl.rs.j2 new file mode 100644 index 0000000..7bbbbf3 --- /dev/null +++ b/codegenerator/templates/rust_tui/impl.rs.j2 @@ -0,0 +1,162 @@ +// 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. +// +// SPDX-License-Identifier: Apache-2.0 +// +// WARNING: This file is automatically generated from OpenAPI schema using +// `openstack-codegenerator`. +{% import 'rust_macros.j2' as macros with context -%} +use eyre::{Result, WrapErr}; +use serde::{Deserialize, Serialize}; +use std::fmt; +use tokio::sync::mpsc::UnboundedSender; + +use crate::action::Action; +use crate::cloud_worker::common::CloudWorkerError; +use crate::cloud_worker::types::{ApiRequest, ExecuteApiRequest}; + +{% for mod in additional_imports | sort -%} +use {{ mod }}; +{% endfor %} + +{%- with data_type = type_manager.get_root_data_type() %} +{%- set sdk_data_type = sdk_type_manager.get_root_data_type() %} + +{%- if data_type.__class__.__name__ == "Struct" %} +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct {{ class_name }}{ +{%- for name, param in type_manager.parameters | dictsort %} + {%- if param.location != "header" %} + {{ param.local_name }}: {{ param.type_hint }}, + {%- if param.local_name.endswith("id") %} + {%- set param_type = False %} + {{ param.local_name[:-2] }}name: + {%- if param.type_hint.startswith("Option") %} + {{ param.type_hint }}, + {%- else %} + Option<{{ param.type_hint }}>, + {%- endif %} + {%- endif %} + {%- endif %} +{%- endfor %} +} + +impl fmt::Display for {{ class_name }} { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut parts: Vec = Vec::new(); + {%- for name, param in type_manager.parameters | dictsort %} + {%- if param.location != "header" %} + {%- if param.local_name.endswith("_id") %} + {%- set alt_name = param.local_name.replace("_id", "_name") %} + {%- if param.type_hint.startswith("Option") %} + if self.{{ param.local_name }}.is_some() || self.{{ alt_name }}.is_some() { + parts.push(format!( + "{{ param.local_name[:-3] }}: {}", + self.{{ alt_name }} + .as_ref() + .or(self.{{ param.local_name }}.as_ref()) + .unwrap_or(&String::new()) + )); + } + {% else %} + parts.push(format!( + "{{ param.local_name[:-3] }}: {}", + self.{{ alt_name }} + .clone() + .unwrap_or(self.{{ param.local_name }}.clone()) + )); + {%- endif %} + {%- endif %} + {%- endif %} + {%- endfor %} + write!(f, "{}", parts.join(",")) + } +} + +impl From<&{{ class_name }}> for RequestBuilder{{ "<'_>" if sdk_type_manager.get_request_static_lifetimes(data_type) else "" }} { + fn from(value: &{{ class_name }}) -> Self { + let mut ep_builder = Self::default(); + +{%- for (k, v) in type_manager.get_parameters("path") %} +{%- if not v.is_required %} + {%- if k != "project_id" %} + if let Some(val) = &value.{{ v.local_name }} { + ep_builder.{{ v.local_name }}(val.clone()); + } + {%- else %} + if let Some(val) = &value.{{ v.local_name }} { + ep_builder.{{ v.local_name }}(val.clone()); + } else { + ep_builder.{{ v.local_name }}(client.get_current_project().expect("Project ID must be known").id); + } + {%- endif %} +{%- else %} + ep_builder.{{ v.local_name }}(value.{{ v.local_name }}.clone()); +{%- endif %} +{%- endfor %} +{%- for (k, v) in type_manager.get_parameters("query") %} + {%- if not v.is_required %} + if let Some(val) = &value.{{ v.local_name }} { + {{ macros.set_request_data_from_input(type_manager, "ep_builder", v, "val", by_ref=True)}} + } + {%- else %} + {{ macros.set_request_data_from_input(type_manager, "ep_builder", v, "value" + v.local_name, by_ref=True )}} + {%- endif %} +{%- endfor %} + ep_builder + } +} + +impl ExecuteApiRequest for {{ class_name }} { + async fn execute_request( + &self, + session: &mut AsyncOpenStack, + request: &ApiRequest, + app_tx: &UnboundedSender, + ) -> Result<(), CloudWorkerError> { + let ep = Into::::into(self) + .build() + .wrap_err("Cannot prepare request")?; + {%- if operation_type == "list" %} + app_tx.send(Action::ApiResponsesData { + request: request.clone(), + {%- if operation_type == "list" and is_list_paginated %} + data: paged(ep, Pagination::All).query_async(session).await?, + {%- else %} + data: ep.query_async(session).await?, + {%- endif %} + })?; + {%- elif operation_type == "show" %} + app_tx.send(Action::ApiResponseData { + request: request.clone(), + data: ep.query_async(session).await?, + })?; + {%- elif operation_type == "delete" %} + ignore(ep).query_async(session).await?; + {%- endif %} + Ok(()) + } +} + +{%- if operation_type == "delete" %} +impl ConfirmableRequest for {{ class_name }} { + fn get_confirm_message(&self) -> Option { + Some(format!( + "Delete {{ sdk_service_name }} {{ resource_name.title() }} {} ?", + self.name.clone().unwrap_or(self.id.clone()) + )) + } +} +{%- endif %} + +{%- endif %} +{%- endwith %} diff --git a/codegenerator/types.py b/codegenerator/types.py index cf014cb..229290b 100644 --- a/codegenerator/types.py +++ b/codegenerator/types.py @@ -31,7 +31,7 @@ OPERATION_TYPE = Literal[ "find", ] -SUPPORTED_TARGETS = Literal["rust-sdk", "rust-cli"] +SUPPORTED_TARGETS = Literal["rust-sdk", "rust-cli", "rust-tui"] class OperationTargetParams(BaseModel): diff --git a/metadata/block-storage_metadata.yaml b/metadata/block-storage_metadata.yaml index 71eeb4d..a63943e 100644 --- a/metadata/block-storage_metadata.yaml +++ b/metadata/block-storage_metadata.yaml @@ -34,6 +34,8 @@ resources: targets: rust-sdk: module_name: list + rust-tui: + module_name: list create: operation_id: volumes:post operation_type: create @@ -402,6 +404,8 @@ resources: sdk_mod_name: delete find_implemented_by_sdk: true cli_full_command: volume delete + rust-tui: + module_name: delete find: operation_id: volumes/detail:get operation_type: find @@ -975,6 +979,8 @@ resources: targets: rust-sdk: module_name: list + rust-tui: + module_name: list create: operation_id: snapshots:post operation_type: create @@ -1070,6 +1076,8 @@ resources: sdk_mod_name: delete find_implemented_by_sdk: true cli_full_command: snapshot delete + rust-tui: + module_name: delete find: operation_id: snapshots/detail:get operation_type: find @@ -1374,6 +1382,8 @@ resources: targets: rust-sdk: module_name: list + rust-tui: + module_name: list create: operation_id: backups:post operation_type: create @@ -1417,6 +1427,8 @@ resources: sdk_mod_name: delete find_implemented_by_sdk: true cli_full_command: backup delete + rust-tui: + module_name: delete os-reset-status: operation_id: backups/id/action:post operation_type: action diff --git a/metadata/compute_metadata.yaml b/metadata/compute_metadata.yaml index 8d6357f..efc226c 100644 --- a/metadata/compute_metadata.yaml +++ b/metadata/compute_metadata.yaml @@ -57,6 +57,8 @@ resources: targets: rust-sdk: module_name: list + rust-tui: + module_name: list create: operation_id: flavors:post operation_type: create @@ -89,6 +91,8 @@ resources: sdk_mod_name: get find_implemented_by_sdk: true cli_full_command: flavor show + rust-tui: + module_name: get update: operation_id: flavors/id:put operation_type: set @@ -111,6 +115,8 @@ resources: sdk_mod_name: delete find_implemented_by_sdk: true cli_full_command: flavor delete + rust-tui: + module_name: delete add-tenant-access: operation_id: flavors/id/action:post operation_type: action @@ -246,6 +252,8 @@ resources: module_name: list sdk_mod_name: list cli_full_command: aggregate list + rust-tui: + module_name: list create: operation_id: os-aggregates:post operation_type: create @@ -266,6 +274,8 @@ resources: module_name: show sdk_mod_name: get cli_full_command: aggregate show + rust-tui: + module_name: get update: operation_id: os-aggregates/id:put operation_type: set @@ -286,6 +296,8 @@ resources: module_name: delete sdk_mod_name: delete cli_full_command: aggregate delete + rust-tui: + module_name: delete add-host: operation_id: os-aggregates/id/action:post operation_type: action @@ -512,6 +524,8 @@ resources: targets: rust-sdk: module_name: list + rust-tui: + module_name: list list_detailed: operation_id: os-hypervisors/detail:get operation_type: list @@ -532,6 +546,8 @@ resources: module_name: show sdk_mod_name: get cli_full_command: hypervisor show + rust-tui: + module_name: get compute.hypervisor/statistic: spec_file: wrk/openapi_specs/compute/v2.yaml api_version: v2 @@ -751,6 +767,8 @@ resources: module_name: details sdk_mod_name: details cli_full_command: quota-set details + rust-tui: + module_name: details defaults: operation_id: os-quota-sets/id/defaults:get operation_type: show @@ -899,6 +917,8 @@ resources: targets: rust-sdk: module_name: list + rust-tui: + module_name: list create: operation_id: servers:post operation_type: create @@ -931,6 +951,8 @@ resources: sdk_mod_name: get find_implemented_by_sdk: true cli_full_command: server show + rust-tui: + module_name: get update: operation_id: servers/id:put operation_type: set @@ -953,6 +975,8 @@ resources: sdk_mod_name: delete find_implemented_by_sdk: true cli_full_command: server delete + rust-tui: + module_name: delete confirm-resize: operation_id: servers/id/action:post operation_type: action @@ -1660,6 +1684,8 @@ resources: sdk_mod_name: list response_key: instanceActions cli_full_command: server instance-action list + rust-tui: + module_name: list show: operation_id: servers/server_id/os-instance-actions/id:get operation_type: show @@ -1672,6 +1698,8 @@ resources: sdk_mod_name: get response_key: instanceAction cli_full_command: server instance-action show + rust-tui: + module_name: get compute.server/interface: spec_file: wrk/openapi_specs/compute/v2.yaml api_version: v2 diff --git a/metadata/dns_metadata.yaml b/metadata/dns_metadata.yaml index aadb216..e833b50 100644 --- a/metadata/dns_metadata.yaml +++ b/metadata/dns_metadata.yaml @@ -127,6 +127,8 @@ resources: sdk_mod_name: delete find_implemented_by_sdk: true cli_full_command: zone delete + rust-tui: + module_name: delete update: operation_id: zones/zone_id:patch operation_type: set @@ -148,6 +150,8 @@ resources: module_name: list sdk_mod_name: list cli_full_command: zone list + rust-tui: + module_name: list create: operation_id: zones:post operation_type: create @@ -205,6 +209,8 @@ resources: sdk_mod_name: delete find_implemented_by_sdk: true cli_full_command: zone recordset delete + rust-tui: + module_name: delete list: operation_id: zones/zone_id/recordsets:get operation_type: list @@ -215,6 +221,8 @@ resources: module_name: list sdk_mod_name: list cli_full_command: zone recordset list + rust-tui: + module_name: list create: operation_id: zones/zone_id/recordsets:post operation_type: create @@ -717,6 +725,8 @@ resources: module_name: list sdk_mod_name: list cli_full_command: recordset list + rust-tui: + module_name: list show: operation_id: recordsets/recordset_id:get operation_type: show diff --git a/metadata/identity_metadata.yaml b/metadata/identity_metadata.yaml index dfe65a6..4b72770 100644 --- a/metadata/identity_metadata.yaml +++ b/metadata/identity_metadata.yaml @@ -39,6 +39,8 @@ resources: module_name: list sdk_mod_name: list cli_full_command: auth project list + rust-tui: + module_name: list identity.OS_FEDERATION/project: spec_file: wrk/openapi_specs/identity/v3.yaml api_version: v3 @@ -384,11 +386,31 @@ resources: api_version: v3 operations: check: - operation_id: domains:head + operation_id: domains/domain_id:head operation_type: get targets: rust-sdk: module_name: head + list: + operation_id: domains:get + operation_type: list + targets: + rust-sdk: + module_name: list + rust-cli: + module_name: list + sdk_mod_name: list + cli_full_command: domain list + create: + operation_id: domains:post + operation_type: create + targets: + rust-sdk: + module_name: create + rust-cli: + module_name: create + sdk_mod_name: create + cli_full_command: domain create show: operation_id: domains/domain_id:get operation_type: show @@ -422,26 +444,6 @@ resources: sdk_mod_name: set find_implemented_by_sdk: true cli_full_command: domain set - list: - operation_id: domains:get - operation_type: list - targets: - rust-sdk: - module_name: list - rust-cli: - module_name: list - sdk_mod_name: list - cli_full_command: domain list - create: - operation_id: domains:post - operation_type: create - targets: - rust-sdk: - module_name: create - rust-cli: - module_name: create - sdk_mod_name: create - cli_full_command: domain create find: operation_id: domains:get operation_type: find @@ -872,6 +874,8 @@ resources: sdk_mod_name: delete find_implemented_by_sdk: true cli_full_command: group delete + rust-tui: + module_name: delete update: operation_id: groups/group_id:patch operation_type: set @@ -893,6 +897,8 @@ resources: module_name: list sdk_mod_name: list cli_full_command: group list + rust-tui: + module_name: list create: operation_id: groups:post operation_type: create @@ -933,6 +939,8 @@ resources: module_name: list sdk_mod_name: list cli_full_command: group user list + rust-tui: + module_name: list show: operation_id: groups/group_id/users/user_id:get operation_type: show @@ -963,6 +971,8 @@ resources: module_name: delete sdk_mod_name: delete cli_full_command: group user delete + rust-tui: + module_name: delete identity.limit: spec_file: wrk/openapi_specs/identity/v3.yaml api_version: v3 @@ -2190,6 +2200,8 @@ resources: module_name: list sdk_mod_name: list cli_full_command: project list + rust-tui: + module_name: list create: operation_id: projects:post operation_type: create @@ -2222,6 +2234,8 @@ resources: sdk_mod_name: delete find_implemented_by_sdk: true cli_full_command: project delete + rust-tui: + module_name: delete update: operation_id: projects/project_id:patch operation_type: set @@ -3015,6 +3029,8 @@ resources: sdk_mod_name: delete find_implemented_by_sdk: true cli_full_command: user delete + rust-tui: + module_name: delete update: operation_id: users/user_id:patch operation_type: set @@ -3036,6 +3052,8 @@ resources: module_name: list sdk_mod_name: list cli_full_command: user list + rust-tui: + module_name: list create: operation_id: users:post operation_type: create @@ -3250,6 +3268,8 @@ resources: module_name: list sdk_mod_name: list cli_full_command: application-credential list + rust-tui: + module_name: list create: operation_id: users/user_id/application_credentials:post operation_type: create @@ -3282,6 +3302,8 @@ resources: sdk_mod_name: delete find_implemented_by_sdk: true cli_full_command: application-credential delete + rust-tui: + module_name: delete find: operation_id: users/user_id/application_credentials:get operation_type: find diff --git a/metadata/image_metadata.yaml b/metadata/image_metadata.yaml index 10fb415..62340ea 100644 --- a/metadata/image_metadata.yaml +++ b/metadata/image_metadata.yaml @@ -564,6 +564,8 @@ resources: module_name: list sdk_mod_name: list cli_full_command: image list + rust-tui: + module_name: list create: operation_id: images:post operation_type: create @@ -596,6 +598,8 @@ resources: sdk_mod_name: delete find_implemented_by_sdk: true cli_full_command: image delete + rust-tui: + module_name: delete patch: operation_id: images/image_id:patch operation_type: set diff --git a/metadata/load-balancer_metadata.yaml b/metadata/load-balancer_metadata.yaml index 26c4161..7caa5c0 100644 --- a/metadata/load-balancer_metadata.yaml +++ b/metadata/load-balancer_metadata.yaml @@ -28,6 +28,8 @@ resources: sdk_mod_name: get find_implemented_by_sdk: true cli_full_command: loadbalancer show + rust-tui: + module_name: get update: operation_id: lbaas/loadbalancers/loadbalancer_id:put operation_type: set @@ -50,6 +52,8 @@ resources: sdk_mod_name: delete find_implemented_by_sdk: true cli_full_command: loadbalancer delete + rust-tui: + module_name: delete list: operation_id: lbaas/loadbalancers:get operation_type: list @@ -60,6 +64,8 @@ resources: module_name: list sdk_mod_name: list cli_full_command: loadbalancer list + rust-tui: + module_name: list create: operation_id: lbaas/loadbalancers:post operation_type: create @@ -128,6 +134,8 @@ resources: sdk_mod_name: get find_implemented_by_sdk: true cli_full_command: listener show + rust-tui: + module_name: get update: operation_id: lbaas/listeners/listener_id:put operation_type: set @@ -150,6 +158,8 @@ resources: sdk_mod_name: delete find_implemented_by_sdk: true cli_full_command: listener delete + rust-tui: + module_name: delete list: operation_id: lbaas/listeners:get operation_type: list @@ -160,6 +170,8 @@ resources: module_name: list sdk_mod_name: list cli_full_command: listener list + rust-tui: + module_name: list create: operation_id: lbaas/listeners:post operation_type: create @@ -206,6 +218,8 @@ resources: sdk_mod_name: get find_implemented_by_sdk: true cli_full_command: pool show + rust-tui: + module_name: get update: operation_id: lbaas/pools/pool_id:put operation_type: set @@ -228,6 +242,8 @@ resources: sdk_mod_name: delete find_implemented_by_sdk: true cli_full_command: pool delete + rust-tui: + module_name: delete list: operation_id: lbaas/pools:get operation_type: list @@ -238,6 +254,8 @@ resources: module_name: list sdk_mod_name: list cli_full_command: pool list + rust-tui: + module_name: list create: operation_id: lbaas/pools:post operation_type: create @@ -340,6 +358,8 @@ resources: sdk_mod_name: get find_implemented_by_sdk: true cli_full_command: healthmonitor show + rust-tui: + module_name: get update: operation_id: lbaas/healthmonitors/healthmonitor_id:put operation_type: set @@ -362,6 +382,8 @@ resources: sdk_mod_name: delete find_implemented_by_sdk: true cli_full_command: healthmonitor delete + rust-tui: + module_name: delete list: operation_id: lbaas/healthmonitors:get operation_type: list @@ -372,6 +394,8 @@ resources: module_name: list sdk_mod_name: list cli_full_command: healthmonitor list + rust-tui: + module_name: list create: operation_id: lbaas/healthmonitors:post operation_type: create @@ -406,6 +430,8 @@ resources: module_name: show sdk_mod_name: get cli_full_command: quota show + rust-tui: + module_name: get update: operation_id: lbaas/quotas/project_id:put operation_type: set @@ -426,6 +452,8 @@ resources: module_name: delete sdk_mod_name: delete cli_full_command: quota delete + rust-tui: + module_name: delete list: operation_id: lbaas/quotas:get operation_type: list @@ -436,6 +464,8 @@ resources: module_name: list sdk_mod_name: list cli_full_command: quota list + rust-tui: + module_name: list load-balancer.provider: spec_file: wrk/openapi_specs/load-balancer/v2.yaml api_version: v2 @@ -879,6 +909,8 @@ resources: module_name: list sdk_mod_name: list cli_full_command: pool member list + rust-tui: + module_name: list replace: operation_id: lbaas/pools/pool_id/members:put operation_type: set @@ -911,6 +943,8 @@ resources: sdk_mod_name: get find_implemented_by_sdk: true cli_full_command: pool member show + rust-tui: + module_name: get update: operation_id: lbaas/pools/pool_id/members/member_id:put operation_type: set @@ -933,6 +967,8 @@ resources: sdk_mod_name: delete find_implemented_by_sdk: true cli_full_command: pool member delete + rust-tui: + module_name: delete find: operation_id: lbaas/pools/pool_id/members:get operation_type: find diff --git a/metadata/network_metadata.yaml b/metadata/network_metadata.yaml index f02e9fe..d6982ae 100644 --- a/metadata/network_metadata.yaml +++ b/metadata/network_metadata.yaml @@ -423,6 +423,8 @@ resources: module_name: list sdk_mod_name: list cli_full_command: router list + rust-tui: + module_name: list create: operation_id: routers:post operation_type: create @@ -543,6 +545,8 @@ resources: sdk_mod_name: delete find_implemented_by_sdk: true cli_full_command: router delete + rust-tui: + module_name: delete find: operation_id: routers:get operation_type: find @@ -2392,6 +2396,8 @@ resources: module_name: details sdk_mod_name: details cli_full_command: quota details + rust-tui: + module_name: details network.rbac_policy: spec_file: wrk/openapi_specs/network/v2.yaml api_version: v2 @@ -2514,6 +2520,8 @@ resources: module_name: list sdk_mod_name: list cli_full_command: security-group list + rust-tui: + module_name: list create: operation_id: security-groups:post operation_type: create @@ -2557,6 +2565,8 @@ resources: sdk_mod_name: delete find_implemented_by_sdk: true cli_full_command: security-group delete + rust-tui: + module_name: delete find: operation_id: security-groups:get operation_type: find @@ -2581,6 +2591,8 @@ resources: module_name: list sdk_mod_name: list cli_full_command: security-group-rule list + rust-tui: + module_name: list create: operation_id: security-group-rules:post operation_type: create @@ -2621,6 +2633,8 @@ resources: module_name: delete sdk_mod_name: delete cli_full_command: security-group-rule delete + rust-tui: + module_name: delete network.segment: spec_file: wrk/openapi_specs/network/v2.yaml api_version: v2 @@ -3499,6 +3513,8 @@ resources: module_name: list sdk_mod_name: list cli_full_command: network list + rust-tui: + module_name: list create: operation_id: networks:post operation_type: create @@ -3542,6 +3558,8 @@ resources: sdk_mod_name: delete find_implemented_by_sdk: true cli_full_command: network delete + rust-tui: + module_name: delete find: operation_id: networks:get operation_type: find @@ -3566,6 +3584,8 @@ resources: module_name: list sdk_mod_name: list cli_full_command: subnet list + rust-tui: + module_name: list create: operation_id: subnets:post operation_type: create @@ -3609,6 +3629,8 @@ resources: sdk_mod_name: delete find_implemented_by_sdk: true cli_full_command: subnet delete + rust-tui: + module_name: delete find: operation_id: subnets:get operation_type: find diff --git a/playbooks/rust/all.yaml b/playbooks/rust/all.yaml index a1bc837..3aa7637 100644 --- a/playbooks/rust/all.yaml +++ b/playbooks/rust/all.yaml @@ -21,7 +21,7 @@ - name: "Pre-Compile current code to ensure it builds" ansible.builtin.command: - cmd: "cargo build -p openstack_sdk -p openstack_cli" + cmd: "cargo build -p openstack_sdk -p openstack_cli -p openstack_tui" chdir: "{{ rust_project_dir }}" - name: "Overwrite generated files" @@ -37,7 +37,7 @@ - name: "Compile new code (only generated crates)" ansible.builtin.command: - cmd: "cargo build -p openstack_sdk -p openstack_cli" + cmd: "cargo build -p openstack_sdk -p openstack_cli -p openstack_tui" chdir: "{{ rust_project_dir }}" - name: "Checkout new branch" diff --git a/zuul.d/rust.yaml b/zuul.d/rust.yaml index 6fab1a4..a98a2ef 100644 --- a/zuul.d/rust.yaml +++ b/zuul.d/rust.yaml @@ -14,25 +14,25 @@ codegenerator_service_metadata_target_map: - service: "block-storage" metadata: "metadata/block-storage_metadata.yaml" - targets: ["rust-sdk", "rust-cli"] + targets: ["rust-sdk", "rust-cli", "rust-tui"] - service: "compute" metadata: "metadata/compute_metadata.yaml" - targets: ["rust-sdk", "rust-cli"] + targets: ["rust-sdk", "rust-cli", "rust-tui"] - service: "dns" metadata: "metadata/dns_metadata.yaml" - targets: ["rust-sdk", "rust-cli"] + targets: ["rust-sdk", "rust-cli", "rust-tui"] - service: "identity" metadata: "metadata/identity_metadata.yaml" - targets: ["rust-sdk", "rust-cli"] + targets: ["rust-sdk", "rust-cli", "rust-tui"] - service: "image" metadata: "metadata/image_metadata.yaml" - targets: ["rust-sdk", "rust-cli"] + targets: ["rust-sdk", "rust-cli", "rust-tui"] - service: "load-balancer" metadata: "metadata/load-balancer_metadata.yaml" - targets: ["rust-sdk", "rust-cli"] + targets: ["rust-sdk", "rust-cli", "rust-tui"] - service: "network" metadata: "metadata/network_metadata.yaml" - targets: ["rust-sdk", "rust-cli"] + targets: ["rust-sdk", "rust-cli", "rust-tui"] - service: "object-store" metadata: "metadata/object-store_metadata.yaml" targets: ["rust-sdk"]