diff --git a/requirements.txt b/requirements.txt index 8fb281ca..15691c6c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ schematics~=2.1.1 pyyaml~=6.0.2 fuzzywuzzy~=0.18.0 -inflect~=5.6.2 azure-mgmt-core~=1.3.0 +pluralizer~=1.2.0 lxml~=4.9.4 flask~=3.0.3 cachelib~=0.13.0 diff --git a/src/aaz_dev/cli/api/__init__.py b/src/aaz_dev/cli/api/__init__.py index b8205959..843abb09 100644 --- a/src/aaz_dev/cli/api/__init__.py +++ b/src/aaz_dev/cli/api/__init__.py @@ -1,6 +1,7 @@ def register_blueprints(app): - from . import az, portal, _cmds + from . import az, ps, portal, _cmds app.register_blueprint(_cmds.bp) app.register_blueprint(az.bp) + app.register_blueprint(ps.bp) app.register_blueprint(portal.bp) diff --git a/src/aaz_dev/cli/api/_cmds.py b/src/aaz_dev/cli/api/_cmds.py index ea647a41..91622b2d 100644 --- a/src/aaz_dev/cli/api/_cmds.py +++ b/src/aaz_dev/cli/api/_cmds.py @@ -2,6 +2,8 @@ import logging from flask import Blueprint import sys +import subprocess +import os from utils.config import Config @@ -237,3 +239,130 @@ def _build_profile(profile_name, commands_map): group_names = parent_group_names return profile + + +@bp.cli.command("generate-powershell", short_help="Generate powershell code based on selected azure cli module.") +@click.option( + "--aaz-path", '-a', + type=click.Path(file_okay=False, dir_okay=True, writable=True, readable=True, resolve_path=True), + default=Config.AAZ_PATH, + required=not Config.AAZ_PATH, + callback=Config.validate_and_setup_aaz_path, + expose_value=False, + help="The local path of aaz repo." +) +@click.option( + "--cli-path", '-c', + type=click.Path(file_okay=False, dir_okay=True, writable=True, readable=True, resolve_path=True), + callback=Config.validate_and_setup_cli_path, + help="The local path of azure-cli repo. Only required when generate from azure-cli module." +) +@click.option( + "--cli-extension-path", '-e', + type=click.Path(file_okay=False, dir_okay=True, writable=True, readable=True, resolve_path=True), + callback=Config.validate_and_setup_cli_extension_path, + help="The local path of azure-cli-extension repo. Only required when generate from azure-cli extension." +) +@click.option( + "--powershell-path", '-p', + type=click.Path(file_okay=False, dir_okay=True, writable=True, readable=True, resolve_path=True), + required=True, + help="The local path of azure-powershell repo." +) +@click.option( + "--extension-or-module-name", '--name', + required=True, + help="Name of the module in azure-cli or the extension in azure-cli-extensions" +) +@click.option( + "--swagger-path", '-s', + type=click.Path(file_okay=False, dir_okay=True, readable=True, resolve_path=True), + default=Config.SWAGGER_PATH, + required=not Config.SWAGGER_PATH, + callback=Config.validate_and_setup_swagger_path, + expose_value=False, + help="The local path of azure-rest-api-specs repo. Official repo is https://github.com/Azure/azure-rest-api-specs" +) +def generate_powershell(extension_or_module_name, cli_path=None, cli_extension_path=None, powershell_path=None): + from cli.controller.ps_config_generator import PSAutoRestConfigurationGenerator + from cli.controller.az_module_manager import AzMainManager, AzExtensionManager + from cli.templates import get_templates + + # Module path in azure-powershell repo + + powershell_path = os.path.join(powershell_path, "src") + if not os.path.exists(powershell_path): + logger.error(f"Path `{powershell_path}` not exist") + sys.exit(1) + + if cli_path is not None: + assert Config.CLI_PATH is not None + manager = AzMainManager() + else: + assert cli_extension_path is not None + assert Config.CLI_EXTENSION_PATH is not None + manager = AzExtensionManager() + + if not manager.has_module(extension_or_module_name): + logger.error(f"Cannot find module or extension `{extension_or_module_name}`") + sys.exit(1) + + # generate README.md for powershell from CLI, ex, for Oracle, README.md should be generated in src/Oracle/Oracle.Autorest/README.md in azure-powershell repo + ps_generator = PSAutoRestConfigurationGenerator(manager, extension_or_module_name) + ps_cfg = ps_generator.generate_config() + + autorest_module_path = os.path.join(powershell_path, ps_cfg.module_name, f"{ps_cfg.module_name}.Autorest") + if not os.path.exists(autorest_module_path): + os.makedirs(autorest_module_path) + readme_file = os.path.join(autorest_module_path, "README.md") + if os.path.exists(readme_file): + # read until to the "### AutoRest Configuration" + with open(readme_file, "r") as f: + lines = f.readlines() + for i, line in enumerate(lines): + if line.startswith("### AutoRest Configuration"): + lines = lines[:i] + break + else: + lines = [] + + tmpl = get_templates()['powershell']['configuration'] + data = tmpl.render(cfg=ps_cfg) + lines.append(data) + with open(readme_file, "w") as f: + f.writelines(lines) + + print(f"Generated {readme_file}") + # Generate and build PowerShell module from the README.md file generated above + print("Start to generate the PowerShell module from the README.md file in " + autorest_module_path) + + # Execute autorest to generate the PowerShell module + original_cwd = os.getcwd() + os.chdir(autorest_module_path) + exit_code = os.system("pwsh -Command autorest") + + # Print the output of the generation + if (exit_code != 0): + print("Failed to generate the module") + os.chdir(original_cwd) + sys.exit(1) + else: + print("Code generation succeeded.") + # print(result.stdout) + + os.chdir(original_cwd) + # Execute autorest to generate the PowerShell module + print("Start to build the generated PowerShell module") + result = subprocess.run( + ["pwsh", "-File", 'build-module.ps1'], + capture_output=True, + text=True, + cwd=autorest_module_path + ) + + if (result.returncode != 0): + print("Failed to build the module, please see following output for details:") + print(result.stderr) + sys.exit(1) + else: + print("Module build succeeds, and you may run the generated module by executing the following command: `./run-module.ps1` in " + autorest_module_path) diff --git a/src/aaz_dev/cli/api/ps.py b/src/aaz_dev/cli/api/ps.py new file mode 100644 index 00000000..d2cc8858 --- /dev/null +++ b/src/aaz_dev/cli/api/ps.py @@ -0,0 +1,14 @@ +from flask import Blueprint, jsonify, request, url_for + +from utils.config import Config +from utils import exceptions +from cli.controller.az_module_manager import AzMainManager, AzExtensionManager +from cli.controller.portal_cli_generator import PortalCliGenerator +from cli.model.view import CLIModule +from command.controller.specs_manager import AAZSpecsManager +import logging + +logging.basicConfig(level="INFO") + +bp = Blueprint('ps', __name__, url_prefix='/CLI/PS') + diff --git a/src/aaz_dev/cli/controller/ps_config_generator.py b/src/aaz_dev/cli/controller/ps_config_generator.py new file mode 100644 index 00000000..adb0bce6 --- /dev/null +++ b/src/aaz_dev/cli/controller/ps_config_generator.py @@ -0,0 +1,617 @@ +from .az_module_manager import AzModuleManager +from swagger.controller.specs_manager import SwaggerSpecsManager +from command.controller.specs_manager import AAZSpecsManager +from utils.config import Config +from utils.exceptions import ResourceNotFind +from command.model.configuration import CMDConfiguration, CMDResource, CMDHttpOperation +import json +import logging +from pluralizer import Pluralizer +import re +import os +from swagger.model.specs._utils import map_path_2_repo + +from fuzzywuzzy import fuzz + + +logger = logging.getLogger("backend") + +class PSAutoRestConfiguration: + + def __init__(self): + self.commit = None + self.version = None + self.module_name = None + self.readme_file = None + self.removed_subjects = [] + self.removed_verbs = [] + + +class PSAutoRestConfigurationGenerator: + _CAMEL_CASE_PATTERN = re.compile(r"^([a-zA-Z][a-z0-9]+)(([A-Z][a-z0-9]*)+)$") + _pluralizer = Pluralizer() + + def __init__(self, az_module_manager: AzModuleManager, module_name) -> None: + self.module_manager = az_module_manager + self.module_name = module_name + self.aaz_specs_manager = AAZSpecsManager() + self.swagger_specs_manager = SwaggerSpecsManager() + self._ps_profile = None + + def generate_config(self): + ps_cfg = PSAutoRestConfiguration() + # TODO: get the commit using git + ps_cfg.commit = "cbbe228fd422db02b65e2748f83df5f2bcad7581" + + module = self.module_manager.load_module(self.module_name) + + cli_profile = {} + + for cli_command in self.iter_cli_commands( + module.profiles[Config.CLI_DEFAULT_PROFILE] + ): + names = cli_command.names + version_name = cli_command.version + aaz_cmd = self.aaz_specs_manager.find_command(*names) + if not aaz_cmd: + raise ResourceNotFind( + "Command '{}' not exist in AAZ".format(" ".join(names)) + ) + version = None + for v in aaz_cmd.versions or []: + if v.name == version_name: + version = v + break + if not version: + raise ResourceNotFind( + "Version '{}' of command '{}' not exist in AAZ".format( + version_name, " ".join(names) + ) + ) + resource = v.resources[0] + cfg: CMDConfiguration = self.aaz_specs_manager.load_resource_cfg_reader( + resource.plane, resource.id, resource.version + ) + if not cfg: + raise ResourceNotFind( + "Resource Configuration '{}' not exist in AAZ".format(resource.id) + ) + for resource in cfg.resources: + tag = (resource.plane, "/".join(resource.mod_names), resource.rp_name) + if tag not in cli_profile: + cli_profile[tag] = {} + if resource.id not in cli_profile[tag]: + cli_profile[tag][resource.id] = { + "path": resource.path, + "cfg": cfg, + "commands": [], + "subresources": [], + } + cli_profile[tag][resource.id]["commands"].append(cli_command.names) + if resource.subresource: + cli_profile[tag][resource.id]["subresources"].append( + resource.subresource + ) + + # TODO: let LLM to choice the plane and rp_name later + if len(cli_profile.keys()) > 1: + raise ValueError("Only one plane module and rp_name is supported") + (plane, mod_names, rp_name) = list(cli_profile.keys())[0] + cli_resources = cli_profile[(plane, mod_names, rp_name)] + + module_manager = self.swagger_specs_manager.get_module_manager( + plane, mod_names.split("/") + ) + rp = module_manager.get_openapi_resource_provider(rp_name) + swagger_resources = rp.get_resource_map_by_tag(rp.default_tag) + if not swagger_resources: + raise ResourceNotFind("Resources not find in Swagger") + + readme_parts= rp._readme_path.split(os.sep) + ps_cfg.readme_file = '/'.join(readme_parts[readme_parts.index("specification"):]) + ps_cfg.version = "0.1.0" + ps_cfg.module_name = mod_names.split("/")[0] + ps_cfg.module_name = ps_cfg.module_name[0].upper() + ps_cfg.module_name[1:] + + ps_profile = {} + + # create ps_profile by swagger resources + for resource_id, resource in swagger_resources.items(): + resource = list(resource.values())[0] + methods = set() + operations = {} + op_group_name = self.get_operation_group_name(resource) + if resource_id not in cli_resources: + # the whole resource id is not used in cli + for op_tag, method in resource.operations.items(): + operations[op_tag] = { + "tag": op_tag, + "delete": True, + "resource_id": resource_id, + "method": method, + } + methods.add(method) + else: + for cmd_names in cli_resources[resource_id]["commands"]: + cfg = cli_resources[resource_id]["cfg"] + command = cfg.find_command(*cmd_names) + assert command is not None + for cmd_op in command.operations: + if not isinstance(cmd_op, CMDHttpOperation): + continue + op_tag = cmd_op.operation_id + if op_tag not in resource.operations: + # make sure the operation is from the same resource + continue + method = resource.operations[op_tag] + operations[op_tag] = { + "tag": op_tag, + "delete": False, + "resource_id": resource_id, + "method": method, + } + methods.add(method) + + for op_tag, method in resource.operations.items(): + if op_tag not in operations: + operations[op_tag] = { + "tag": op_tag, + "delete": ( + False if method == "patch" else True + ), # PowerShell Prefer Patch Method for update commands + "resource_id": resource_id, + "method": method, + } + methods.add(method) + + for op_tag, op in operations.items(): + variants = self.inferCommandNames(op_tag, op_group_name) + op['variants'] = [] + for variant in variants: + if op['method'] == 'put' and variant['action'] == 'Update': + if 'get' not in methods: + # "update" should be "set" if it's a PUT and not the generic update (GET+PUT) + variant['verb'] = 'Set' + else: + use_generic_update = True + for patch_op in operations.values(): + if patch_op['method'] == 'patch': + use_generic_update = patch_op['delete'] + break + if not use_generic_update: + continue + op['variants'].append(variant) + # make sure the variants should have the same subject + subjects = list(set([v['subject'] for v in op['variants']])) + assert len(subjects) == 1, f"Operation {op_tag} has different subjects: {subjects}" + + subject = subjects[0] + if subject not in ps_profile: + ps_profile[subject] = { + "operations": {}, + "delete": False, + } + ps_profile[subject]["operations"][op_tag] = op + + for subject in ps_profile.values(): + subject_delete = True + for op in subject['operations'].values(): + if not op['delete']: + subject_delete = False + break + subject['delete'] = subject_delete + + self._ps_profile = ps_profile + + ps_cfg.removed_subjects = [] + ps_cfg.removed_verbs = [] + for subject_name, subject in ps_profile.items(): + if subject['delete']: + ps_cfg.removed_subjects.append(subject_name) + continue + for op in subject['operations'].values(): + if op['delete']: + for variant in op['variants']: + ps_cfg.removed_verbs.append((variant['subject'], variant['verb'])) + return ps_cfg + + def iter_cli_commands(self, profile): + for command_group in profile.command_groups.values(): + for cli_command in self._iter_cli_commands(command_group): + yield cli_command + + def _iter_cli_commands(self, view_command_group): + if view_command_group.commands: + for cli_command in view_command_group.commands.values(): + yield cli_command + if view_command_group.command_groups: + for command_group in view_command_group.command_groups.values(): + for cli_command in self._iter_cli_commands(command_group): + yield cli_command + + def get_operation_group_name(self, resource): + operation_groups = set() + for operation_id, method in resource.operations.items(): + op_group = self._parse_operation_group_name(resource, operation_id, method) + operation_groups.add(op_group) + + if None in operation_groups: + return None + + if len(operation_groups) == 1: + return operation_groups.pop() + + op_group_name = sorted( + operation_groups, + key=lambda nm: fuzz.partial_ratio( + resource.id, nm + ), # use the name which is closest to resource_id + reverse=True, + )[0] + return op_group_name + + def _parse_operation_group_name(self, resource, op_id, method): + # extract operation group name from operation_id + value = op_id.strip() + value = value.replace("-", "_") + if "_" in value: + parts = value.split("_") + op_group_name = parts[0] + if op_group_name.lower() in ("create", "get", "update", "delete", "patch"): + op_group_name = parts[1] + else: + if " " in value: + value = value.replace(" ", "") # Changed to Camel Case + match = self._CAMEL_CASE_PATTERN.match(value) + if not match: + logger.error( + f"InvalidOperationIdFormat:" + f"\toperationId should be in format of '[OperationGroupName]_[OperationName]' " + f"or '[Verb][OperationGroupName]':\n" + f"\tfile: {map_path_2_repo(resource.file_path)}\n" + f"\tpath: {resource.path}\n" + f"\tmethod: {method} operationId: {op_id}\n" + ) + return None + op_group_name = match[2] # [OperationGroupName] + + return self.singular_noun(op_group_name) + + @classmethod + def inferCommandNames(cls, operation_id, op_group_name): + parts = operation_id.split("_") + assert len(parts) == 2 + method = parts[1] + method = method[0].upper() + method[1:] + + if VERB_MAPPING.get(method): + return [ + cls.create_command_variant(method, [op_group_name], []) + ] + + # split camel case to words + words = re.findall(r'[A-Z][a-z]*', method) + return cls._infer_command(words, op_group_name, []) + + @classmethod + def _infer_command(cls, operation, op_group_name, suffix): + operation = [w for w in operation if w != 'All'] + if len(operation) == 1: + # simple operation, just an id with a single value + return [ + cls.create_command_variant(operation[0], [op_group_name], suffix) + ] + + if len(operation) == 2: + # should try to infer [SUBJECT] and [ACTION] from operation + if VERB_MAPPING.get(operation[0]): + # [ACTION][SUBJECT] + return [ + cls.create_command_variant(operation[0], [op_group_name, operation[1]], suffix) + ] + if VERB_MAPPING.get(operation[1]): + # [SUBJECT][ACTION] + return [ + cls.create_command_variant(operation[1], [op_group_name, operation[0]], suffix) + ] + logger.warning(f"Operation ${operation[0]}/${operation[1]} is inferred without finding action.") + return [ + cls.create_command_variant(operation[0], [op_group_name, operation[1]], suffix) + ] + + # three or more words. + # first, see if it's an 'or' + if 'Or' in operation: + idx = operation.index('Or') + return cls._infer_command( + operation[:idx] + operation[idx+2:], + op_group_name, + suffix + ) + cls._infer_command( + operation[idx+1:], + op_group_name, + suffix + ) + + for w in ['With', 'At', 'By', 'For', 'In', 'Of']: + if w in operation: + idx = operation.index(w) + if idx > 0: + # so this is something like DoActionWithStyle + return cls._infer_command( + operation[:idx], + op_group_name, + operation[idx:], + ) + + # if not, then seek out a verb from there. + for i in range(len(operation)): + if VERB_MAPPING.get(operation[i]): + # if the action is first + if i == 0: + # everything else is the subject + return [ + cls.create_command_variant(operation[0], [op_group_name] + operation[1:], suffix) + ] + if i == len(operation) - 1: + # if it's last, the subject would be the first thing + return [ + cls.create_command_variant(operation[i], [op_group_name] + operation[:i], suffix) + ] + # otherwise + # things before are part of the subject + # things after the verb should be part of the suffix + return [ + cls.create_command_variant(operation[i], [op_group_name] + operation[:i], suffix + operation[i+1:]) + ] + + # so couldn't tell what the action was. + # fallback to the original behavior with a warning. + logger.warning(f"Operation ${operation[0]}/${operation[1]} is inferred without finding action.") + return [ + cls.create_command_variant(operation[0], [op_group_name] + operation[1:], suffix) + ] + + @classmethod + def create_command_variant(cls, action, subject, variant): + verb = cls.get_powershell_verb(action) + if verb == 'Invoke': + # if the 'operation' name was "post" -- it's kindof redundant. + # so, only include the operation name in the group name if it's anything else + if action.lower() != 'post': + subject = [action] + subject + subject = [cls.singular_noun(s) or s for s in subject] + # remove duplicate values + values = set() + simplified_subject = [] + for s in subject: + if s in values: + continue + values.add(s) + simplified_subject.append(s) + return { + "subject": ''.join(simplified_subject), + "verb": verb, + "variant": ''.join(variant), + "action": action, + } + + @staticmethod + def get_powershell_verb(action): + action = action[0].upper() + action[1:].lower() + return VERB_MAPPING.get(action, "Invoke") + + @classmethod + def singular_noun(cls, noun): + words = re.findall(r'[A-Z][a-z]*', noun) + # singular the last word in operation group name + w = cls._pluralizer.singular(words[-1].lower()) + words[-1] = w[0].upper() + w[1:] + noun = ''.join(words) + return noun + + +VERB_MAPPING = { + "Access": "Get", + "Acquire": "Get", + "Activate": "Initialize", + "Add": "Add", + "Allocate": "New", + "Analyze": "Test", + "Append": "Add", + "Apply": "Add", + "Approve": "Approve", + "Assert": "Assert", + "Assign": "Set", + "Associate": "Join", + "Attach": "Add", + "Authorize": "Grant", + "Backup": "Backup", + "Block": "Block", + "Build": "Build", + "Bypass": "Skip", + "Cancel": "Stop", + "Capture": "Export", + "Cat": "Get", + "Change": "Rename", + "Check": "Test", + "Checkpoint": "Checkpoint", + "Clear": "Clear", + "Clone": "Copy", + "Close": "Close", + "Combine": "Join", + "Compare": "Compare", + "Compile": "Build", + "Complete": "Complete", + "Compress": "Compress", + "Concatenate": "Add", + "Configure": "Set", + "Confirm": "Confirm", + "Connect": "Connect", + "Convert": "Convert", + "ConvertFrom": "ConvertFrom", + "ConvertTo": "ConvertTo", + "Copy": "Copy", + "Create": "New", + "Cut": "Remove", + "Debug": "Debug", + "Delete": "Remove", + "Deny": "Deny", + "Deploy": "Deploy", + "Dir": "Get", + "Disable": "Disable", + "Discard": "Remove", + "Disconnect": "Disconnect", + "Discover": "Find", + "Dismount": "Dismount", + "Display": "Show", + "Dispose": "Remove", + "Dump": "Get", + "Duplicate": "Copy", + "Edit": "Edit", + "Enable": "Enable", + "End": "Stop", + "Enter": "Enter", + "Erase": "Clear", + "Evaluate": "Test", + "Examine": "Get", + "Execute": "Invoke", + "Exit": "Exit", + "Expand": "Expand", + "Export": "Export", + "Failover": "Set", + "Find": "Find", + "Finish": "Complete", + "Flush": "Clear", + "ForceReboot": "Restart", + "Format": "Format", + "Generalize": "Reset", + "Generate": "New", + "Get": "Get", + "Grant": "Grant", + "Group": "Group", + "Hide": "Hide", + "Import": "Import", + "Initialize": "Initialize", + "Insert": "Add", + "Install": "Install", + "Into": "Enter", + "Invoke": "Invoke", + "Is": "Test", + "Join": "Join", + "Jump": "Skip", + "Limit": "Limit", + "List": "Get", + "Load": "Import", + "Locate": "Find", + "Lock": "Lock", + "Make": "New", + "Measure": "Measure", + "Merge": "Merge", + "Migrate": "Move", + "Mount": "Mount", + "Move": "Move", + "Name": "Move", + "New": "New", + "Notify": "Send", + "Nullify": "Clear", + "Obtain": "Get", + "Open": "Open", + "Optimize": "Optimize", + "Out": "Out", + "Patch": "Update", + "Pause": "Suspend", + "Perform": "Invoke", + "Ping": "Ping", + "Pop": "Pop", + "Post": "Invoke", + "Power": "Start", + "PowerOff": "Stop", + "PowerOn": "Start", + "Produce": "Show", + "Protect": "Protect", + "Provision": "New", + "Publish": "Publish", + "Purge": "Clear", + "Push": "Push", + "Put": "Set", + "Read": "Read", + "Reassociate": "Move", + "Reboot": "Restart", + "Receive": "Receive", + "Recover": "Restore", + "Redo": "Redo", + "Refresh": "Update", + "Regenerate": "New", + "Register": "Register", + "Reimage": "Update", + "Release": "Publish", + "Remove": "Remove", + "Rename": "Rename", + "Repair": "Repair", + "Replace": "Update", + "Replicate": "Copy", + "Reprocess": "Update", + "Request": "Request", + "Reset": "Reset", + "Resize": "Resize", + "Resolve": "Resolve", + "Restart": "Restart", + "Restore": "Restore", + "Restrict": "Lock", + "Resubmit": "Submit", + "Resume": "Resume", + "Retarget": "Update", + "Retrieve": "Get", + "Revoke": "Revoke", + "Run": "Start", + "Save": "Save", + "Search": "Search", + "Secure": "Lock", + "Select": "Select", + "Send": "Send", + "Separate": "Split", + "Set": "Set", + "Show": "Show", + "Shutdown": "Stop", + "Skip": "Skip", + "Split": "Split", + "Start": "Start", + "Step": "Step", + "Stop": "Stop", + "Submit": "Submit", + "Suggest": "Get", + "Suspend": "Suspend", + "Swap": "Switch", + "Switch": "Switch", + "Sync": "Sync", + "Synch": "Sync", + "Synchronize": "Sync", + "Test": "Test", + "Trace": "Trace", + "Transfer": "Move", + "Trigger": "Start", + "Type": "Get", + "Unblock": "Unblock", + "Undelete": "Restore", + "Undo": "Undo", + "Uninstall": "Uninstall", + "Unite": "Join", + "Unlock": "Unlock", + "Unmark": "Clear", + "Unprotect": "Unprotect", + "Unpublish": "Unpublish", + "Unregister": "Unregister", + "Unrestrict": "Unlock", + "Unsecure": "Unlock", + "Unset": "Clear", + "Update": "Update", + "Upgrade": "Update", + "Use": "Use", + "Validate": "Test", + "Verify": "Test", + "Wait": "Wait", + "Watch": "Watch", + "Wipe": "Clear", + "Write": "Write", +} diff --git a/src/aaz_dev/cli/templates/__init__.py b/src/aaz_dev/cli/templates/__init__.py index 7eafd9b8..83bb8293 100644 --- a/src/aaz_dev/cli/templates/__init__.py +++ b/src/aaz_dev/cli/templates/__init__.py @@ -60,5 +60,8 @@ def get_templates(): } } }, + 'powershell': { + "configuration": env.get_template("powershell/configuration.yaml.j2") + }, } return _templates diff --git a/src/aaz_dev/cli/templates/powershell/configuration.yaml.j2 b/src/aaz_dev/cli/templates/powershell/configuration.yaml.j2 new file mode 100644 index 00000000..ef8084e0 --- /dev/null +++ b/src/aaz_dev/cli/templates/powershell/configuration.yaml.j2 @@ -0,0 +1,53 @@ +### AutoRest Configuration +> see https://aka.ms/autorest + +```yaml +commit: {{ cfg.commit }} +require: + - $(this-folder)/../../readme.azure.noprofile.md + - $(repo)/{{ cfg.readme_file }} + +try-require: + - $(repo)/{{ cfg.readme_file }} + +module-version: {{ cfg.version }} +title: {{ cfg.module_name }} +subject-prefix: $(service-name) + +inlining-threshold: 100 +resourcegroup-append: true +nested-object-to-string: true +identity-correction-for-post: true + +directive: + # Model complex objects + + # Remove the set-* cmdlet + - where: + verb: Set + remove: true + + # Remove APIs + {%- if cfg.removed_subjects|length %} + - where: + subject: {{ cfg.removed_subjects | join("|") }} + remove: true + {%- endif %} + {%- for subject, verb in cfg.removed_verbs %} + - where: + subject: {{ subject }} + verb: {{ verb }} + remove: true + {%- endfor %} + + # Remove variants + - where: + variant: ^(Create|Update)(?!.*?(Expanded|JsonFilePath|JsonString)) + remove: true + - where: + variant: ^CreateViaIdentity.*$ + remove: true + + # Rename parameter + +``` diff --git a/src/aaz_dev/command/model/configuration/_arg_builder.py b/src/aaz_dev/command/model/configuration/_arg_builder.py index 6b376443..ab82aff1 100644 --- a/src/aaz_dev/command/model/configuration/_arg_builder.py +++ b/src/aaz_dev/command/model/configuration/_arg_builder.py @@ -1,6 +1,6 @@ import re -import inflect +from pluralizer import Pluralizer from utils.case import to_camel_case from ._arg import CMDArg, CMDArgBase, CMDArgumentHelp, CMDArgEnum, CMDArgDefault, CMDBooleanArgBase, \ @@ -14,7 +14,7 @@ class CMDArgBuilder: - _inflect_engine = inflect.engine() + _pluralizer = Pluralizer() @classmethod def new_builder(cls, schema, parent=None, var_prefix=None, ref_args=None, ref_arg=None, is_update_action=False): @@ -364,7 +364,7 @@ def get_options(self): if name == "[Index]" or name == "{Key}": assert self._arg_var.endswith(name) prefix = self._arg_var[:-len(name)].split('.')[-1] - prefix = self._inflect_engine.singular_noun(prefix) + prefix = self._pluralizer.singular(prefix) if name == "[Index]": name = f'{prefix}-index' elif name == "{Key}": @@ -372,7 +372,7 @@ def get_options(self): elif name.startswith('[].') or name.startswith('{}.'): assert self._arg_var.endswith(name) prefix = self._arg_var[:-len(name)].split('.')[-1] - prefix = self._inflect_engine.singular_noun(prefix) + prefix = self._pluralizer.singular(prefix) name = prefix + name[2:] name = name.replace('.', '-') opt_name = self._build_option_name(name) # some schema name may contain $ @@ -392,7 +392,7 @@ def get_singular_options(self): # Disable singular options by default # if isinstance(self.schema, CMDArraySchema): # opt_name = self._build_option_name(self.schema.name.replace('$', '')) # some schema name may contain $ - # singular_opt_name = self._inflect_engine.singular_noun(opt_name) or opt_name + # singular_opt_name = self._pluralizer.singular(opt_name) or opt_name # if singular_opt_name != opt_name: # return [singular_opt_name, ] return None diff --git a/src/aaz_dev/command/model/configuration/_xml.py b/src/aaz_dev/command/model/configuration/_xml.py index 69ad4991..c9655241 100644 --- a/src/aaz_dev/command/model/configuration/_xml.py +++ b/src/aaz_dev/command/model/configuration/_xml.py @@ -1,4 +1,4 @@ -import inflect +from pluralizer import Pluralizer import re from lxml.builder import ElementMaker @@ -49,7 +49,7 @@ def build_xml(primitive, parent=None): if parent is None: parent = getattr(ElementMaker(), XML_ROOT)() # normalize element name - if elem_name := _inflect_engine.singular_noun(parent.tag): + if elem_name := _pluralizer.singular(parent.tag): parent.tag = elem_name for field_name, data in primitive.items(): primitive_to_xml(field_name, data, parent) @@ -87,7 +87,7 @@ def build_model(model, primitive): # obtain suitable element name if serialized_name in primitive: curr_name = serialized_name - elif (elem_name := _inflect_engine.singular_noun(serialized_name)) in primitive: + elif (elem_name := _pluralizer.singular(serialized_name)) in primitive: curr_name = elem_name else: continue @@ -137,4 +137,4 @@ def _unwrap(field): return field -_inflect_engine = inflect.engine() +_pluralizer = Pluralizer() diff --git a/src/aaz_dev/ps_profile.json b/src/aaz_dev/ps_profile.json new file mode 100644 index 00000000..feb009b4 --- /dev/null +++ b/src/aaz_dev/ps_profile.json @@ -0,0 +1,1073 @@ +{ + "AutonomousDatabase": { + "operations": { + "AutonomousDatabases_ListBySubscription": { + "tag": "AutonomousDatabases_ListBySubscription", + "delete": false, + "resource_id": "/subscriptions/{}/providers/oracle.database/autonomousdatabases", + "method": "get", + "variants": [ + { + "subject": "AutonomousDatabase", + "verb": "Get", + "variant": "BySubscription", + "action": "List" + } + ] + }, + "AutonomousDatabases_ListByResourceGroup": { + "tag": "AutonomousDatabases_ListByResourceGroup", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/autonomousdatabases", + "method": "get", + "variants": [ + { + "subject": "AutonomousDatabase", + "verb": "Get", + "variant": "ByResourceGroup", + "action": "List" + } + ] + }, + "AutonomousDatabases_CreateOrUpdate": { + "tag": "AutonomousDatabases_CreateOrUpdate", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/autonomousdatabases/{}", + "method": "put", + "variants": [ + { + "subject": "AutonomousDatabase", + "verb": "New", + "variant": "", + "action": "Create" + } + ] + }, + "AutonomousDatabases_Delete": { + "tag": "AutonomousDatabases_Delete", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/autonomousdatabases/{}", + "method": "delete", + "variants": [ + { + "subject": "AutonomousDatabase", + "verb": "Remove", + "variant": "", + "action": "Delete" + } + ] + }, + "AutonomousDatabases_Get": { + "tag": "AutonomousDatabases_Get", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/autonomousdatabases/{}", + "method": "get", + "variants": [ + { + "subject": "AutonomousDatabase", + "verb": "Get", + "variant": "", + "action": "Get" + } + ] + }, + "AutonomousDatabases_Update": { + "tag": "AutonomousDatabases_Update", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/autonomousdatabases/{}", + "method": "patch", + "variants": [ + { + "subject": "AutonomousDatabase", + "verb": "Update", + "variant": "", + "action": "Update" + } + ] + }, + "AutonomousDatabases_Failover": { + "tag": "AutonomousDatabases_Failover", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/autonomousdatabases/{}/failover", + "method": "post", + "variants": [ + { + "subject": "AutonomousDatabase", + "verb": "Set", + "variant": "", + "action": "Failover" + } + ] + }, + "AutonomousDatabases_Restore": { + "tag": "AutonomousDatabases_Restore", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/autonomousdatabases/{}/restore", + "method": "post", + "variants": [ + { + "subject": "AutonomousDatabase", + "verb": "Restore", + "variant": "", + "action": "Restore" + } + ] + } + }, + "delete": false + }, + "CloudExadataInfrastructure": { + "operations": { + "CloudExadataInfrastructures_ListBySubscription": { + "tag": "CloudExadataInfrastructures_ListBySubscription", + "delete": false, + "resource_id": "/subscriptions/{}/providers/oracle.database/cloudexadatainfrastructures", + "method": "get", + "variants": [ + { + "subject": "CloudExadataInfrastructure", + "verb": "Get", + "variant": "BySubscription", + "action": "List" + } + ] + }, + "CloudExadataInfrastructures_ListByResourceGroup": { + "tag": "CloudExadataInfrastructures_ListByResourceGroup", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/cloudexadatainfrastructures", + "method": "get", + "variants": [ + { + "subject": "CloudExadataInfrastructure", + "verb": "Get", + "variant": "ByResourceGroup", + "action": "List" + } + ] + }, + "CloudExadataInfrastructures_CreateOrUpdate": { + "tag": "CloudExadataInfrastructures_CreateOrUpdate", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/cloudexadatainfrastructures/{}", + "method": "put", + "variants": [ + { + "subject": "CloudExadataInfrastructure", + "verb": "New", + "variant": "", + "action": "Create" + } + ] + }, + "CloudExadataInfrastructures_Delete": { + "tag": "CloudExadataInfrastructures_Delete", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/cloudexadatainfrastructures/{}", + "method": "delete", + "variants": [ + { + "subject": "CloudExadataInfrastructure", + "verb": "Remove", + "variant": "", + "action": "Delete" + } + ] + }, + "CloudExadataInfrastructures_Get": { + "tag": "CloudExadataInfrastructures_Get", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/cloudexadatainfrastructures/{}", + "method": "get", + "variants": [ + { + "subject": "CloudExadataInfrastructure", + "verb": "Get", + "variant": "", + "action": "Get" + } + ] + }, + "CloudExadataInfrastructures_Update": { + "tag": "CloudExadataInfrastructures_Update", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/cloudexadatainfrastructures/{}", + "method": "patch", + "variants": [ + { + "subject": "CloudExadataInfrastructure", + "verb": "Update", + "variant": "", + "action": "Update" + } + ] + } + }, + "delete": false + }, + "CloudVmCluster": { + "operations": { + "CloudVmClusters_ListBySubscription": { + "tag": "CloudVmClusters_ListBySubscription", + "delete": false, + "resource_id": "/subscriptions/{}/providers/oracle.database/cloudvmclusters", + "method": "get", + "variants": [ + { + "subject": "CloudVmCluster", + "verb": "Get", + "variant": "BySubscription", + "action": "List" + } + ] + }, + "CloudVmClusters_ListByResourceGroup": { + "tag": "CloudVmClusters_ListByResourceGroup", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/cloudvmclusters", + "method": "get", + "variants": [ + { + "subject": "CloudVmCluster", + "verb": "Get", + "variant": "ByResourceGroup", + "action": "List" + } + ] + }, + "CloudVmClusters_CreateOrUpdate": { + "tag": "CloudVmClusters_CreateOrUpdate", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/cloudvmclusters/{}", + "method": "put", + "variants": [ + { + "subject": "CloudVmCluster", + "verb": "New", + "variant": "", + "action": "Create" + } + ] + }, + "CloudVmClusters_Delete": { + "tag": "CloudVmClusters_Delete", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/cloudvmclusters/{}", + "method": "delete", + "variants": [ + { + "subject": "CloudVmCluster", + "verb": "Remove", + "variant": "", + "action": "Delete" + } + ] + }, + "CloudVmClusters_Get": { + "tag": "CloudVmClusters_Get", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/cloudvmclusters/{}", + "method": "get", + "variants": [ + { + "subject": "CloudVmCluster", + "verb": "Get", + "variant": "", + "action": "Get" + } + ] + }, + "CloudVmClusters_Update": { + "tag": "CloudVmClusters_Update", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/cloudvmclusters/{}", + "method": "patch", + "variants": [ + { + "subject": "CloudVmCluster", + "verb": "Update", + "variant": "", + "action": "Update" + } + ] + } + }, + "delete": false + }, + "AutonomousDatabaseCharacterSet": { + "operations": { + "AutonomousDatabaseCharacterSets_ListByLocation": { + "tag": "AutonomousDatabaseCharacterSets_ListByLocation", + "delete": false, + "resource_id": "/subscriptions/{}/providers/oracle.database/locations/{}/autonomousdatabasecharactersets", + "method": "get", + "variants": [ + { + "subject": "AutonomousDatabaseCharacterSet", + "verb": "Get", + "variant": "ByLocation", + "action": "List" + } + ] + }, + "AutonomousDatabaseCharacterSets_Get": { + "tag": "AutonomousDatabaseCharacterSets_Get", + "delete": true, + "resource_id": "/subscriptions/{}/providers/oracle.database/locations/{}/autonomousdatabasecharactersets/{}", + "method": "get", + "variants": [ + { + "subject": "AutonomousDatabaseCharacterSet", + "verb": "Get", + "variant": "", + "action": "Get" + } + ] + } + }, + "delete": false + }, + "AutonomousDatabaseNationalCharacterSet": { + "operations": { + "AutonomousDatabaseNationalCharacterSets_ListByLocation": { + "tag": "AutonomousDatabaseNationalCharacterSets_ListByLocation", + "delete": false, + "resource_id": "/subscriptions/{}/providers/oracle.database/locations/{}/autonomousdatabasenationalcharactersets", + "method": "get", + "variants": [ + { + "subject": "AutonomousDatabaseNationalCharacterSet", + "verb": "Get", + "variant": "ByLocation", + "action": "List" + } + ] + }, + "AutonomousDatabaseNationalCharacterSets_Get": { + "tag": "AutonomousDatabaseNationalCharacterSets_Get", + "delete": true, + "resource_id": "/subscriptions/{}/providers/oracle.database/locations/{}/autonomousdatabasenationalcharactersets/{}", + "method": "get", + "variants": [ + { + "subject": "AutonomousDatabaseNationalCharacterSet", + "verb": "Get", + "variant": "", + "action": "Get" + } + ] + } + }, + "delete": false + }, + "AutonomousDatabaseVersion": { + "operations": { + "AutonomousDatabaseVersions_ListByLocation": { + "tag": "AutonomousDatabaseVersions_ListByLocation", + "delete": false, + "resource_id": "/subscriptions/{}/providers/oracle.database/locations/{}/autonomousdbversions", + "method": "get", + "variants": [ + { + "subject": "AutonomousDatabaseVersion", + "verb": "Get", + "variant": "ByLocation", + "action": "List" + } + ] + }, + "AutonomousDatabaseVersions_Get": { + "tag": "AutonomousDatabaseVersions_Get", + "delete": true, + "resource_id": "/subscriptions/{}/providers/oracle.database/locations/{}/autonomousdbversions/{}", + "method": "get", + "variants": [ + { + "subject": "AutonomousDatabaseVersion", + "verb": "Get", + "variant": "", + "action": "Get" + } + ] + } + }, + "delete": false + }, + "DbSystemShape": { + "operations": { + "DbSystemShapes_ListByLocation": { + "tag": "DbSystemShapes_ListByLocation", + "delete": false, + "resource_id": "/subscriptions/{}/providers/oracle.database/locations/{}/dbsystemshapes", + "method": "get", + "variants": [ + { + "subject": "DbSystemShape", + "verb": "Get", + "variant": "ByLocation", + "action": "List" + } + ] + }, + "DbSystemShapes_Get": { + "tag": "DbSystemShapes_Get", + "delete": true, + "resource_id": "/subscriptions/{}/providers/oracle.database/locations/{}/dbsystemshapes/{}", + "method": "get", + "variants": [ + { + "subject": "DbSystemShape", + "verb": "Get", + "variant": "", + "action": "Get" + } + ] + } + }, + "delete": false + }, + "DnsPrivateView": { + "operations": { + "DnsPrivateViews_ListByLocation": { + "tag": "DnsPrivateViews_ListByLocation", + "delete": false, + "resource_id": "/subscriptions/{}/providers/oracle.database/locations/{}/dnsprivateviews", + "method": "get", + "variants": [ + { + "subject": "DnsPrivateView", + "verb": "Get", + "variant": "ByLocation", + "action": "List" + } + ] + }, + "DnsPrivateViews_Get": { + "tag": "DnsPrivateViews_Get", + "delete": true, + "resource_id": "/subscriptions/{}/providers/oracle.database/locations/{}/dnsprivateviews/{}", + "method": "get", + "variants": [ + { + "subject": "DnsPrivateView", + "verb": "Get", + "variant": "", + "action": "Get" + } + ] + } + }, + "delete": false + }, + "DnsPrivateZone": { + "operations": { + "DnsPrivateZones_ListByLocation": { + "tag": "DnsPrivateZones_ListByLocation", + "delete": false, + "resource_id": "/subscriptions/{}/providers/oracle.database/locations/{}/dnsprivatezones", + "method": "get", + "variants": [ + { + "subject": "DnsPrivateZone", + "verb": "Get", + "variant": "ByLocation", + "action": "List" + } + ] + }, + "DnsPrivateZones_Get": { + "tag": "DnsPrivateZones_Get", + "delete": true, + "resource_id": "/subscriptions/{}/providers/oracle.database/locations/{}/dnsprivatezones/{}", + "method": "get", + "variants": [ + { + "subject": "DnsPrivateZone", + "verb": "Get", + "variant": "", + "action": "Get" + } + ] + } + }, + "delete": false + }, + "GiVersion": { + "operations": { + "GiVersions_ListByLocation": { + "tag": "GiVersions_ListByLocation", + "delete": false, + "resource_id": "/subscriptions/{}/providers/oracle.database/locations/{}/giversions", + "method": "get", + "variants": [ + { + "subject": "GiVersion", + "verb": "Get", + "variant": "ByLocation", + "action": "List" + } + ] + }, + "GiVersions_Get": { + "tag": "GiVersions_Get", + "delete": true, + "resource_id": "/subscriptions/{}/providers/oracle.database/locations/{}/giversions/{}", + "method": "get", + "variants": [ + { + "subject": "GiVersion", + "verb": "Get", + "variant": "", + "action": "Get" + } + ] + } + }, + "delete": false + }, + "SystemVersion": { + "operations": { + "SystemVersions_ListByLocation": { + "tag": "SystemVersions_ListByLocation", + "delete": true, + "resource_id": "/subscriptions/{}/providers/oracle.database/locations/{}/systemversions", + "method": "get", + "variants": [ + { + "subject": "SystemVersion", + "verb": "Get", + "variant": "ByLocation", + "action": "List" + } + ] + }, + "SystemVersions_Get": { + "tag": "SystemVersions_Get", + "delete": true, + "resource_id": "/subscriptions/{}/providers/oracle.database/locations/{}/systemversions/{}", + "method": "get", + "variants": [ + { + "subject": "SystemVersion", + "verb": "Get", + "variant": "", + "action": "Get" + } + ] + } + }, + "delete": true + }, + "OracleSubscription": { + "operations": { + "OracleSubscriptions_ListBySubscription": { + "tag": "OracleSubscriptions_ListBySubscription", + "delete": true, + "resource_id": "/subscriptions/{}/providers/oracle.database/oraclesubscriptions", + "method": "get", + "variants": [ + { + "subject": "OracleSubscription", + "verb": "Get", + "variant": "BySubscription", + "action": "List" + } + ] + }, + "OracleSubscriptions_Get": { + "tag": "OracleSubscriptions_Get", + "delete": true, + "resource_id": "/subscriptions/{}/providers/oracle.database/oraclesubscriptions/default", + "method": "get", + "variants": [ + { + "subject": "OracleSubscription", + "verb": "Get", + "variant": "", + "action": "Get" + } + ] + }, + "OracleSubscriptions_CreateOrUpdate": { + "tag": "OracleSubscriptions_CreateOrUpdate", + "delete": true, + "resource_id": "/subscriptions/{}/providers/oracle.database/oraclesubscriptions/default", + "method": "put", + "variants": [ + { + "subject": "OracleSubscription", + "verb": "New", + "variant": "", + "action": "Create" + }, + { + "subject": "OracleSubscription", + "verb": "Update", + "variant": "", + "action": "Update" + } + ] + }, + "OracleSubscriptions_Update": { + "tag": "OracleSubscriptions_Update", + "delete": true, + "resource_id": "/subscriptions/{}/providers/oracle.database/oraclesubscriptions/default", + "method": "patch", + "variants": [ + { + "subject": "OracleSubscription", + "verb": "Update", + "variant": "", + "action": "Update" + } + ] + }, + "OracleSubscriptions_Delete": { + "tag": "OracleSubscriptions_Delete", + "delete": true, + "resource_id": "/subscriptions/{}/providers/oracle.database/oraclesubscriptions/default", + "method": "delete", + "variants": [ + { + "subject": "OracleSubscription", + "verb": "Remove", + "variant": "", + "action": "Delete" + } + ] + } + }, + "delete": true + }, + "OracleSubscriptionAzureSubscription": { + "operations": { + "OracleSubscriptions_AddAzureSubscriptions": { + "tag": "OracleSubscriptions_AddAzureSubscriptions", + "delete": true, + "resource_id": "/subscriptions/{}/providers/oracle.database/oraclesubscriptions/default/addazuresubscriptions", + "method": "post", + "variants": [ + { + "subject": "OracleSubscriptionAzureSubscription", + "verb": "Add", + "variant": "", + "action": "Add" + } + ] + } + }, + "delete": true + }, + "OracleSubscriptionActivationLink": { + "operations": { + "OracleSubscriptions_ListActivationLinks": { + "tag": "OracleSubscriptions_ListActivationLinks", + "delete": true, + "resource_id": "/subscriptions/{}/providers/oracle.database/oraclesubscriptions/default/listactivationlinks", + "method": "post", + "variants": [ + { + "subject": "OracleSubscriptionActivationLink", + "verb": "Get", + "variant": "", + "action": "List" + } + ] + } + }, + "delete": true + }, + "OracleSubscriptionCloudAccountDetail": { + "operations": { + "OracleSubscriptions_ListCloudAccountDetails": { + "tag": "OracleSubscriptions_ListCloudAccountDetails", + "delete": true, + "resource_id": "/subscriptions/{}/providers/oracle.database/oraclesubscriptions/default/listcloudaccountdetails", + "method": "post", + "variants": [ + { + "subject": "OracleSubscriptionCloudAccountDetail", + "verb": "Get", + "variant": "", + "action": "List" + } + ] + } + }, + "delete": true + }, + "OracleSubscriptionSaaSubscriptionDetail": { + "operations": { + "OracleSubscriptions_ListSaasSubscriptionDetails": { + "tag": "OracleSubscriptions_ListSaasSubscriptionDetails", + "delete": true, + "resource_id": "/subscriptions/{}/providers/oracle.database/oraclesubscriptions/default/listsaassubscriptiondetails", + "method": "post", + "variants": [ + { + "subject": "OracleSubscriptionSaaSubscriptionDetail", + "verb": "Get", + "variant": "", + "action": "List" + } + ] + } + }, + "delete": true + }, + "AutonomousDatabaseBackup": { + "operations": { + "AutonomousDatabaseBackups_ListByAutonomousDatabase": { + "tag": "AutonomousDatabaseBackups_ListByAutonomousDatabase", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/autonomousdatabases/{}/autonomousdatabasebackups", + "method": "get", + "variants": [ + { + "subject": "AutonomousDatabaseBackup", + "verb": "Get", + "variant": "ByAutonomousDatabase", + "action": "List" + } + ] + }, + "AutonomousDatabaseBackups_CreateOrUpdate": { + "tag": "AutonomousDatabaseBackups_CreateOrUpdate", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/autonomousdatabases/{}/autonomousdatabasebackups/{}", + "method": "put", + "variants": [ + { + "subject": "AutonomousDatabaseBackup", + "verb": "New", + "variant": "", + "action": "Create" + } + ] + }, + "AutonomousDatabaseBackups_Delete": { + "tag": "AutonomousDatabaseBackups_Delete", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/autonomousdatabases/{}/autonomousdatabasebackups/{}", + "method": "delete", + "variants": [ + { + "subject": "AutonomousDatabaseBackup", + "verb": "Remove", + "variant": "", + "action": "Delete" + } + ] + }, + "AutonomousDatabaseBackups_Get": { + "tag": "AutonomousDatabaseBackups_Get", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/autonomousdatabases/{}/autonomousdatabasebackups/{}", + "method": "get", + "variants": [ + { + "subject": "AutonomousDatabaseBackup", + "verb": "Get", + "variant": "", + "action": "Get" + } + ] + }, + "AutonomousDatabaseBackups_Update": { + "tag": "AutonomousDatabaseBackups_Update", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/autonomousdatabases/{}/autonomousdatabasebackups/{}", + "method": "patch", + "variants": [ + { + "subject": "AutonomousDatabaseBackup", + "verb": "Update", + "variant": "", + "action": "Update" + } + ] + } + }, + "delete": false + }, + "AutonomousDatabaseWallet": { + "operations": { + "AutonomousDatabases_GenerateWallet": { + "tag": "AutonomousDatabases_GenerateWallet", + "delete": true, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/autonomousdatabases/{}/generatewallet", + "method": "post", + "variants": [ + { + "subject": "AutonomousDatabaseWallet", + "verb": "New", + "variant": "", + "action": "Generate" + } + ] + } + }, + "delete": true + }, + "ShrinkAutonomousDatabase": { + "operations": { + "AutonomousDatabases_Shrink": { + "tag": "AutonomousDatabases_Shrink", + "delete": true, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/autonomousdatabases/{}/shrink", + "method": "post", + "variants": [ + { + "subject": "ShrinkAutonomousDatabase", + "verb": "Invoke", + "variant": "", + "action": "Shrink" + } + ] + } + }, + "delete": true + }, + "SwitchoverAutonomousDatabase": { + "operations": { + "AutonomousDatabases_Switchover": { + "tag": "AutonomousDatabases_Switchover", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/autonomousdatabases/{}/switchover", + "method": "post", + "variants": [ + { + "subject": "SwitchoverAutonomousDatabase", + "verb": "Invoke", + "variant": "", + "action": "Switchover" + } + ] + } + }, + "delete": false + }, + "CloudExadataInfrastructureStorageCapacity": { + "operations": { + "CloudExadataInfrastructures_AddStorageCapacity": { + "tag": "CloudExadataInfrastructures_AddStorageCapacity", + "delete": true, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/cloudexadatainfrastructures/{}/addstoragecapacity", + "method": "post", + "variants": [ + { + "subject": "CloudExadataInfrastructureStorageCapacity", + "verb": "Add", + "variant": "", + "action": "Add" + } + ] + } + }, + "delete": true + }, + "DbServer": { + "operations": { + "DbServers_ListByCloudExadataInfrastructure": { + "tag": "DbServers_ListByCloudExadataInfrastructure", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/cloudexadatainfrastructures/{}/dbservers", + "method": "get", + "variants": [ + { + "subject": "DbServer", + "verb": "Get", + "variant": "ByCloudExadataInfrastructure", + "action": "List" + } + ] + }, + "DbServers_Get": { + "tag": "DbServers_Get", + "delete": true, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/cloudexadatainfrastructures/{}/dbservers/{}", + "method": "get", + "variants": [ + { + "subject": "DbServer", + "verb": "Get", + "variant": "", + "action": "Get" + } + ] + } + }, + "delete": false + }, + "CloudVmClusterVm": { + "operations": { + "CloudVmClusters_AddVms": { + "tag": "CloudVmClusters_AddVms", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/cloudvmclusters/{}/addvms", + "method": "post", + "variants": [ + { + "subject": "CloudVmClusterVm", + "verb": "Add", + "variant": "", + "action": "Add" + } + ] + }, + "CloudVmClusters_RemoveVms": { + "tag": "CloudVmClusters_RemoveVms", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/cloudvmclusters/{}/removevms", + "method": "post", + "variants": [ + { + "subject": "CloudVmClusterVm", + "verb": "Remove", + "variant": "", + "action": "Remove" + } + ] + } + }, + "delete": false + }, + "DbNode": { + "operations": { + "DbNodes_ListByCloudVmCluster": { + "tag": "DbNodes_ListByCloudVmCluster", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/cloudvmclusters/{}/dbnodes", + "method": "get", + "variants": [ + { + "subject": "DbNode", + "verb": "Get", + "variant": "ByCloudVmCluster", + "action": "List" + } + ] + }, + "DbNodes_Get": { + "tag": "DbNodes_Get", + "delete": true, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/cloudvmclusters/{}/dbnodes/{}", + "method": "get", + "variants": [ + { + "subject": "DbNode", + "verb": "Get", + "variant": "", + "action": "Get" + } + ] + } + }, + "delete": false + }, + "ActionDbNode": { + "operations": { + "DbNodes_Action": { + "tag": "DbNodes_Action", + "delete": false, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/cloudvmclusters/{}/dbnodes/{}/action", + "method": "post", + "variants": [ + { + "subject": "ActionDbNode", + "verb": "Invoke", + "variant": "", + "action": "Action" + } + ] + } + }, + "delete": false + }, + "CloudVmClusterPrivateIpAddress": { + "operations": { + "CloudVmClusters_ListPrivateIpAddresses": { + "tag": "CloudVmClusters_ListPrivateIpAddresses", + "delete": true, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/cloudvmclusters/{}/listprivateipaddresses", + "method": "post", + "variants": [ + { + "subject": "CloudVmClusterPrivateIpAddress", + "verb": "Get", + "variant": "", + "action": "List" + } + ] + } + }, + "delete": true + }, + "VirtualNetworkAddress": { + "operations": { + "VirtualNetworkAddresses_ListByCloudVmCluster": { + "tag": "VirtualNetworkAddresses_ListByCloudVmCluster", + "delete": true, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/cloudvmclusters/{}/virtualnetworkaddresses", + "method": "get", + "variants": [ + { + "subject": "VirtualNetworkAddress", + "verb": "Get", + "variant": "ByCloudVmCluster", + "action": "List" + } + ] + }, + "VirtualNetworkAddresses_Get": { + "tag": "VirtualNetworkAddresses_Get", + "delete": true, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/cloudvmclusters/{}/virtualnetworkaddresses/{}", + "method": "get", + "variants": [ + { + "subject": "VirtualNetworkAddress", + "verb": "Get", + "variant": "", + "action": "Get" + } + ] + }, + "VirtualNetworkAddresses_CreateOrUpdate": { + "tag": "VirtualNetworkAddresses_CreateOrUpdate", + "delete": true, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/cloudvmclusters/{}/virtualnetworkaddresses/{}", + "method": "put", + "variants": [ + { + "subject": "VirtualNetworkAddress", + "verb": "New", + "variant": "", + "action": "Create" + }, + { + "subject": "VirtualNetworkAddress", + "verb": "Update", + "variant": "", + "action": "Update" + } + ] + }, + "VirtualNetworkAddresses_Delete": { + "tag": "VirtualNetworkAddresses_Delete", + "delete": true, + "resource_id": "/subscriptions/{}/resourcegroups/{}/providers/oracle.database/cloudvmclusters/{}/virtualnetworkaddresses/{}", + "method": "delete", + "variants": [ + { + "subject": "VirtualNetworkAddress", + "verb": "Remove", + "variant": "", + "action": "Delete" + } + ] + } + }, + "delete": true + } +} \ No newline at end of file diff --git a/src/aaz_dev/swagger/controller/command_generator.py b/src/aaz_dev/swagger/controller/command_generator.py index 3ca5a792..ec410059 100644 --- a/src/aaz_dev/swagger/controller/command_generator.py +++ b/src/aaz_dev/swagger/controller/command_generator.py @@ -1,7 +1,7 @@ import logging import re -import inflect +from pluralizer import Pluralizer from abc import abstractmethod, ABC from command.model.configuration import CMDCommandGroup, CMDCommand, CMDHttpOperation, CMDHttpRequest, \ @@ -25,7 +25,7 @@ class _CommandGenerator(ABC): - _inflect_engine = inflect.engine() + _pluralizer = Pluralizer() @staticmethod def generate_command_version(resource): @@ -161,7 +161,7 @@ def generate_command_group_name_by_resource(cls, resource_path, rp_name): part = re.sub(r"\{[^{}]*}", '', part) part = re.sub(r"[^a-zA-Z0-9\-._]", '', part) name = camel_case_to_snake_case(part, '-') - singular_name = cls._inflect_engine.singular_noun(name) or name + singular_name = cls._pluralizer.singular(name) or name names.append(singular_name) return " ".join([name for name in names if name]) diff --git a/src/aaz_dev/swagger/model/specs/_resource.py b/src/aaz_dev/swagger/model/specs/_resource.py index 674c92bb..3a8703d0 100644 --- a/src/aaz_dev/swagger/model/specs/_resource.py +++ b/src/aaz_dev/swagger/model/specs/_resource.py @@ -4,7 +4,7 @@ import os import re -import inflect +from pluralizer import Pluralizer from fuzzywuzzy import fuzz from command.model.configuration import CMDResource @@ -17,7 +17,7 @@ class Resource: _CAMEL_CASE_PATTERN = re.compile(r"^([a-zA-Z][a-z0-9]+)(([A-Z][a-z0-9]*)+)$") - _inflect_engine = inflect.engine() + _pluralizer = Pluralizer() def __init__(self, resource_id, path, version, file_path, resource_provider, body): self.path = path @@ -98,22 +98,22 @@ def _parse_operation_group_name(self, op_id, method): words = [] for part in self.id.split('?')[0].split('/'): if part == '{}' and len(words): - singular = self._inflect_engine.singular_noun(words[-1]) + singular = self._pluralizer.singular(words[-1]) if singular: words[-1] = singular else: words.append(part.replace('_', "")) - op_group_singular = self._inflect_engine.singular_noun(op_group_name) or op_group_name + op_group_singular = self._pluralizer.singular(op_group_name) or op_group_name words.reverse() # search from tail for word in words: - word_singular = self._inflect_engine.singular_noun(word) or word + word_singular = self._pluralizer.singular(word) or word if len(word_singular) > 1 and op_group_singular.lower().endswith(word_singular.lower()): if word == word_singular: # use singular op_group_name = op_group_singular elif word != word_singular: # use plural - op_group_plural = self._inflect_engine.plural_noun(op_group_singular) + op_group_plural = self._pluralizer.singular(op_group_singular) if op_group_plural is not False: op_group_name = op_group_plural break diff --git a/src/aaz_dev/swagger/model/specs/_resource_provider.py b/src/aaz_dev/swagger/model/specs/_resource_provider.py index 20e85578..78f57f75 100644 --- a/src/aaz_dev/swagger/model/specs/_resource_provider.py +++ b/src/aaz_dev/swagger/model/specs/_resource_provider.py @@ -73,6 +73,29 @@ def get_resource_map_by_tag(self, tag): ): resource_map[resource.id][resource.version] = resource return resource_map + + @property + def default_tag(self): + if self._readme_path is None: + return None + + with open(self._readme_path, 'r', encoding='utf-8') as f: + readme = f.read() + lines = readme.split('\n') + for i in range(len(lines)): + line = lines[i] + if line.startswith('### Basic Information'): + lines = lines[i+1:] + break + latest_tag = None + for i in range(len(lines)): + line = lines[i] + if line.startswith('##'): + break + if line.startswith('tag:'): + latest_tag = line.split(':')[-1].strip() + break + return latest_tag @property def tags(self):