diff --git a/src/containerapp/azext_containerapp/_help.py b/src/containerapp/azext_containerapp/_help.py index 3a5fa25e5dc..cb5126c3f61 100644 --- a/src/containerapp/azext_containerapp/_help.py +++ b/src/containerapp/azext_containerapp/_help.py @@ -145,13 +145,13 @@ az containerapp revision deactivate -n MyContainerapp -g MyResourceGroup --revision-name MyContainerappRevision """ -helps['containerapp revision mode set'] = """ +helps['containerapp revision set-mode'] = """ type: command short-summary: Set the revision mode of a container app. examples: - name: Set a container app to single revision mode. text: | - az containerapp revision mode set-n MyContainerapp -g MyResourceGroup --mode Single + az containerapp revision set-mode -n MyContainerapp -g MyResourceGroup --mode Single """ helps['containerapp revision copy'] = """ @@ -164,15 +164,6 @@ --from-revision PreviousRevisionName --cpu 0.75 --memory 1.5Gi """ -helps['containerapp revision mode set'] = """ - type: command - short-summary: Set the revision mode of a Containerapp. - examples: - - name: Set the revision mode of a Containerapp. - text: | - az containerapp revision set --mode Single -n MyContainerapp -g MyResourceGroup -""" - helps['containerapp revision copy'] = """ type: command short-summary: Create a revision based on a previous revision. @@ -238,6 +229,47 @@ az containerapp env list -g MyResourceGroup """ +helps['containerapp env dapr-component'] = """ + type: group + short-summary: Commands to manage Container App environment dapr components. +""" + +helps['containerapp env dapr-component list'] = """ + type: command + short-summary: List dapr components for a Containerapp environment. + examples: + - name: List dapr components for a Containerapp environment. + text: | + az containerapp env dapr-component list -g MyResourceGroup --environment-name MyEnvironment +""" + +helps['containerapp env dapr-component show'] = """ + type: command + short-summary: Show the details of a dapr component. + examples: + - name: Show the details of a dapr component. + text: | + az containerapp env dapr-component show -g MyResourceGroup --dapr-component-name MyDaprComponenetName --environment-name MyEnvironment +""" + +helps['containerapp env dapr-component set'] = """ + type: command + short-summary: Create or update a dapr component. + examples: + - name: Create a dapr component. + text: | + az containerapp env dapr-component set -g MyResourceGroup --environment-name MyEnv --yaml MyYAMLPath --name MyDaprName +""" + +helps['containerapp env dapr-component remove'] = """ + type: command + short-summary: Remove a dapr componenet from a Containerapp environment. + examples: + - name: Remove a dapr componenet from a Containerapp environment. + text: | + az containerapp env dapr-component remove -g MyResourceGroup --dapr-component-name MyDaprComponenetName --environment-name MyEnvironment +""" + # Identity Commands helps['containerapp identity'] = """ type: group @@ -374,13 +406,13 @@ """ -helps['containerapp registry delete'] = """ +helps['containerapp registry remove'] = """ type: command short-summary: Remove a container registry's details. examples: - name: Remove a registry from a Containerapp. text: | - az containerapp registry delete -n MyContainerapp -g MyResourceGroup --server MyContainerappRegistry.azurecr.io + az containerapp registry remove -n MyContainerapp -g MyResourceGroup --server MyContainerappRegistry.azurecr.io """ # Secret Commands @@ -407,13 +439,13 @@ az containerapp secret list -n MyContainerapp -g MyResourceGroup """ -helps['containerapp secret delete'] = """ +helps['containerapp secret remove'] = """ type: command - short-summary: Delete secrets from a container app. + short-summary: Remove secrets from a container app. examples: - - name: Delete secrets from a container app. + - name: Remove secrets from a container app. text: | - az containerapp secret delete -n MyContainerapp -g MyResourceGroup --secret-names MySecret MySecret2 + az containerapp secret remove -n MyContainerapp -g MyResourceGroup --secret-names MySecret MySecret2 """ helps['containerapp secret set'] = """ @@ -504,39 +536,3 @@ text: | az containerapp dapr disable -n MyContainerapp -g MyResourceGroup """ - -helps['containerapp dapr list'] = """ - type: command - short-summary: List Dapr components. - examples: - - name: List Dapr components for a Container Apps environment. - text: | - az containerapp dapr list -g MyResourceGroup --environment-name MyEnvironment -""" - -helps['containerapp dapr show'] = """ - type: command - short-summary: Show the details of a Dapr component. - examples: - - name: Show the details of a Dapr component. - text: | - az containerapp dapr show -g MyResourceGroup --dapr-component-name MyDaprComponenetName --environment-name MyEnvironment -""" - -helps['containerapp dapr set'] = """ - type: command - short-summary: Create or update a Dapr component. - examples: - - name: Create a Dapr component. - text: | - az containerapp dapr set -g MyResourceGroup --environment-name MyEnv --yaml my-component.yaml --name MyDaprName -""" - -helps['containerapp dapr remove'] = """ - type: command - short-summary: Remove a Dapr component. - examples: - - name: Remove a Dapr component. - text: | - az containerapp dapr delete -g MyResourceGroup --dapr-component-name MyDaprComponenetName --environment-name MyEnvironment -""" \ No newline at end of file diff --git a/src/containerapp/azext_containerapp/_params.py b/src/containerapp/azext_containerapp/_params.py index 85ee7f4239e..1d3e3b6dc27 100644 --- a/src/containerapp/azext_containerapp/_params.py +++ b/src/containerapp/azext_containerapp/_params.py @@ -40,6 +40,13 @@ def load_arguments(self, _): c.argument('args', nargs='*', options_list=['--args'], help="A list of container startup command argument(s). Space-separated values e.g. \"-c\" \"mycommand\". Empty string to clear existing values") c.argument('revision_suffix', type=str, options_list=['--revision-suffix'], help='User friendly suffix that is appended to the revision name') + # Env vars + with self.argument_context('containerapp', arg_group='Environment variables (Creates new revision)') as c: + c.argument('set_env_vars', options_list=['--set-env-vars, --env-vars'], nargs='*', help="A list of environment variable(s) to add to the container. Space-separated values in 'key=value' format. If stored as a secret, value must start with \'secretref:\' followed by the secret name.") + c.argument('remove_env_vars', nargs='*', help="A list of environment variable(s) to remove from container. Space-separated env var name values.") + c.argument('replace_env_vars', nargs='*', help="A list of environment variable(s) to replace from the container. Space-separated values in 'key=value' format. If stored as a secret, value must start with \'secretref:\' followed by the secret name.") + c.argument('remove_all_env_vars', help="Option to remove all environment variable(s) from the container.") + # Scale with self.argument_context('containerapp', arg_group='Scale (Creates new revision)') as c: c.argument('min_replicas', type=int, options_list=['--min-replicas'], help="The minimum number of replicas.") @@ -147,12 +154,21 @@ def load_arguments(self, _): with self.argument_context('containerapp secret set') as c: c.argument('secrets', nargs='+', options_list=['--secrets', '-s'], help="A list of secret(s) for the container app. Space-separated values in 'key=value' format.") - with self.argument_context('containerapp secret delete') as c: + with self.argument_context('containerapp secret remove') as c: c.argument('secret_names', nargs='+', help="A list of secret(s) for the container app. Space-separated secret values names.") - with self.argument_context('containerapp dapr') as c: - c.argument('dapr_app_id', help="The Dapr app id.") - c.argument('dapr_app_port', help="The port Dapr uses to talk to the application.") - c.argument('dapr_app_protocol', help="The protocol Dapr uses to talk to the application. Allowed values: grpc, http.") - c.argument('dapr_component_name', help="The Dapr component name.") - c.argument('environment_name', help="The Container Apps environment name.") + with self.argument_context('containerapp env dapr-component') as c: + c.argument('dapr_app_id', help="The dapr app id.") + c.argument('dapr_app_port', help="The port of your app.") + c.argument('dapr_app_protocol', help="Tells Dapr which protocol your application is using. Allowed values: grpc, http.") + c.argument('dapr_component_name', help="The dapr component name.") + c.argument('environment_name', options_list=['--name','-n'], help="The environment name.") + + with self.argument_context('containerapp revision set-mode') as c: + c.argument('mode', arg_type=get_enum_type(['single', 'multiple']), help="The active revisions mode for the container app.") + + with self.argument_context('containerapp registry') as c: + c.argument('server', help="The container registry server, e.g. myregistry.azurecr.io") + c.argument('username', help='The username of the registry. If using Azure Container Registry, we will try to infer the credentials if not supplied') + c.argument('password', help='The password of the registry. If using Azure Container Registry, we will try to infer the credentials if not supplied') + diff --git a/src/containerapp/azext_containerapp/_utils.py b/src/containerapp/azext_containerapp/_utils.py index 297ce4904ba..1c5a10e5d29 100644 --- a/src/containerapp/azext_containerapp/_utils.py +++ b/src/containerapp/azext_containerapp/_utils.py @@ -116,6 +116,12 @@ def parse_secret_flags(secret_list): return secret_var_def +def _update_revision_env_secretrefs(containers, name): + for container in containers: + if "env" in container: + for var in container["env"]: + if "secretRef" in var: + var["secretRef"] = var["secretRef"].replace("{}-".format(name), "") def store_as_secret_and_return_secret_ref(secrets_list, registry_user, registry_server, registry_pass, update_existing_secret=False): if registry_pass.startswith("secretref:"): @@ -328,7 +334,7 @@ def _remove_secret(containerapp_def, secret_name): containerapp_def["properties"]["configuration"]["secrets"].pop(i) break -def _add_or_update_env_vars(existing_env_vars, new_env_vars): +def _add_or_update_env_vars(existing_env_vars, new_env_vars, is_add=False): for new_env_var in new_env_vars: # Check if updating existing env var @@ -336,6 +342,8 @@ def _add_or_update_env_vars(existing_env_vars, new_env_vars): for existing_env_var in existing_env_vars: if existing_env_var["name"].lower() == new_env_var["name"].lower(): is_existing = True + if is_add: + logger.warning("Environment variable {} already exists. Replacing environment variable value.".format(new_env_var["name"])) if "value" in new_env_var: existing_env_var["value"] = new_env_var["value"] @@ -350,8 +358,25 @@ def _add_or_update_env_vars(existing_env_vars, new_env_vars): # If not updating existing env var, add it as a new env var if not is_existing: + if not is_add: + logger.warning("Environment variable {} does not exist. Adding as new environment variable.".format(new_env_var["name"])) existing_env_vars.append(new_env_var) +def _remove_env_vars(existing_env_vars, remove_env_vars): + for old_env_var in remove_env_vars: + + # Check if updating existing env var + is_existing = False + for i in range(0, len(existing_env_vars)): + existing_env_var = existing_env_vars[i] + if existing_env_var["name"].lower() == old_env_var.lower(): + is_existing = True + existing_env_vars.pop(i) + break + + # If not updating existing env var, add it as a new env var + if not is_existing: + logger.warning("Environment variable {} does not exist.".format(old_env_var)) def _add_or_update_tags(containerapp_def, tags): if 'tags' not in containerapp_def: diff --git a/src/containerapp/azext_containerapp/commands.py b/src/containerapp/azext_containerapp/commands.py index 40e422bb532..9fd58c7575c 100644 --- a/src/containerapp/azext_containerapp/commands.py +++ b/src/containerapp/azext_containerapp/commands.py @@ -26,7 +26,7 @@ def transform_containerapp_list_output(apps): def transform_revision_output(rev): - props = ['name', 'replicas', 'active', 'createdTime'] + props = ['name', 'active', 'createdTime', 'trafficWeight'] result = {k: rev['properties'][k] for k in rev['properties'] if k in props} if 'name' in rev: @@ -50,7 +50,6 @@ def load_command_table(self, _): g.custom_command('update', 'update_containerapp', supports_no_wait=True, exception_handler=ex_handler_factory()) g.custom_command('delete', 'delete_containerapp', exception_handler=ex_handler_factory()) - with self.command_group('containerapp env') as g: g.custom_command('show', 'show_managed_environment') g.custom_command('list', 'list_managed_environments') @@ -58,13 +57,17 @@ def load_command_table(self, _): # g.custom_command('update', 'update_managed_environment', supports_no_wait=True, exception_handler=ex_handler_factory()) g.custom_command('delete', 'delete_managed_environment', supports_no_wait=True, confirmation=True, exception_handler=ex_handler_factory()) + with self.command_group('containerapp env dapr-component') as g: + g.custom_command('list', 'list_dapr_components') + g.custom_command('show', 'show_dapr_component') + g.custom_command('set', 'create_or_update_dapr_component') + g.custom_command('remove', 'remove_dapr_component') with self.command_group('containerapp identity') as g: g.custom_command('assign', 'assign_managed_identity', supports_no_wait=True, exception_handler=ex_handler_factory()) g.custom_command('remove', 'remove_managed_identity', supports_no_wait=True, exception_handler=ex_handler_factory()) g.custom_command('show', 'show_managed_identity') - with self.command_group('containerapp github-action') as g: g.custom_command('add', 'create_or_update_github_action', exception_handler=ex_handler_factory()) g.custom_command('show', 'show_github_action', exception_handler=ex_handler_factory()) @@ -77,9 +80,7 @@ def load_command_table(self, _): g.custom_command('restart', 'restart_revision') g.custom_command('show', 'show_revision', table_transformer=transform_revision_output, exception_handler=ex_handler_factory()) g.custom_command('copy', 'copy_revision', exception_handler=ex_handler_factory()) - - with self.command_group('containerapp revision mode') as g: - g.custom_command('set', 'set_revision_mode', exception_handler=ex_handler_factory()) + g.custom_command('set-mode', 'set_revision_mode', exception_handler=ex_handler_factory()) with self.command_group('containerapp ingress') as g: g.custom_command('enable', 'enable_ingress', exception_handler=ex_handler_factory()) @@ -94,19 +95,15 @@ def load_command_table(self, _): g.custom_command('set', 'set_registry', exception_handler=ex_handler_factory()) g.custom_command('show', 'show_registry') g.custom_command('list', 'list_registry') - g.custom_command('delete', 'delete_registry', exception_handler=ex_handler_factory()) + g.custom_command('remove', 'remove_registry', exception_handler=ex_handler_factory()) with self.command_group('containerapp secret') as g: g.custom_command('list', 'list_secrets') g.custom_command('show', 'show_secret') - g.custom_command('delete', 'delete_secrets', exception_handler=ex_handler_factory()) + g.custom_command('remove', 'remove_secrets', exception_handler=ex_handler_factory()) g.custom_command('set', 'set_secrets', exception_handler=ex_handler_factory()) with self.command_group('containerapp dapr') as g: g.custom_command('enable', 'enable_dapr', exception_handler=ex_handler_factory()) g.custom_command('disable', 'disable_dapr', exception_handler=ex_handler_factory()) - g.custom_command('list', 'list_dapr_components') - g.custom_command('show', 'show_dapr_component') - g.custom_command('set', 'create_or_update_dapr_component') - g.custom_command('remove', 'remove_dapr_component') diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index 1fcb7c2176b..961fbd500f0 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -45,7 +45,7 @@ _object_to_dict, _add_or_update_secrets, _remove_additional_attributes, _remove_readonly_attributes, _add_or_update_env_vars, _add_or_update_tags, update_nested_dictionary, _update_traffic_weights, _get_app_from_revision, raise_missing_token_suggestion, _infer_acr_credentials, _remove_registry_secret, _remove_secret, - _ensure_identity_resource_id, _remove_dapr_readonly_attributes, _registry_exists) + _ensure_identity_resource_id, _remove_dapr_readonly_attributes, _registry_exists, _remove_env_vars, _update_revision_env_secretrefs) logger = get_logger(__name__) @@ -126,8 +126,10 @@ def update_containerapp_yaml(cmd, name, resource_group_name, file_name, from_rev r = ContainerAppClient.show_revision(cmd=cmd, resource_group_name=resource_group_name, container_app_name=name, name=from_revision) except CLIError as e: handle_raw_exception(e) + + _update_revision_env_secretrefs(r["properties"]["template"]["containers"], name) current_containerapp_def["properties"]["template"] = r["properties"]["template"] - + # Deserialize the yaml into a ContainerApp object. Need this since we're not using SDK try: deserializer = create_deserializer() @@ -497,7 +499,10 @@ def update_containerapp(cmd, max_replicas=None, revisions_mode=None, secrets=None, - env_vars=None, + set_env_vars=None, + remove_env_vars=None, + replace_env_vars=None, + remove_all_env_vars=False, cpu=None, memory=None, registry_server=None, @@ -512,7 +517,7 @@ def update_containerapp(cmd, if yaml: if image or min_replicas or max_replicas or\ - revisions_mode or secrets or env_vars or cpu or memory or registry_server or\ + revisions_mode or secrets or set_env_vars or remove_env_vars or replace_env_vars or remove_all_env_vars or cpu or memory or registry_server or\ registry_user or registry_pass or\ startup_command or args or tags: logger.warning('Additional flags were passed along with --yaml. These flags will be ignored, and the configuration defined in the yaml will be used instead') @@ -539,7 +544,7 @@ def update_containerapp(cmd, update_map['secrets'] = secrets is not None update_map['registries'] = registry_server or registry_user or registry_pass update_map['scale'] = min_replicas or max_replicas - update_map['container'] = image or container_name or env_vars is not None or cpu or memory or startup_command is not None or args is not None + update_map['container'] = image or container_name or set_env_vars is not None or remove_env_vars is not None or replace_env_vars is not None or remove_all_env_vars or cpu or memory or startup_command is not None or args is not None update_map['configuration'] = update_map['secrets'] or update_map['registries'] or revisions_mode is not None if tags: @@ -564,13 +569,28 @@ def update_containerapp(cmd, if image is not None: c["image"] = image - if env_vars is not None: - if isinstance(env_vars, list) and not env_vars: + + if set_env_vars is not None: + if "env" not in c or not c["env"]: c["env"] = [] - else: - if "env" not in c or not c["env"]: - c["env"] = [] - _add_or_update_env_vars(c["env"], parse_env_var_flags(env_vars)) + # env vars + _add_or_update_env_vars(c["env"], parse_env_var_flags(set_env_vars), is_add=True) + + if replace_env_vars is not None: + if "env" not in c or not c["env"]: + c["env"] = [] + # env vars + _add_or_update_env_vars(c["env"], parse_env_var_flags(replace_env_vars)) + + if remove_env_vars is not None: + if "env" not in c or not c["env"]: + c["env"] = [] + # env vars + _remove_env_vars(c["env"], remove_env_vars) + + if remove_all_env_vars: + c["env"] = [] + if startup_command is not None: if isinstance(startup_command, list) and not startup_command: c["command"] = None @@ -607,8 +627,23 @@ def update_containerapp(cmd, container_def = ContainerModel container_def["name"] = container_name container_def["image"] = image - if env_vars is not None: - container_def["env"] = parse_env_var_flags(env_vars) + container_def["env"] = [] + + if set_env_vars is not None: + # env vars + _add_or_update_env_vars(container_def["env"], parse_env_var_flags(set_env_vars), is_add=True) + + if replace_env_vars is not None: + # env vars + _add_or_update_env_vars(container_def["env"], parse_env_var_flags(replace_env_vars)) + + if remove_env_vars is not None: + # env vars + _remove_env_vars(container_def["env"], remove_env_vars) + + if remove_all_env_vars: + container_def["env"] = [] + if startup_command is not None: if isinstance(startup_command, list) and not startup_command: container_def["command"] = None @@ -1284,16 +1319,19 @@ def deactivate_revision(cmd, resource_group_name, revision_name, name=None): handle_raw_exception(e) def copy_revision(cmd, - name, resource_group_name, from_revision=None, #label=None, + name=None, yaml=None, image=None, container_name=None, min_replicas=None, max_replicas=None, - env_vars=None, + set_env_vars=None, + replace_env_vars=None, + remove_env_vars=None, + remove_all_env_vars=False, cpu=None, memory=None, revision_suffix=None, @@ -1303,12 +1341,16 @@ def copy_revision(cmd, no_wait=False): _validate_subscription_registered(cmd, "Microsoft.App") - if not from_revision: - from_revision = containerapp_def["properties"]["latestRevisionName"] + if not name and not from_revision: + raise RequiredArgumentMissingError('Usage error: --name is required if not using --from-revision.') + + if not name: + name = _get_app_from_revision(from_revision) if yaml: if image or min_replicas or max_replicas or\ - env_vars or cpu or memory or \ + set_env_vars or replace_env_vars or remove_env_vars or \ + remove_all_env_vars or cpu or memory or \ startup_command or args or tags: logger.warning('Additional flags were passed along with --yaml. These flags will be ignored, and the configuration defined in the yaml will be used instead') return update_containerapp_yaml(cmd=cmd, name=name, resource_group_name=resource_group_name, file_name=yaml, from_revision=from_revision, no_wait=no_wait) @@ -1322,13 +1364,15 @@ def copy_revision(cmd, if not containerapp_def: raise CLIError("The containerapp '{}' does not exist".format(name)) - try: - r = ContainerAppClient.show_revision(cmd=cmd, resource_group_name=resource_group_name, container_app_name=name, name=from_revision) - except CLIError as e: - # Error handle the case where revision not found? - handle_raw_exception(e) + if from_revision: + try: + r = ContainerAppClient.show_revision(cmd=cmd, resource_group_name=resource_group_name, container_app_name=name, name=from_revision) + except CLIError as e: + # Error handle the case where revision not found? + handle_raw_exception(e) - containerapp_def["properties"]["template"] = r["properties"]["template"] + _update_revision_env_secretrefs(r["properties"]["template"]["containers"], name) + containerapp_def["properties"]["template"] = r["properties"]["template"] # Doing this while API has bug. If env var is an empty string, API doesn't return "value" even though the "value" should be an empty string if "properties" in containerapp_def and "template" in containerapp_def["properties"] and "containers" in containerapp_def["properties"]["template"]: @@ -1340,7 +1384,7 @@ def copy_revision(cmd, update_map = {} update_map['scale'] = min_replicas or max_replicas - update_map['container'] = image or container_name or env_vars or cpu or memory or startup_command is not None or args is not None + update_map['container'] = image or container_name or set_env_vars or replace_env_vars or remove_env_vars or remove_all_env_vars or cpu or memory or startup_command is not None or args is not None if tags: _add_or_update_tags(containerapp_def, tags) @@ -1364,10 +1408,28 @@ def copy_revision(cmd, if image is not None: c["image"] = image - if env_vars is not None: + + if set_env_vars is not None: + if "env" not in c or not c["env"]: + c["env"] = [] + # env vars + _add_or_update_env_vars(c["env"], parse_env_var_flags(set_env_vars), is_add=True) + + if replace_env_vars is not None: + if "env" not in c or not c["env"]: + c["env"] = [] + # env vars + _add_or_update_env_vars(c["env"], parse_env_var_flags(replace_env_vars)) + + if remove_env_vars is not None: if "env" not in c or not c["env"]: c["env"] = [] - _add_or_update_env_vars(c["env"], parse_env_var_flags(env_vars)) + # env vars + _remove_env_vars(c["env"], remove_env_vars) + + if remove_all_env_vars: + c["env"] = [] + if startup_command is not None: if isinstance(startup_command, list) and not startup_command: c["command"] = None @@ -1404,8 +1466,22 @@ def copy_revision(cmd, container_def = ContainerModel container_def["name"] = container_name container_def["image"] = image - if env_vars is not None: - container_def["env"] = parse_env_var_flags(env_vars) + + if set_env_vars is not None: + # env vars + _add_or_update_env_vars(container_def["env"], parse_env_var_flags(set_env_vars), is_add=True) + + if replace_env_vars is not None: + # env vars + _add_or_update_env_vars(container_def["env"], parse_env_var_flags(replace_env_vars)) + + if remove_env_vars is not None: + # env vars + _remove_env_vars(container_def["env"], remove_env_vars) + + if remove_all_env_vars: + container_def["env"] = [] + if startup_command is not None: if isinstance(startup_command, list) and not startup_command: container_def["command"] = None @@ -1706,7 +1782,7 @@ def set_registry(cmd, name, resource_group_name, server, username=None, password except Exception as e: handle_raw_exception(e) -def delete_registry(cmd, name, resource_group_name, server, no_wait=False): +def remove_registry(cmd, name, resource_group_name, server, no_wait=False): _validate_subscription_registered(cmd, "Microsoft.App") containerapp_def = None @@ -1789,7 +1865,7 @@ def show_secret(cmd, name, resource_group_name, secret_name): return secret raise CLIError("The containerapp {} does not have a secret assigned with name {}.".format(name, secret_name)) -def delete_secrets(cmd, name, resource_group_name, secret_names, no_wait = False): +def remove_secrets(cmd, name, resource_group_name, secret_names, no_wait = False): _validate_subscription_registered(cmd, "Microsoft.App") containerapp_def = None