diff --git a/src/containerapp/HISTORY.rst b/src/containerapp/HISTORY.rst index b66465a832a..2abbd24f08a 100644 --- a/src/containerapp/HISTORY.rst +++ b/src/containerapp/HISTORY.rst @@ -3,6 +3,10 @@ Release History =============== +0.3.0 +++++++ +* Subgroup commands for managed identities: az containerapp identity + 0.1.0 ++++++ * Initial release for Container App support with Microsoft.App RP. diff --git a/src/containerapp/azext_containerapp/_help.py b/src/containerapp/azext_containerapp/_help.py index 5e75c334d82..895c769a2dc 100644 --- a/src/containerapp/azext_containerapp/_help.py +++ b/src/containerapp/azext_containerapp/_help.py @@ -162,6 +162,14 @@ """ +helps['containerapp revision copy'] = """ + type: command + short-summary: Create a revision based on a previous revision. + examples: + - name: Create a revision based on a previous revision. + text: | + az containerapp revision copy -n MyContainerapp -g MyResourceGroup --cpu 0.75 --memory 1.5Gi +""" # Environment Commands helps['containerapp env'] = """ @@ -176,13 +184,13 @@ - name: Create an environment with an auto-generated Log Analytics workspace. text: | az containerapp env create -n MyContainerappEnvironment -g MyResourceGroup \\ - --location "Canada Central" + --location eastus2 - name: Create an environment with an existing Log Analytics workspace. text: | az containerapp env create -n MyContainerappEnvironment -g MyResourceGroup \\ --logs-workspace-id myLogsWorkspaceID \\ --logs-workspace-key myLogsWorkspaceKey \\ - --location "Canada Central" + --location eastus2 """ @@ -256,6 +264,61 @@ az containerapp env dapr-component remove -g MyResourceGroup --dapr-component-name MyDaprComponentName --name MyEnvironment """ +# Identity Commands +helps['containerapp identity'] = """ + type: group + short-summary: Commands to manage managed identities. +""" + +helps['containerapp identity assign'] = """ + type: command + short-summary: Assign managed identity to a container app. + long-summary: Managed identities can be user-assigned or system-assigned. + examples: + - name: Assign system identity. + text: | + az containerapp identity assign -n myContainerapp -g MyResourceGroup --system-assigned + - name: Assign user identity. + text: | + az containerapp identity assign -n myContainerapp -g MyResourceGroup --user-assigned myUserIdentityName + - name: Assign user identity (from a different resource group than the containerapp). + text: | + az containerapp identity assign -n myContainerapp -g MyResourceGroup --user-assigned myUserIdentityResourceId + - name: Assign system and user identity. + text: | + az containerapp identity assign -n myContainerapp -g MyResourceGroup --system-assigned --user-assigned myUserIdentityResourceId +""" + +helps['containerapp identity remove'] = """ + type: command + short-summary: Remove a managed identity from a container app. + examples: + - name: Remove system identity. + text: | + az containerapp identity remove -n myContainerapp -g MyResourceGroup --system-assigned + - name: Remove user identity. + text: | + az containerapp identity remove -n myContainerapp -g MyResourceGroup --user-assigned myUserIdentityName + - name: Remove system and user identity (from a different resource group than the containerapp). + text: | + az containerapp identity remove -n myContainerapp -g MyResourceGroup --system-assigned --user-assigned myUserIdentityResourceId + - name: Remove all user identities. + text: | + az containerapp identity remove -n myContainerapp -g MyResourceGroup --user-assigned + - name: Remove system identity and all user identities. + text: | + az containerapp identity remove -n myContainerapp -g MyResourceGroup --system-assigned --user-assigned +""" + +helps['containerapp identity show'] = """ + type: command + short-summary: Show managed identities of a container app. + examples: + - name: Show managed identities. + text: | + az containerapp identity show -n myContainerapp -g MyResourceGroup +""" + # Ingress Commands helps['containerapp ingress'] = """ type: group @@ -470,7 +533,7 @@ helps['containerapp dapr enable'] = """ type: command - short-summary: Enable Dapr for a container app. + short-summary: Enable Dapr for a container app. Updates existing values. examples: - name: Enable Dapr for a container app. text: | @@ -479,7 +542,7 @@ helps['containerapp dapr disable'] = """ type: command - short-summary: Disable Dapr for a container app. + short-summary: Disable Dapr for a container app. Removes existing values. examples: - name: Disable Dapr for a container app. text: | diff --git a/src/containerapp/azext_containerapp/_params.py b/src/containerapp/azext_containerapp/_params.py index 6e0ee6918d4..4c958cd0077 100644 --- a/src/containerapp/azext_containerapp/_params.py +++ b/src/containerapp/azext_containerapp/_params.py @@ -34,8 +34,8 @@ def load_arguments(self, _): with self.argument_context('containerapp', arg_group='Container') as c: c.argument('image', type=str, options_list=['--image', '-i'], help="Container image, e.g. publisher/image-name:tag.") c.argument('container_name', type=str, help="Name of the container.") - c.argument('cpu', type=float, validator=validate_cpu, help="Required CPU in cores, e.g. 0.5") - c.argument('memory', type=str, validator=validate_memory, help="Required memory, e.g. 1.0Gi") + c.argument('cpu', type=float, validator=validate_cpu, help="Required CPU in cores from 0.25 - 2.0, e.g. 0.5") + c.argument('memory', type=str, validator=validate_memory, help="Required memory from 0.5 - 4.0 ending with \"Gi\", e.g. 1.0Gi") c.argument('env_vars', nargs='*', help="A list of environment variable(s) for the container. Space-separated values in 'key=value' format. Empty string to clear existing values. Prefix value with 'secretref:' to reference a secret.") c.argument('startup_command', nargs='*', options_list=['--command'], help="A list of supported commands on the container that will executed during startup. Space-separated values e.g. \"/bin/queue\" \"mycommand\". Empty string to clear existing values") c.argument('args', nargs='*', help="A list of container startup command argument(s). Space-separated values e.g. \"-c\" \"mycommand\". Empty string to clear existing values") @@ -77,6 +77,10 @@ def load_arguments(self, _): with self.argument_context('containerapp create') as c: c.argument('traffic_weights', nargs='*', options_list=['--traffic-weight'], help="A list of revision weight(s) for the container app. Space-separated values in 'revision_name=weight' format. For latest revision, use 'latest=weight'") + with self.argument_context('containerapp create', arg_group='Identity') as c: + c.argument('user_assigned', nargs='+', help="Space-separated user identities to be assigned.") + c.argument('system_assigned', help="Boolean indicating whether to assign system-assigned identity.") + with self.argument_context('containerapp scale') as c: c.argument('min_replicas', type=int, help="The minimum number of replicas.") c.argument('max_replicas', type=int, help="The maximum number of replicas.") @@ -84,7 +88,7 @@ def load_arguments(self, _): with self.argument_context('containerapp env') as c: c.argument('name', name_type, help='Name of the Container Apps environment.') c.argument('resource_group_name', arg_type=resource_group_name_type) - c.argument('location', arg_type=get_location_type(self.cli_ctx), help='Location of resource. Examples: Canada Central, North Europe') + c.argument('location', arg_type=get_location_type(self.cli_ctx), help='Location of resource. Examples: eastus2, northeurope') c.argument('tags', arg_type=tags_type) with self.argument_context('containerapp env', arg_group='Log Analytics') as c: @@ -112,6 +116,13 @@ def load_arguments(self, _): with self.argument_context('containerapp env show') as c: c.argument('name', name_type, help='Name of the Container Apps Environment.') + with self.argument_context('containerapp identity') as c: + c.argument('user_assigned', nargs='+', help="Space-separated user identities.") + c.argument('system_assigned', help="Boolean indicating whether to assign system-assigned identity.") + + with self.argument_context('containerapp identity remove') as c: + c.argument('user_assigned', nargs='*', help="Space-separated user identities. If no user identities are specified, all user identities will be removed.") + with self.argument_context('containerapp github-action add') as c: c.argument('repo_url', help='The GitHub repository to which the workflow file will be added. In the format: https://github.com//') c.argument('token', help='A Personal Access Token with write access to the specified repository. For more information: https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line') @@ -144,14 +155,11 @@ def load_arguments(self, _): with self.argument_context('containerapp ingress traffic') as c: c.argument('traffic_weights', nargs='*', options_list=['--traffic-weight'], help="A list of revision weight(s) for the container app. Space-separated values in 'revision_name=weight' format. For latest revision, use 'latest=weight'") - with self.argument_context('containerapp secret set') as c: + with self.argument_context('containerapp secret') 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 show') as c: c.argument('secret_name', help="The name of the secret to show.") - - 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.") + c.argument('show_values', help='Show the secret values.') with self.argument_context('containerapp env dapr-component') as c: c.argument('dapr_app_id', help="The Dapr app ID.") diff --git a/src/containerapp/azext_containerapp/_validators.py b/src/containerapp/azext_containerapp/_validators.py index e7fe0435a11..861a7f049b6 100644 --- a/src/containerapp/azext_containerapp/_validators.py +++ b/src/containerapp/azext_containerapp/_validators.py @@ -16,13 +16,14 @@ def _is_number(s): def validate_memory(namespace): - memory = namespace.memory - - if memory is not None: + if namespace.memory is not None: valid = False - if memory.endswith("Gi"): - valid = _is_number(memory[:-2]) + if not namespace.memory.endswith("Gi"): + namespace.memory = namespace.memory.rstrip() + namespace.memory += "Gi" + + valid = _is_number(namespace.memory[:-2]) if not valid: raise ValidationError("Usage error: --memory must be a number ending with \"Gi\"") diff --git a/src/containerapp/azext_containerapp/commands.py b/src/containerapp/azext_containerapp/commands.py index c5b924287f8..dd6f2d067dc 100644 --- a/src/containerapp/azext_containerapp/commands.py +++ b/src/containerapp/azext_containerapp/commands.py @@ -62,6 +62,11 @@ def load_command_table(self, _): 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_show_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_show_command('show', 'show_github_action', exception_handler=ex_handler_factory()) diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index 15cea81f0ff..06ce16ad26d 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -292,7 +292,9 @@ def create_containerapp(cmd, startup_command=None, args=None, tags=None, - no_wait=False): + no_wait=False, + system_assigned=False, + user_assigned=None): _validate_subscription_registered(cmd, "Microsoft.App") if yaml: @@ -374,6 +376,31 @@ def create_containerapp(cmd, config_def["registries"] = [registries_def] if registries_def is not None else None config_def["dapr"] = dapr_def + # Identity actions + identity_def = ManagedServiceIdentityModel + identity_def["type"] = "None" + + assign_system_identity = system_assigned + if user_assigned: + assign_user_identities = [x.lower() for x in user_assigned] + else: + assign_user_identities = [] + + if assign_system_identity and assign_user_identities: + identity_def["type"] = "SystemAssigned, UserAssigned" + elif assign_system_identity: + identity_def["type"] = "SystemAssigned" + elif assign_user_identities: + identity_def["type"] = "UserAssigned" + + if assign_user_identities: + identity_def["userAssignedIdentities"] = {} + subscription_id = get_subscription_id(cmd.cli_ctx) + + for r in assign_user_identities: + r = _ensure_identity_resource_id(subscription_id, resource_group_name, r) + identity_def["userAssignedIdentities"][r] = {} # pylint: disable=unsupported-assignment-operation + scale_def = None if min_replicas is not None or max_replicas is not None: scale_def = ScaleModel @@ -407,6 +434,7 @@ def create_containerapp(cmd, containerapp_def = ContainerAppModel containerapp_def["location"] = location + containerapp_def["identity"] = identity_def containerapp_def["properties"]["managedEnvironmentId"] = managed_env containerapp_def["properties"]["configuration"] = config_def containerapp_def["properties"]["template"] = template_def @@ -429,25 +457,26 @@ def create_containerapp(cmd, handle_raw_exception(e) -def update_containerapp(cmd, - name, - resource_group_name, - yaml=None, - image=None, - container_name=None, - min_replicas=None, - max_replicas=None, - set_env_vars=None, - remove_env_vars=None, - replace_env_vars=None, - remove_all_env_vars=False, - cpu=None, - memory=None, - revision_suffix=None, - startup_command=None, - args=None, - tags=None, - no_wait=False): +def update_containerapp_logic(cmd, + name, + resource_group_name, + yaml=None, + image=None, + container_name=None, + min_replicas=None, + max_replicas=None, + set_env_vars=None, + remove_env_vars=None, + replace_env_vars=None, + remove_all_env_vars=False, + cpu=None, + memory=None, + revision_suffix=None, + startup_command=None, + args=None, + tags=None, + no_wait=False, + from_revision=None): _validate_subscription_registered(cmd, "Microsoft.App") if yaml: @@ -455,7 +484,7 @@ def update_containerapp(cmd, set_env_vars or remove_env_vars or replace_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, no_wait=no_wait) + return update_containerapp_yaml(cmd=cmd, name=name, resource_group_name=resource_group_name, file_name=yaml, no_wait=no_wait, from_revision=from_revision) containerapp_def = None try: @@ -466,6 +495,16 @@ def update_containerapp(cmd, if not containerapp_def: raise ResourceNotFoundError("The containerapp '{}' does not exist".format(name)) + 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) + + _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"]: for container in containerapp_def["properties"]["template"]["containers"]: @@ -490,7 +529,7 @@ def update_containerapp(cmd, if len(containerapp_def["properties"]["template"]["containers"]) == 1: container_name = containerapp_def["properties"]["template"]["containers"][0]["name"] else: - raise ValidationError("Usage error: --image-name is required when adding or updating a container") + raise ValidationError("Usage error: --container-name is required when adding or updating a container") # Check if updating existing container updating_existing_container = False @@ -613,6 +652,48 @@ def update_containerapp(cmd, handle_raw_exception(e) +def update_containerapp(cmd, + name, + resource_group_name, + yaml=None, + image=None, + container_name=None, + min_replicas=None, + max_replicas=None, + set_env_vars=None, + remove_env_vars=None, + replace_env_vars=None, + remove_all_env_vars=False, + cpu=None, + memory=None, + revision_suffix=None, + startup_command=None, + args=None, + tags=None, + no_wait=False): + _validate_subscription_registered(cmd, "Microsoft.App") + + return update_containerapp_logic(cmd, + name, + resource_group_name, + yaml, + image, + container_name, + min_replicas, + max_replicas, + set_env_vars, + remove_env_vars, + replace_env_vars, + remove_all_env_vars, + cpu, + memory, + revision_suffix, + startup_command, + args, + tags, + no_wait) + + def show_containerapp(cmd, name, resource_group_name): _validate_subscription_registered(cmd, "Microsoft.App") @@ -763,17 +844,13 @@ def delete_managed_environment(cmd, name, resource_group_name, no_wait=False): handle_raw_exception(e) -def assign_managed_identity(cmd, name, resource_group_name, identities=None, no_wait=False): +def assign_managed_identity(cmd, name, resource_group_name, system_assigned=False, user_assigned=None, no_wait=False): _validate_subscription_registered(cmd, "Microsoft.App") - # if no identities, then assign system by default - if not identities: - identities = ['[system]'] - logger.warning('Identities not specified. Assigning managed system identity.') - - identities = [x.lower() for x in identities] - assign_system_identity = '[system]' in identities - assign_user_identities = [x for x in identities if x != '[system]'] + assign_system_identity = system_assigned + if not user_assigned: + user_assigned = [] + assign_user_identities = [x.lower() for x in user_assigned] containerapp_def = None @@ -826,12 +903,16 @@ def assign_managed_identity(cmd, name, resource_group_name, identities=None, no_ subscription_id = get_subscription_id(cmd.cli_ctx) for r in assign_user_identities: - old_id = r r = _ensure_identity_resource_id(subscription_id, resource_group_name, r).replace("resourceGroup", "resourcegroup") - try: - containerapp_def["identity"]["userAssignedIdentities"][r] - logger.warning("User identity {} is already assigned to containerapp".format(old_id)) - except: + isExisting = False + + for old_user_identity in containerapp_def["identity"]["userAssignedIdentities"]: + if old_user_identity.lower() == r.lower(): + isExisting = True + logger.warning("User identity {} is already assigned to containerapp".format(old_user_identity)) + break + + if not isExisting: containerapp_def["identity"]["userAssignedIdentities"][r] = {} try: @@ -843,18 +924,19 @@ def assign_managed_identity(cmd, name, resource_group_name, identities=None, no_ handle_raw_exception(e) -def remove_managed_identity(cmd, name, resource_group_name, identities, no_wait=False): +def remove_managed_identity(cmd, name, resource_group_name, system_assigned=False, user_assigned=None, no_wait=False): _validate_subscription_registered(cmd, "Microsoft.App") - identities = [x.lower() for x in identities] - remove_system_identity = '[system]' in identities - remove_user_identities = [x for x in identities if x != '[system]'] - remove_id_size = len(remove_user_identities) + remove_system_identity = system_assigned + remove_user_identities = user_assigned - # Remove duplicate identities that are passed and notify - remove_user_identities = list(set(remove_user_identities)) - if remove_id_size != len(remove_user_identities): - logger.warning("At least one identity was passed twice.") + if user_assigned: + remove_id_size = len(remove_user_identities) + + # Remove duplicate identities that are passed and notify + remove_user_identities = list(set(remove_user_identities)) + if remove_id_size != len(remove_user_identities): + logger.warning("At least one identity was passed twice.") containerapp_def = None # Get containerapp properties of CA we are updating @@ -884,6 +966,14 @@ def remove_managed_identity(cmd, name, resource_group_name, identities, no_wait= raise InvalidArgumentValueError("The containerapp {} has no system assigned identities.".format(name)) containerapp_def["identity"]["type"] = ("None" if containerapp_def["identity"]["type"] == "SystemAssigned" else "UserAssigned") + if isinstance(user_assigned, list) and not user_assigned: + containerapp_def["identity"]["userAssignedIdentities"] = {} + remove_user_identities = [] + + if containerapp_def["identity"]["userAssignedIdentities"] == {}: + containerapp_def["identity"]["userAssignedIdentities"] = None + containerapp_def["identity"]["type"] = ("None" if containerapp_def["identity"]["type"] == "UserAssigned" else "SystemAssigned") + if remove_user_identities: subscription_id = get_subscription_id(cmd.cli_ctx) try: @@ -1195,177 +1285,26 @@ def copy_revision(cmd, if not name: name = _get_app_from_revision(from_revision) - if yaml: - if image or min_replicas or max_replicas 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) - - containerapp_def = None - try: - containerapp_def = ContainerAppClient.show(cmd=cmd, resource_group_name=resource_group_name, name=name) - except: - pass - - if not containerapp_def: - raise ResourceNotFoundError("The containerapp '{}' does not exist".format(name)) - - 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) - - _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"]: - for container in containerapp_def["properties"]["template"]["containers"]: - if "env" in container: - for e in container["env"]: - if "value" not in e: - e["value"] = "" - - update_map = {} - update_map['scale'] = min_replicas or max_replicas - 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) - - if revision_suffix is not None: - containerapp_def["properties"]["template"]["revisionSuffix"] = revision_suffix - - # Containers - if update_map["container"]: - if not container_name: - if len(containerapp_def["properties"]["template"]["containers"]) == 1: - container_name = containerapp_def["properties"]["template"]["containers"][0]["name"] - else: - raise ValidationError("Usage error: --image-name is required when adding or updating a container") - - # Check if updating existing container - updating_existing_container = False - for c in containerapp_def["properties"]["template"]["containers"]: - if c["name"].lower() == container_name.lower(): - updating_existing_container = True - - if image is not None: - c["image"] = image - - 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"] = [] - # 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 - else: - c["command"] = startup_command - if args is not None: - if isinstance(args, list) and not args: - c["args"] = None - else: - c["args"] = args - if cpu is not None or memory is not None: - if "resources" in c and c["resources"]: - if cpu is not None: - c["resources"]["cpu"] = cpu - if memory is not None: - c["resources"]["memory"] = memory - else: - c["resources"] = { - "cpu": cpu, - "memory": memory - } - - # If not updating existing container, add as new container - if not updating_existing_container: - if image is None: - raise ValidationError("Usage error: --image is required when adding a new container") - - resources_def = None - if cpu is not None or memory is not None: - resources_def = ContainerResourcesModel - resources_def["cpu"] = cpu - resources_def["memory"] = memory - - container_def = ContainerModel - container_def["name"] = container_name - container_def["image"] = image - - 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 - else: - container_def["command"] = startup_command - if args is not None: - if isinstance(args, list) and not args: - container_def["args"] = None - else: - container_def["args"] = args - if resources_def is not None: - container_def["resources"] = resources_def - - containerapp_def["properties"]["template"]["containers"].append(container_def) - - # Scale - if update_map["scale"]: - if "scale" not in containerapp_def["properties"]["template"]: - containerapp_def["properties"]["template"]["scale"] = {} - if min_replicas is not None: - containerapp_def["properties"]["template"]["scale"]["minReplicas"] = min_replicas - if max_replicas is not None: - containerapp_def["properties"]["template"]["scale"]["maxReplicas"] = max_replicas - - _get_existing_secrets(cmd, resource_group_name, name, containerapp_def) - - try: - r = ContainerAppClient.create_or_update( - cmd=cmd, resource_group_name=resource_group_name, name=name, container_app_envelope=containerapp_def, no_wait=no_wait) - - if "properties" in r and "provisioningState" in r["properties"] and r["properties"]["provisioningState"].lower() == "waiting" and not no_wait: - logger.warning('Containerapp update in progress. Please monitor the update using `az containerapp show -n {} -g {}`'.format(name, resource_group_name)) - - return r - except Exception as e: - handle_raw_exception(e) + return update_containerapp_logic(cmd, + name, + resource_group_name, + yaml, + image, + container_name, + min_replicas, + max_replicas, + set_env_vars, + remove_env_vars, + replace_env_vars, + remove_all_env_vars, + cpu, + memory, + revision_suffix, + startup_command, + args, + tags, + no_wait, + from_revision) def set_revision_mode(cmd, resource_group_name, name, mode, no_wait=False): @@ -1410,7 +1349,7 @@ def show_ingress(cmd, name, resource_group_name): raise ValidationError("The containerapp '{}' does not have ingress enabled.".format(name)) from e -def enable_ingress(cmd, name, resource_group_name, type, target_port, transport, allow_insecure=False, no_wait=False): # pylint: disable=redefined-builtin +def enable_ingress(cmd, name, resource_group_name, type, target_port, transport="auto", allow_insecure=False, no_wait=False): # pylint: disable=redefined-builtin _validate_subscription_registered(cmd, "Microsoft.App") containerapp_def = None @@ -1688,22 +1627,28 @@ def remove_registry(cmd, name, resource_group_name, server, no_wait=False): pass -def list_secrets(cmd, name, resource_group_name): +def list_secrets(cmd, name, resource_group_name, show_values=False): _validate_subscription_registered(cmd, "Microsoft.App") containerapp_def = None try: - containerapp_def = ContainerAppClient.show(cmd=cmd, resource_group_name=resource_group_name, name=name) + r = containerapp_def = ContainerAppClient.show(cmd=cmd, resource_group_name=resource_group_name, name=name) except: pass if not containerapp_def: raise ResourceNotFoundError("The containerapp '{}' does not exist".format(name)) + if not show_values: + try: + return r["properties"]["configuration"]["secrets"] + except: + return [] try: return ContainerAppClient.list_secrets(cmd=cmd, resource_group_name=resource_group_name, name=name)["value"] - except Exception as e: - raise ValidationError("The containerapp {} has no assigned secrets.".format(name)) from e + except Exception: + return [] + # raise ValidationError("The containerapp {} has no assigned secrets.".format(name)) from e def show_secret(cmd, name, resource_group_name, secret_name): @@ -1796,6 +1741,7 @@ def set_secrets(cmd, name, resource_group_name, secrets, try: r = ContainerAppClient.create_or_update( cmd=cmd, resource_group_name=resource_group_name, name=name, container_app_envelope=containerapp_def, no_wait=no_wait) + logger.warning("Containerapp '{}' must be restarted in order for secret changes to take effect.".format(name)) return r["properties"]["configuration"]["secrets"] except Exception as e: handle_raw_exception(e) diff --git a/src/containerapp/setup.py b/src/containerapp/setup.py index e23b0011367..490525870da 100644 --- a/src/containerapp/setup.py +++ b/src/containerapp/setup.py @@ -16,7 +16,7 @@ # TODO: Confirm this is the right version number you want and it matches your # HISTORY.rst entry. -VERSION = '0.1.0' +VERSION = '0.3.0' # The full list of classifiers is available at # https://pypi.python.org/pypi?%3Aaction=list_classifiers