From eb642e29ce3dd9ddbdc8d341dc1bf34f41f54ed7 Mon Sep 17 00:00:00 2001 From: Haroon Feisal Date: Tue, 22 Feb 2022 19:52:59 -0500 Subject: [PATCH 01/13] Added identity show and assign. --- .../azext_containerapp/_params.py | 3 + .../azext_containerapp/commands.py | 8 ++ src/containerapp/azext_containerapp/custom.py | 85 +++++++++++++++++++ 3 files changed, 96 insertions(+) diff --git a/src/containerapp/azext_containerapp/_params.py b/src/containerapp/azext_containerapp/_params.py index 184a3a0e100..ea09f8d3aa6 100644 --- a/src/containerapp/azext_containerapp/_params.py +++ b/src/containerapp/azext_containerapp/_params.py @@ -101,3 +101,6 @@ def load_arguments(self, _): with self.argument_context('containerapp env show') as c: c.argument('name', name_type, help='Name of the managed Environment.') + + with self.argument_context('containerapp identity') as c: + c.argument('identities', nargs='+', help="Space-separated identities. Use '[system]' to refer to the system assigned identity") diff --git a/src/containerapp/azext_containerapp/commands.py b/src/containerapp/azext_containerapp/commands.py index 998e41cf3ae..3054644cc7b 100644 --- a/src/containerapp/azext_containerapp/commands.py +++ b/src/containerapp/azext_containerapp/commands.py @@ -41,3 +41,11 @@ def load_command_table(self, _): g.custom_command('create', 'create_managed_environment', supports_no_wait=True, exception_handler=ex_handler_factory()) # 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 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') + + diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index a2827b62eea..d45c23d7a8a 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -561,3 +561,88 @@ def delete_managed_environment(cmd, name, resource_group_name, no_wait=False): return ManagedEnvironmentClient.delete(cmd=cmd, name=name, resource_group_name=resource_group_name, no_wait=no_wait) except CLIError as e: handle_raw_exception(e) + +def assign_managed_identity(cmd, name, resource_group_name, identities, no_wait=False): + _validate_subscription_registered(cmd, "Microsoft.App") + + from azure.cli.core.commands.client_factory import get_subscription_id + + assign_system_identity = '[system]' in identities + assign_user_identities = [x for x in identities if x != '[system]'] + + containerapp_def = None + + # Get containerapp properties of CA we are updating + try: + containerapp_def = ContainerAppClient.show(cmd=cmd, resource_group_name=resource_group_name, name=name) + except: + pass + + if not containerapp_def: + raise CLIError("The containerapp '{}' does not exist".format(name)) + + # Assign correct type + try: + if containerapp_def["identity"]["type"] != "None": + if containerapp_def["identity"]["type"] == "SystemAssigned" and assign_user_identities: + containerapp_def["identity"]["type"] = "SystemAssigned,UserAssigned" + if containerapp_def["identity"]["type"] == "UserAssigned" and assign_system_identity: + containerapp_def["identity"]["type"] = "SystemAssigned,UserAssigned" + else: + if assign_system_identity and assign_user_identities: + containerapp_def["identity"]["type"] = "SystemAssigned,UserAssigned" + elif assign_system_identity: + containerapp_def["identity"]["type"] = "SystemAssigned" + elif assign_user_identities: + containerapp_def["identity"]["type"] = "UserAssigned" + except: + # Server always returns "type": "None" when CA has no previous identities + pass + + + # If there are no identities, create something empty to assign to + try: + containerapp_def["identity"]["userAssignedIdentities"] + except: + containerapp_def["identity"]["userAssignedIdentities"] = {} + + + if assign_user_identities: + 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) + containerapp_def["identity"]["userAssignedIdentities"][r] = {} + + 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) + return r["identity"] + except Exception as e: + handle_raw_exception(e) + + + +def remove_managed_identity(cmd, name, resource_group_name): + _validate_subscription_registered(cmd, "Microsoft.App") + + return + +def show_managed_identity(cmd, name, resource_group_name): + _validate_subscription_registered(cmd, "Microsoft.App") + + try: + return ContainerAppClient.show(cmd=cmd, resource_group_name=resource_group_name, name=name)["identity"] + except CLIError as e: + handle_raw_exception(e) + + +def _ensure_identity_resource_id(subscription_id, resource_group, resource): + from msrestazure.tools import resource_id, is_valid_resource_id + if is_valid_resource_id(resource): + return resource + + return resource_id(subscription=subscription_id, + resource_group=resource_group, + namespace='Microsoft.ManagedIdentity', + type='userAssignedIdentities', + name=resource) \ No newline at end of file From 2333a04c501aabeebb9f7a5dbfab8e469cf9b2e4 Mon Sep 17 00:00:00 2001 From: Haroon Feisal Date: Wed, 23 Feb 2022 13:57:55 -0500 Subject: [PATCH 02/13] Finisheed identity remove. --- src/containerapp/azext_containerapp/custom.py | 112 +++++++++++++++--- 1 file changed, 95 insertions(+), 17 deletions(-) diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index d45c23d7a8a..c870f5ab00e 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -562,12 +562,17 @@ def delete_managed_environment(cmd, name, resource_group_name, no_wait=False): except CLIError as e: handle_raw_exception(e) -def assign_managed_identity(cmd, name, resource_group_name, identities, no_wait=False): + +def assign_managed_identity(cmd, name, resource_group_name, identities=None, no_wait=False): _validate_subscription_registered(cmd, "Microsoft.App") from azure.cli.core.commands.client_factory import get_subscription_id - assign_system_identity = '[system]' in identities + # if no identities, then assign system by default + if not identities: + identities = ['[system]'] + + assign_system_identity = '[system]' in identities assign_user_identities = [x for x in identities if x != '[system]'] containerapp_def = None @@ -581,6 +586,14 @@ def assign_managed_identity(cmd, name, resource_group_name, identities, no_wait= if not containerapp_def: raise CLIError("The containerapp '{}' does not exist".format(name)) + # If identity not returned + try: + containerapp_def["identity"] + containerapp_def["identity"]["type"] + except: + containerapp_def["identity"] = {} + containerapp_def["identity"]["type"] = "None" + # Assign correct type try: if containerapp_def["identity"]["type"] != "None": @@ -590,24 +603,21 @@ def assign_managed_identity(cmd, name, resource_group_name, identities, no_wait= containerapp_def["identity"]["type"] = "SystemAssigned,UserAssigned" else: if assign_system_identity and assign_user_identities: - containerapp_def["identity"]["type"] = "SystemAssigned,UserAssigned" + containerapp_def["identity"]["type"] = "SystemAssigned, UserAssigned" elif assign_system_identity: containerapp_def["identity"]["type"] = "SystemAssigned" elif assign_user_identities: containerapp_def["identity"]["type"] = "UserAssigned" except: - # Server always returns "type": "None" when CA has no previous identities + # Always returns "type": "None" when CA has no previous identities pass - - # If there are no identities, create something empty to assign to - try: - containerapp_def["identity"]["userAssignedIdentities"] - except: - containerapp_def["identity"]["userAssignedIdentities"] = {} - - if assign_user_identities: + try: + containerapp_def["identity"]["userAssignedIdentities"] + except: + containerapp_def["identity"]["userAssignedIdentities"] = {} + subscription_id = get_subscription_id(cmd.cli_ctx) for r in assign_user_identities: @@ -616,26 +626,94 @@ def assign_managed_identity(cmd, name, resource_group_name, identities, no_wait= 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 identity is not returned, do nothing return r["identity"] + except Exception as e: handle_raw_exception(e) - -def remove_managed_identity(cmd, name, resource_group_name): +def remove_managed_identity(cmd, name, resource_group_name, identities=None, no_wait=False): _validate_subscription_registered(cmd, "Microsoft.App") - return + from azure.cli.core.commands.client_factory import get_subscription_id + + if not identities: + identities = ['[system]'] + + remove_system_identity = '[system]' in identities + remove_user_identities = [x for x in identities if x != '[system]'] + + containerapp_def = None + + # Get containerapp properties of CA we are updating + try: + containerapp_def = ContainerAppClient.show(cmd=cmd, resource_group_name=resource_group_name, name=name) + except: + pass + + if not containerapp_def: + raise CLIError("The containerapp '{}' does not exist".format(name)) + + # If identity not returned + try: + containerapp_def["identity"] + containerapp_def["identity"]["type"] + except: + containerapp_def["identity"] = {} + containerapp_def["identity"]["type"] = "None" + + if containerapp_def["identity"]["type"] == "None": + raise CLIError("The containerapp {} has no system or user assigned identities.".format(name)) + + if remove_system_identity: + if containerapp_def["identity"]["type"] == "UserAssigned": + raise CLIError("The containerapp {} has no system assigned identities.".format(name)) + containerapp_def["identity"]["type"] = ("None" if containerapp_def["identity"]["type"] == "SystemAssigned" else "UserAssigned") + if remove_user_identities: + subscription_id = get_subscription_id(cmd.cli_ctx) + try: + containerapp_def["identity"]["userAssignedIdentities"] + except: + containerapp_def["identity"]["userAssignedIdentities"] = {} + + for id in remove_user_identities: + id = _ensure_identity_resource_id(subscription_id, resource_group_name, id) + try: + # If attribute doesn't exist, we will know which one can't be removed + containerapp_def["identity"]["userAssignedIdentities"].pop(id) + except: + raise CLIError("The containerapp does not have specified user identity '{}' assigned, " + "so it cannot be removed.".format(id)) + + if containerapp_def["identity"]["userAssignedIdentities"] == {}: + # Wipe identity here if necessary instead of userAssignedIdentities = None + # containerapp_def["identity"] = {} or pop userassignedidentities + containerapp_def["identity"]["userAssignedIdentities"] = None + containerapp_def["identity"]["type"] = ("None" if containerapp_def["identity"]["type"] == "UserAssigned" else "SystemAssigned") + + 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) + return r["identity"] + except Exception as e: + handle_raw_exception(e) + + def show_managed_identity(cmd, name, resource_group_name): _validate_subscription_registered(cmd, "Microsoft.App") try: - return ContainerAppClient.show(cmd=cmd, resource_group_name=resource_group_name, name=name)["identity"] + r = ContainerAppClient.show(cmd=cmd, resource_group_name=resource_group_name, name=name) except CLIError as e: handle_raw_exception(e) - + try: + return r["identity"] + except: + raise CLIError("The containerapp {} does not have any identities assigned.".format(name)) + + def _ensure_identity_resource_id(subscription_id, resource_group, resource): from msrestazure.tools import resource_id, is_valid_resource_id if is_valid_resource_id(resource): From 5709fe7241962b9e03ebf480ea7124eabcc27034 Mon Sep 17 00:00:00 2001 From: Haroon Feisal Date: Wed, 23 Feb 2022 14:51:47 -0500 Subject: [PATCH 03/13] Added helps, updated identity remove to work with identity names instead of requiring identity resource ids. --- src/containerapp/azext_containerapp/_help.py | 22 ++++++++++++++++ .../azext_containerapp/_params.py | 2 +- src/containerapp/azext_containerapp/custom.py | 25 +++++++++++-------- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/containerapp/azext_containerapp/_help.py b/src/containerapp/azext_containerapp/_help.py index d6a4b353e15..4b223f507a2 100644 --- a/src/containerapp/azext_containerapp/_help.py +++ b/src/containerapp/azext_containerapp/_help.py @@ -193,3 +193,25 @@ text: | az containerapp env list -g MyResourceGroup """ + +# Identity Commands +helps['containerapp identity'] = """ +type: group +short-summary: Manage service (managed) identities for a containerapp +""" + +helps['containerapp identity assign'] = """ +type: command +short-summary: Assign a managed identity to a containerapp +long-summary: Managed identities can be user-assigned or system-assigned +""" + +helps['containerapp identity remove'] = """ +type: command +short-summary: Remove a managed identity from a containerapp +""" + +helps['containerapp identity show'] = """ +type: command +short-summary: Show the containerapp's identity details +""" \ No newline at end of file diff --git a/src/containerapp/azext_containerapp/_params.py b/src/containerapp/azext_containerapp/_params.py index ea09f8d3aa6..85c355c66d7 100644 --- a/src/containerapp/azext_containerapp/_params.py +++ b/src/containerapp/azext_containerapp/_params.py @@ -103,4 +103,4 @@ def load_arguments(self, _): c.argument('name', name_type, help='Name of the managed Environment.') with self.argument_context('containerapp identity') as c: - c.argument('identities', nargs='+', help="Space-separated identities. Use '[system]' to refer to the system assigned identity") + c.argument('identities', nargs='+', help="Space-separated identities. Use '[system]' to refer to the system assigned identity.") diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index c870f5ab00e..bbaf9cb5649 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -679,17 +679,20 @@ def remove_managed_identity(cmd, name, resource_group_name, identities=None, no_ containerapp_def["identity"]["userAssignedIdentities"] = {} for id in remove_user_identities: + given_id = id id = _ensure_identity_resource_id(subscription_id, resource_group_name, id) - try: - # If attribute doesn't exist, we will know which one can't be removed - containerapp_def["identity"]["userAssignedIdentities"].pop(id) - except: - raise CLIError("The containerapp does not have specified user identity '{}' assigned, " - "so it cannot be removed.".format(id)) + wasRemoved = False + + for old_user_identities in containerapp_def["identity"]["userAssignedIdentities"]: + if old_user_identities.lower() == id.lower(): + containerapp_def["identity"]["userAssignedIdentities"].pop(old_user_identities) + wasRemoved = True + break + + if not wasRemoved: + raise CLIError("The containerapp does not have specified user identity '{}' assigned, so it cannot be removed.".format(given_id)) if containerapp_def["identity"]["userAssignedIdentities"] == {}: - # Wipe identity here if necessary instead of userAssignedIdentities = None - # containerapp_def["identity"] = {} or pop userassignedidentities containerapp_def["identity"]["userAssignedIdentities"] = None containerapp_def["identity"]["type"] = ("None" if containerapp_def["identity"]["type"] == "UserAssigned" else "SystemAssigned") @@ -707,11 +710,13 @@ def show_managed_identity(cmd, name, resource_group_name): r = ContainerAppClient.show(cmd=cmd, resource_group_name=resource_group_name, name=name) except CLIError as e: handle_raw_exception(e) - + try: return r["identity"] except: - raise CLIError("The containerapp {} does not have any identities assigned.".format(name)) + r["identity"] = {} + r["identity"]["type"] = "None" + return r["identity"] def _ensure_identity_resource_id(subscription_id, resource_group, resource): From 28a48e64efab6e3d7fedda424e10ef98670f065e Mon Sep 17 00:00:00 2001 From: Haroon Feisal Date: Wed, 23 Feb 2022 14:56:18 -0500 Subject: [PATCH 04/13] Moved helper function to utils. --- src/containerapp/azext_containerapp/_utils.py | 11 +++++++++++ src/containerapp/azext_containerapp/custom.py | 18 ++---------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/containerapp/azext_containerapp/_utils.py b/src/containerapp/azext_containerapp/_utils.py index 573b5ead3a5..53655b8ae1b 100644 --- a/src/containerapp/azext_containerapp/_utils.py +++ b/src/containerapp/azext_containerapp/_utils.py @@ -274,3 +274,14 @@ def _get_existing_secrets(cmd, resource_group_name, name, containerapp_def): handle_raw_exception(e) containerapp_def["properties"]["configuration"]["secrets"] = secrets["value"] + +def _ensure_identity_resource_id(subscription_id, resource_group, resource): + from msrestazure.tools import resource_id, is_valid_resource_id + if is_valid_resource_id(resource): + return resource + + return resource_id(subscription=subscription_id, + resource_group=resource_group, + namespace='Microsoft.ManagedIdentity', + type='userAssignedIdentities', + name=resource) \ No newline at end of file diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index bbaf9cb5649..1ae499ea908 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -18,7 +18,7 @@ Ingress, Configuration, Template, RegistryCredentials, ContainerApp, Dapr, ContainerResources, Scale, Container) from ._utils import (_validate_subscription_registered, _get_location_from_resource_group, _ensure_location_allowed, parse_secret_flags, store_as_secret_and_return_secret_ref, parse_list_of_strings, parse_env_var_flags, - _generate_log_analytics_if_not_provided, _get_existing_secrets) + _generate_log_analytics_if_not_provided, _get_existing_secrets, _ensure_identity_resource_id) logger = get_logger(__name__) @@ -566,8 +566,6 @@ def delete_managed_environment(cmd, name, resource_group_name, no_wait=False): def assign_managed_identity(cmd, name, resource_group_name, identities=None, no_wait=False): _validate_subscription_registered(cmd, "Microsoft.App") - from azure.cli.core.commands.client_factory import get_subscription_id - # if no identities, then assign system by default if not identities: identities = ['[system]'] @@ -636,8 +634,6 @@ def assign_managed_identity(cmd, name, resource_group_name, identities=None, no_ def remove_managed_identity(cmd, name, resource_group_name, identities=None, no_wait=False): _validate_subscription_registered(cmd, "Microsoft.App") - from azure.cli.core.commands.client_factory import get_subscription_id - if not identities: identities = ['[system]'] @@ -710,7 +706,7 @@ def show_managed_identity(cmd, name, resource_group_name): r = ContainerAppClient.show(cmd=cmd, resource_group_name=resource_group_name, name=name) except CLIError as e: handle_raw_exception(e) - + try: return r["identity"] except: @@ -719,13 +715,3 @@ def show_managed_identity(cmd, name, resource_group_name): return r["identity"] -def _ensure_identity_resource_id(subscription_id, resource_group, resource): - from msrestazure.tools import resource_id, is_valid_resource_id - if is_valid_resource_id(resource): - return resource - - return resource_id(subscription=subscription_id, - resource_group=resource_group, - namespace='Microsoft.ManagedIdentity', - type='userAssignedIdentities', - name=resource) \ No newline at end of file From 3fad6131a5c91c7c30289417791fbae80370ddee Mon Sep 17 00:00:00 2001 From: Haroon Feisal Date: Wed, 23 Feb 2022 16:07:55 -0500 Subject: [PATCH 05/13] Require --identities flag when removing identities. --- src/containerapp/azext_containerapp/custom.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index 1ae499ea908..6960f3ca4da 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -631,11 +631,11 @@ 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=None, no_wait=False): +def remove_managed_identity(cmd, name, resource_group_name, identities, no_wait=False): _validate_subscription_registered(cmd, "Microsoft.App") - if not identities: - identities = ['[system]'] + # if not identities: + # identities = ['[system]'] remove_system_identity = '[system]' in identities remove_user_identities = [x for x in identities if x != '[system]'] From 9b743b309b15e5e69309600f8ea6681b522cae9f Mon Sep 17 00:00:00 2001 From: Haroon Feisal Date: Wed, 23 Feb 2022 16:11:38 -0500 Subject: [PATCH 06/13] Added message for assign identity with no specified identity. --- src/containerapp/azext_containerapp/custom.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index 6960f3ca4da..2122caaad56 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -569,6 +569,7 @@ def assign_managed_identity(cmd, name, resource_group_name, identities=None, no_ # if no identities, then assign system by default if not identities: identities = ['[system]'] + logger.warning('Identities not specified. Assigning managed system identity.') assign_system_identity = '[system]' in identities assign_user_identities = [x for x in identities if x != '[system]'] @@ -634,6 +635,7 @@ def assign_managed_identity(cmd, name, resource_group_name, identities=None, no_ def remove_managed_identity(cmd, name, resource_group_name, identities, no_wait=False): _validate_subscription_registered(cmd, "Microsoft.App") + # if identities=None, remove system identity # if not identities: # identities = ['[system]'] From a06865697f89ff55937f80acb603c0c3c15ffd2b Mon Sep 17 00:00:00 2001 From: Haroon Feisal Date: Wed, 23 Feb 2022 17:58:17 -0500 Subject: [PATCH 07/13] Added --assign-identity flag to containerapp create. --- .../azext_containerapp/_params.py | 1 + src/containerapp/azext_containerapp/custom.py | 28 +++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/containerapp/azext_containerapp/_params.py b/src/containerapp/azext_containerapp/_params.py index 85c355c66d7..450c5d9cef8 100644 --- a/src/containerapp/azext_containerapp/_params.py +++ b/src/containerapp/azext_containerapp/_params.py @@ -59,6 +59,7 @@ def load_arguments(self, _): c.argument('registry_pass', type=str, validator=validate_registry_pass, options_list=['--registry-password'], help="The password to log in container image registry server. If stored as a secret, value must start with \'secretref:\' followed by the secret name.") c.argument('registry_user', type=str, validator=validate_registry_user, options_list=['--registry-username'], help="The username to log in container image registry server") c.argument('secrets', type=str, options_list=['--secrets', '-s'], help="A list of secret(s) for the containerapp. Comma-separated values in 'key=value' format.") + c.argument('assign_identity', nargs='+', help="Space-separated identities. Use '[system]' to refer to the system assigned identity.") # Ingress with self.argument_context('containerapp', arg_group='Ingress') as c: diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index 2122caaad56..b3f104fff99 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -15,7 +15,7 @@ from ._client_factory import handle_raw_exception from ._clients import ManagedEnvironmentClient, ContainerAppClient from ._models import (ManagedEnvironment, VnetConfiguration, AppLogsConfiguration, LogAnalyticsConfiguration, - Ingress, Configuration, Template, RegistryCredentials, ContainerApp, Dapr, ContainerResources, Scale, Container) + Ingress, Configuration, Template, RegistryCredentials, ContainerApp, Dapr, ContainerResources, Scale, Container, ManagedServiceIdentity) from ._utils import (_validate_subscription_registered, _get_location_from_resource_group, _ensure_location_allowed, parse_secret_flags, store_as_secret_and_return_secret_ref, parse_list_of_strings, parse_env_var_flags, _generate_log_analytics_if_not_provided, _get_existing_secrets, _ensure_identity_resource_id) @@ -52,7 +52,8 @@ def create_containerapp(cmd, startup_command=None, args=None, tags=None, - no_wait=False): + no_wait=False, + assign_identity=[]): location = location or _get_location_from_resource_group(cmd.cli_ctx, resource_group_name) _validate_subscription_registered(cmd, "Microsoft.App") @@ -124,6 +125,28 @@ def create_containerapp(cmd, config_def["ingress"] = ingress_def config_def["registries"] = [registries_def] if registries_def is not None else None + # Identity actions + identity_def = ManagedServiceIdentity + identity_def["type"] = "None" + + assign_system_identity = '[system]' in assign_identity + assign_user_identities = [x for x in assign_identity if x != '[system]'] + + 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] = {} + scale_def = None if min_replicas is not None or max_replicas is not None: scale_def = Scale @@ -166,6 +189,7 @@ def create_containerapp(cmd, containerapp_def = ContainerApp 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 From eb74c55f8b847cdbc0ca8c51cae2e884be1643e3 Mon Sep 17 00:00:00 2001 From: Haroon Feisal Date: Thu, 24 Feb 2022 12:37:08 -0500 Subject: [PATCH 08/13] Moved assign-identity flag to containerapp create. --- src/containerapp/azext_containerapp/_params.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/containerapp/azext_containerapp/_params.py b/src/containerapp/azext_containerapp/_params.py index 450c5d9cef8..ccdb722644f 100644 --- a/src/containerapp/azext_containerapp/_params.py +++ b/src/containerapp/azext_containerapp/_params.py @@ -59,13 +59,15 @@ def load_arguments(self, _): c.argument('registry_pass', type=str, validator=validate_registry_pass, options_list=['--registry-password'], help="The password to log in container image registry server. If stored as a secret, value must start with \'secretref:\' followed by the secret name.") c.argument('registry_user', type=str, validator=validate_registry_user, options_list=['--registry-username'], help="The username to log in container image registry server") c.argument('secrets', type=str, options_list=['--secrets', '-s'], help="A list of secret(s) for the containerapp. Comma-separated values in 'key=value' format.") - c.argument('assign_identity', nargs='+', help="Space-separated identities. Use '[system]' to refer to the system assigned identity.") # Ingress with self.argument_context('containerapp', arg_group='Ingress') as c: c.argument('ingress', validator=validate_ingress, options_list=['--ingress'], default=None, arg_type=get_enum_type(['internal', 'external']), help="Ingress type that allows either internal or external+internal ingress traffic to the Containerapp.") c.argument('target_port', type=int, validator=validate_target_port, options_list=['--target-port'], help="The application port used for ingress traffic.") c.argument('transport', arg_type=get_enum_type(['auto', 'http', 'http2']), help="The transport protocol used for ingress traffic.") + + with self.argument_context('containerapp create') as c: + c.argument('assign_identity', nargs='+', help="Space-separated identities. Use '[system]' to refer to the system assigned identity.") with self.argument_context('containerapp scale') as c: c.argument('min_replicas', type=int, options_list=['--min-replicas'], help="The minimum number of containerapp replicas.") From 777609758d4aee024315b018a41e57194b201c22 Mon Sep 17 00:00:00 2001 From: Haroon Feisal Date: Thu, 24 Feb 2022 13:30:37 -0500 Subject: [PATCH 09/13] Fixed small logic error on remove identities when passing duplicate identities. Added warnings for certain edge cases. --- src/containerapp/azext_containerapp/custom.py | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index b3f104fff99..7f2ddea48fe 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -617,6 +617,9 @@ def assign_managed_identity(cmd, name, resource_group_name, identities=None, no_ containerapp_def["identity"] = {} containerapp_def["identity"]["type"] = "None" + if assign_system_identity and containerapp_def["identity"]["type"].__contains__("SystemAssigned"): + logger.warning("System identity is already assigned to containerapp") + # Assign correct type try: if containerapp_def["identity"]["type"] != "None": @@ -634,7 +637,7 @@ def assign_managed_identity(cmd, name, resource_group_name, identities=None, no_ except: # Always returns "type": "None" when CA has no previous identities pass - + if assign_user_identities: try: containerapp_def["identity"]["userAssignedIdentities"] @@ -644,9 +647,14 @@ 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: - r = _ensure_identity_resource_id(subscription_id, resource_group_name, r) - containerapp_def["identity"]["userAssignedIdentities"][r] = {} - + 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: + containerapp_def["identity"]["userAssignedIdentities"][r] = {} + 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 identity is not returned, do nothing @@ -659,15 +667,16 @@ def assign_managed_identity(cmd, name, resource_group_name, identities=None, no_ def remove_managed_identity(cmd, name, resource_group_name, identities, no_wait=False): _validate_subscription_registered(cmd, "Microsoft.App") - # if identities=None, remove system identity - # if not identities: - # identities = ['[system]'] - 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 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 try: containerapp_def = ContainerAppClient.show(cmd=cmd, resource_group_name=resource_group_name, name=name) @@ -699,15 +708,14 @@ def remove_managed_identity(cmd, name, resource_group_name, identities, no_wait= containerapp_def["identity"]["userAssignedIdentities"] except: containerapp_def["identity"]["userAssignedIdentities"] = {} - for id in remove_user_identities: given_id = id id = _ensure_identity_resource_id(subscription_id, resource_group_name, id) wasRemoved = False - for old_user_identities in containerapp_def["identity"]["userAssignedIdentities"]: - if old_user_identities.lower() == id.lower(): - containerapp_def["identity"]["userAssignedIdentities"].pop(old_user_identities) + for old_user_identity in containerapp_def["identity"]["userAssignedIdentities"]: + if old_user_identity.lower() == id.lower(): + containerapp_def["identity"]["userAssignedIdentities"].pop(old_user_identity) wasRemoved = True break From 0e5be6266453959587a710dbca0d739b5a25f2f9 Mon Sep 17 00:00:00 2001 From: Haroon Feisal Date: Thu, 24 Feb 2022 14:34:14 -0500 Subject: [PATCH 10/13] Updated param definition for identity assign --identity default. --- src/containerapp/azext_containerapp/_params.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/containerapp/azext_containerapp/_params.py b/src/containerapp/azext_containerapp/_params.py index ccdb722644f..fae5df13c08 100644 --- a/src/containerapp/azext_containerapp/_params.py +++ b/src/containerapp/azext_containerapp/_params.py @@ -107,3 +107,7 @@ def load_arguments(self, _): with self.argument_context('containerapp identity') as c: c.argument('identities', nargs='+', help="Space-separated identities. Use '[system]' to refer to the system assigned identity.") + + with self.argument_context('containerapp identity assign') as c: + c.argument('identities', nargs='+', help="Space-separated identities. Use '[system]' to refer to the system assigned identity. Default is '[system]'.") + From 13111e8fad15df068217bc2cc497e8f8aa3db24a Mon Sep 17 00:00:00 2001 From: Haroon Feisal Date: Thu, 24 Feb 2022 14:44:37 -0500 Subject: [PATCH 11/13] Added identity examples in help. --- src/containerapp/azext_containerapp/_help.py | 32 ++++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/containerapp/azext_containerapp/_help.py b/src/containerapp/azext_containerapp/_help.py index 4b223f507a2..81cca068d8a 100644 --- a/src/containerapp/azext_containerapp/_help.py +++ b/src/containerapp/azext_containerapp/_help.py @@ -196,22 +196,36 @@ # Identity Commands helps['containerapp identity'] = """ -type: group -short-summary: Manage service (managed) identities for a containerapp + type: group + short-summary: Manage service (managed) identities for a containerapp """ helps['containerapp identity assign'] = """ -type: command -short-summary: Assign a managed identity to a containerapp -long-summary: Managed identities can be user-assigned or system-assigned + type: command + short-summary: Assign a managed identity to a containerapp + long-summary: Managed identities can be user-assigned or system-assigned + examples: + - name: Assign system identity. + text: | + az containerapp identity assign + - name: Assign system and user identity. + text: | + az containerapp identity assign --identities [system] myAssignedId """ helps['containerapp identity remove'] = """ -type: command -short-summary: Remove a managed identity from a containerapp + type: command + short-summary: Remove a managed identity from a containerapp + examples: + - name: Remove system identity. + text: | + az containerapp identity remove [system] + - name: Remove system and user identity. + text: | + az containerapp identity remove --identities [system] myAssignedId """ helps['containerapp identity show'] = """ -type: command -short-summary: Show the containerapp's identity details + type: command + short-summary: Show the containerapp's identity details """ \ No newline at end of file From 48273f35e2ac3dcdc4cb54b83530764b1659bf99 Mon Sep 17 00:00:00 2001 From: Haroon Feisal Date: Fri, 25 Feb 2022 12:49:26 -0500 Subject: [PATCH 12/13] Made sure secrets were not removed when assigning identities. Added tolerance for [system] passed with capital letters. --- src/containerapp/azext_containerapp/custom.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index 7f2ddea48fe..0e41faea71f 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -595,6 +595,7 @@ def assign_managed_identity(cmd, name, resource_group_name, identities=None, no_ 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]'] @@ -609,6 +610,8 @@ def assign_managed_identity(cmd, name, resource_group_name, identities=None, no_ if not containerapp_def: raise CLIError("The containerapp '{}' does not exist".format(name)) + _get_existing_secrets(cmd, resource_group_name, name, containerapp_def) + # If identity not returned try: containerapp_def["identity"] @@ -629,7 +632,7 @@ def assign_managed_identity(cmd, name, resource_group_name, identities=None, no_ containerapp_def["identity"]["type"] = "SystemAssigned,UserAssigned" else: if assign_system_identity and assign_user_identities: - containerapp_def["identity"]["type"] = "SystemAssigned, UserAssigned" + containerapp_def["identity"]["type"] = "SystemAssigned,UserAssigned" elif assign_system_identity: containerapp_def["identity"]["type"] = "SystemAssigned" elif assign_user_identities: @@ -667,6 +670,7 @@ def assign_managed_identity(cmd, name, resource_group_name, identities=None, no_ def remove_managed_identity(cmd, name, resource_group_name, identities, 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) @@ -686,6 +690,8 @@ def remove_managed_identity(cmd, name, resource_group_name, identities, no_wait= if not containerapp_def: raise CLIError("The containerapp '{}' does not exist".format(name)) + _get_existing_secrets(cmd, resource_group_name, name, containerapp_def) + # If identity not returned try: containerapp_def["identity"] From e26f2a8b8a230aa96e5d8f081811c25f10f086a3 Mon Sep 17 00:00:00 2001 From: Haroon Feisal Date: Mon, 14 Mar 2022 16:57:32 -0400 Subject: [PATCH 13/13] Fixed error from merge. --- src/containerapp/azext_containerapp/custom.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index e4303d98a29..db5bdb00db2 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -333,8 +333,6 @@ def create_containerapp(cmd, tags=None, no_wait=False, assign_identity=[]): - location = location or _get_location_from_resource_group(cmd.cli_ctx, resource_group_name) - _validate_subscription_registered(cmd, "Microsoft.App") if yaml: