From 2c1640f3fe0b06432fde03febd210954ed9d86c8 Mon Sep 17 00:00:00 2001 From: Xiaoyun Ding Date: Wed, 8 Apr 2020 14:15:24 +0800 Subject: [PATCH 1/6] spring-cloud: add support to enable/disable MSI --- src/spring-cloud/azext_spring_cloud/_help.py | 20 +++++++ .../azext_spring_cloud/_params.py | 2 + .../azext_spring_cloud/commands.py | 3 + src/spring-cloud/azext_spring_cloud/custom.py | 59 +++++++++++++++++-- .../appplatform/models/__init__.py | 5 ++ .../_app_platform_management_client_enums.py | 8 +++ .../appplatform/models/_models.py | 29 +++++++++ .../appplatform/models/_models_py3.py | 31 +++++++++- .../operations/_apps_operations.py | 32 ++++------ 9 files changed, 161 insertions(+), 28 deletions(-) diff --git a/src/spring-cloud/azext_spring_cloud/_help.py b/src/spring-cloud/azext_spring_cloud/_help.py index 73a6cb8123a..370ab8ed524 100644 --- a/src/spring-cloud/azext_spring_cloud/_help.py +++ b/src/spring-cloud/azext_spring_cloud/_help.py @@ -147,6 +147,26 @@ short-summary: Show logs of an app instance, logs will be streamed when setting '-f/--follow'. """ +helps['spring-cloud app identity'] = """ + type: group + short-summary: Operate on assign an Azure Active Directory Identity for this app for use with key management services like Azure KeyVault identity. +""" + +helps['spring-cloud app identity assign'] = """ + type: command + short-summary: Assign an Azure Active Directory Identity for this app. +""" + +helps['spring-cloud app identity remove'] = """ + type: command + short-summary: Remove an Azure Active Directory Identity for this app. +""" + +helps['spring-cloud app identity show'] = """ + type: command + short-summary: Show the Azure Active Directory Identity for this app. +""" + helps['spring-cloud app set-deployment'] = """ type: command short-summary: Set production deployment of an app. diff --git a/src/spring-cloud/azext_spring_cloud/_params.py b/src/spring-cloud/azext_spring_cloud/_params.py index 4fdbd766155..c2abba39c8f 100644 --- a/src/spring-cloud/azext_spring_cloud/_params.py +++ b/src/spring-cloud/azext_spring_cloud/_params.py @@ -45,6 +45,8 @@ def load_arguments(self, _): with self.argument_context('spring-cloud app create') as c: c.argument( 'is_public', arg_type=get_three_state_flag(), help='If true, assign public domain', default=False) + c.argument('assign_identity', arg_type=get_three_state_flag(), + help='Generate and assign an Azure Active Directory Identity for this app for use with key management services like Azure KeyVault.') with self.argument_context('spring-cloud app update') as c: c.argument('is_public', arg_type=get_three_state_flag(), diff --git a/src/spring-cloud/azext_spring_cloud/commands.py b/src/spring-cloud/azext_spring_cloud/commands.py index c12e7b55564..42cbb9a941b 100644 --- a/src/spring-cloud/azext_spring_cloud/commands.py +++ b/src/spring-cloud/azext_spring_cloud/commands.py @@ -57,6 +57,9 @@ def load_command_table(self, _): g.custom_command('stop', 'app_stop', supports_no_wait=True) g.custom_command('restart', 'app_restart', supports_no_wait=True) g.custom_command('logs', 'app_tail_log') + g.custom_command('identity assign', 'app_identity_assign') + g.custom_command('identity remove', 'app_identity_remove') + g.custom_command('identity show', 'app_identity_show') with self.command_group('spring-cloud app log', client_factory=cf_spring_cloud, deprecate_info=g.deprecate(redirect='az spring-cloud app logs', hide=True)) as g: diff --git a/src/spring-cloud/azext_spring_cloud/custom.py b/src/spring-cloud/azext_spring_cloud/custom.py index d9ec9d24ce4..0b1540c451a 100644 --- a/src/spring-cloud/azext_spring_cloud/custom.py +++ b/src/spring-cloud/azext_spring_cloud/custom.py @@ -100,7 +100,8 @@ def app_create(cmd, client, resource_group, service, name, runtime_version=None, jvm_options=None, env=None, - enable_persistent_storage=None): + enable_persistent_storage=None, + assign_identity=None): apps = _get_all_apps(client, resource_group, service) if name in apps: raise CLIError("App '{}' already exists.".format(name)) @@ -119,8 +120,14 @@ def app_create(cmd, client, resource_group, service, name, resource = client.services.get(resource_group, service) location = resource.location + app_resource = models.AppResource() + app_resource.properties = properties + app_resource.location = location + if assign_identity is True: + app_resource.identity = models.ManagedIdentityProperties(type="systemassigned") + poller = client.apps.create_or_update( - resource_group, service, name, properties, location) + resource_group, service, name, app_resource) while poller.done() is False: sleep(APP_CREATE_OR_UPDATE_SLEEP_INTERVAL) @@ -147,7 +154,10 @@ def app_create(cmd, client, resource_group, service, name, properties = models.AppResourceProperties( active_deployment_name=DEFAULT_DEPLOYMENT_NAME, public=is_public) - app_poller = client.apps.update(resource_group, service, name, properties, location) + app_resource.properties = properties + app_resource.location = location + + app_poller = client.apps.update(resource_group, service, name, app_resource) logger.warning( "[4/4] Updating app '{}' (this operation can take a while to complete)".format(name)) while not poller.done() or not app_poller.done(): @@ -178,9 +188,13 @@ def app_update(cmd, client, resource_group, service, name, resource = client.services.get(resource_group, service) location = resource.location + app_resource = models.AppResource() + app_resource.properties = properties + app_resource.location = location + logger.warning("[1/2] updating app '{}'".format(name)) poller = client.apps.update( - resource_group, service, name, properties, location) + resource_group, service, name, app_resource) while poller.done() is False: sleep(APP_CREATE_OR_UPDATE_SLEEP_INTERVAL) @@ -425,6 +439,37 @@ def app_tail_log(cmd, client, resource_group, service, name, instance=None, foll raise exceptions[0] +def app_identity_assign(cmd, client, resource_group, service, name): + app_resource = models.AppResource() + identity = models.ManagedIdentityProperties(type="systemassigned") + properties = models.AppResourceProperties() + resource = client.services.get(resource_group, service) + location = resource.location + + app_resource.identity = identity + app_resource.properties = properties + app_resource.location = location + return client.apps.update(resource_group, service, name, app_resource) + + +def app_identity_remove(cmd, client, resource_group, service, name): + app_resource = models.AppResource() + identity = models.ManagedIdentityProperties(type="none") + properties = models.AppResourceProperties() + resource = client.services.get(resource_group, service) + location = resource.location + + app_resource.identity = identity + app_resource.properties = properties + app_resource.location = location + return client.apps.update(resource_group, service, name, app_resource) + + +def app_identity_show(cmd, client, resource_group, service, name): + app = client.apps.get(resource_group, service, name) + return app.identity + + def app_set_deployment(cmd, client, resource_group, service, name, deployment): deployments = _get_all_deployments(client, resource_group, service, name) active_deployment = client.apps.get( @@ -441,7 +486,11 @@ def app_set_deployment(cmd, client, resource_group, service, name, deployment): resource = client.services.get(resource_group, service) location = resource.location - return client.apps.update(resource_group, service, name, properties, location) + app_resource = models.AppResource() + app_resource.properties = properties + app_resource.location = location + + return client.apps.update(resource_group, service, name, app_resource) def deployment_create(cmd, client, resource_group, service, app, name, diff --git a/src/spring-cloud/azext_spring_cloud/vendored_sdks/appplatform/models/__init__.py b/src/spring-cloud/azext_spring_cloud/vendored_sdks/appplatform/models/__init__.py index 64b07494a26..aaaceb0bcf2 100644 --- a/src/spring-cloud/azext_spring_cloud/vendored_sdks/appplatform/models/__init__.py +++ b/src/spring-cloud/azext_spring_cloud/vendored_sdks/appplatform/models/__init__.py @@ -26,6 +26,7 @@ from ._models_py3 import GitPatternRepository from ._models_py3 import LogFileUrlResponse from ._models_py3 import LogSpecification + from ._models_py3 import ManagedIdentityProperties from ._models_py3 import MetricDimension from ._models_py3 import MetricSpecification from ._models_py3 import NameAvailability @@ -62,6 +63,7 @@ from ._models import GitPatternRepository from ._models import LogFileUrlResponse from ._models import LogSpecification + from ._models import ManagedIdentityProperties from ._models import MetricDimension from ._models import MetricSpecification from ._models import NameAvailability @@ -90,6 +92,7 @@ ProvisioningState, ConfigServerState, TraceProxyState, + ManagedIdentityType, TestKeyType, AppResourceProvisioningState, UserSourceType, @@ -115,6 +118,7 @@ 'GitPatternRepository', 'LogFileUrlResponse', 'LogSpecification', + 'ManagedIdentityProperties', 'MetricDimension', 'MetricSpecification', 'NameAvailability', @@ -142,6 +146,7 @@ 'ProvisioningState', 'ConfigServerState', 'TraceProxyState', + 'ManagedIdentityType', 'TestKeyType', 'AppResourceProvisioningState', 'UserSourceType', diff --git a/src/spring-cloud/azext_spring_cloud/vendored_sdks/appplatform/models/_app_platform_management_client_enums.py b/src/spring-cloud/azext_spring_cloud/vendored_sdks/appplatform/models/_app_platform_management_client_enums.py index 2978170fb08..c4b4fd4c440 100644 --- a/src/spring-cloud/azext_spring_cloud/vendored_sdks/appplatform/models/_app_platform_management_client_enums.py +++ b/src/spring-cloud/azext_spring_cloud/vendored_sdks/appplatform/models/_app_platform_management_client_enums.py @@ -42,6 +42,14 @@ class TraceProxyState(str, Enum): updating = "Updating" +class ManagedIdentityType(str, Enum): + + none = "None" + system_assigned = "SystemAssigned" + user_assigned = "UserAssigned" + system_assigned_user_assigned = "SystemAssigned,UserAssigned" + + class TestKeyType(str, Enum): primary = "Primary" diff --git a/src/spring-cloud/azext_spring_cloud/vendored_sdks/appplatform/models/_models.py b/src/spring-cloud/azext_spring_cloud/vendored_sdks/appplatform/models/_models.py index bc4fe4abbe8..62e040c706a 100644 --- a/src/spring-cloud/azext_spring_cloud/vendored_sdks/appplatform/models/_models.py +++ b/src/spring-cloud/azext_spring_cloud/vendored_sdks/appplatform/models/_models.py @@ -91,6 +91,8 @@ class AppResource(ProxyResource): :vartype type: str :param properties: Properties of the App resource :type properties: ~azure.mgmt.appplatform.models.AppResourceProperties + :param identity: The Managed Identity type of the app resource + :type identity: ~azure.mgmt.appplatform.models.ManagedIdentityProperties :param location: The GEO location of the application, always the same with its parent resource :type location: str @@ -107,12 +109,14 @@ class AppResource(ProxyResource): 'name': {'key': 'name', 'type': 'str'}, 'type': {'key': 'type', 'type': 'str'}, 'properties': {'key': 'properties', 'type': 'AppResourceProperties'}, + 'identity': {'key': 'identity', 'type': 'ManagedIdentityProperties'}, 'location': {'key': 'location', 'type': 'str'}, } def __init__(self, **kwargs): super(AppResource, self).__init__(**kwargs) self.properties = kwargs.get('properties', None) + self.identity = kwargs.get('identity', None) self.location = kwargs.get('location', None) @@ -778,6 +782,31 @@ def __init__(self, **kwargs): self.blob_duration = kwargs.get('blob_duration', None) +class ManagedIdentityProperties(Model): + """Managed identity properties retrieved from ARM request headers. + + :param type: Possible values include: 'None', 'SystemAssigned', + 'UserAssigned', 'SystemAssigned,UserAssigned' + :type type: str or ~azure.mgmt.appplatform.models.ManagedIdentityType + :param principal_id: + :type principal_id: str + :param tenant_id: + :type tenant_id: str + """ + + _attribute_map = { + 'type': {'key': 'type', 'type': 'str'}, + 'principal_id': {'key': 'principalId', 'type': 'str'}, + 'tenant_id': {'key': 'tenantId', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(ManagedIdentityProperties, self).__init__(**kwargs) + self.type = kwargs.get('type', None) + self.principal_id = kwargs.get('principal_id', None) + self.tenant_id = kwargs.get('tenant_id', None) + + class MetricDimension(Model): """Specifications of the Dimension of metrics. diff --git a/src/spring-cloud/azext_spring_cloud/vendored_sdks/appplatform/models/_models_py3.py b/src/spring-cloud/azext_spring_cloud/vendored_sdks/appplatform/models/_models_py3.py index a65d1ccb8f2..0e84315de2d 100644 --- a/src/spring-cloud/azext_spring_cloud/vendored_sdks/appplatform/models/_models_py3.py +++ b/src/spring-cloud/azext_spring_cloud/vendored_sdks/appplatform/models/_models_py3.py @@ -91,6 +91,8 @@ class AppResource(ProxyResource): :vartype type: str :param properties: Properties of the App resource :type properties: ~azure.mgmt.appplatform.models.AppResourceProperties + :param identity: The Managed Identity type of the app resource + :type identity: ~azure.mgmt.appplatform.models.ManagedIdentityProperties :param location: The GEO location of the application, always the same with its parent resource :type location: str @@ -107,12 +109,14 @@ class AppResource(ProxyResource): 'name': {'key': 'name', 'type': 'str'}, 'type': {'key': 'type', 'type': 'str'}, 'properties': {'key': 'properties', 'type': 'AppResourceProperties'}, + 'identity': {'key': 'identity', 'type': 'ManagedIdentityProperties'}, 'location': {'key': 'location', 'type': 'str'}, } - def __init__(self, *, properties=None, location: str=None, **kwargs) -> None: + def __init__(self, *, properties=None, identity=None, location: str=None, **kwargs) -> None: super(AppResource, self).__init__(**kwargs) self.properties = properties + self.identity = identity self.location = location @@ -778,6 +782,31 @@ def __init__(self, *, name: str=None, display_name: str=None, blob_duration: str self.blob_duration = blob_duration +class ManagedIdentityProperties(Model): + """Managed identity properties retrieved from ARM request headers. + + :param type: Possible values include: 'None', 'SystemAssigned', + 'UserAssigned', 'SystemAssigned,UserAssigned' + :type type: str or ~azure.mgmt.appplatform.models.ManagedIdentityType + :param principal_id: + :type principal_id: str + :param tenant_id: + :type tenant_id: str + """ + + _attribute_map = { + 'type': {'key': 'type', 'type': 'str'}, + 'principal_id': {'key': 'principalId', 'type': 'str'}, + 'tenant_id': {'key': 'tenantId', 'type': 'str'}, + } + + def __init__(self, *, type=None, principal_id: str=None, tenant_id: str=None, **kwargs) -> None: + super(ManagedIdentityProperties, self).__init__(**kwargs) + self.type = type + self.principal_id = principal_id + self.tenant_id = tenant_id + + class MetricDimension(Model): """Specifications of the Dimension of metrics. diff --git a/src/spring-cloud/azext_spring_cloud/vendored_sdks/appplatform/operations/_apps_operations.py b/src/spring-cloud/azext_spring_cloud/vendored_sdks/appplatform/operations/_apps_operations.py index 482213a628a..4b8a5e5d58c 100644 --- a/src/spring-cloud/azext_spring_cloud/vendored_sdks/appplatform/operations/_apps_operations.py +++ b/src/spring-cloud/azext_spring_cloud/vendored_sdks/appplatform/operations/_apps_operations.py @@ -113,9 +113,7 @@ def get( def _create_or_update_initial( - self, resource_group_name, service_name, app_name, properties=None, location=None, custom_headers=None, raw=False, **operation_config): - app_resource = models.AppResource(properties=properties, location=location) - + self, resource_group_name, service_name, app_name, app_resource, custom_headers=None, raw=False, **operation_config): # Construct URL url = self.create_or_update.metadata['url'] path_format_arguments = { @@ -167,7 +165,7 @@ def _create_or_update_initial( return deserialized def create_or_update( - self, resource_group_name, service_name, app_name, properties=None, location=None, custom_headers=None, raw=False, polling=True, **operation_config): + self, resource_group_name, service_name, app_name, app_resource, custom_headers=None, raw=False, polling=True, **operation_config): """Create a new App or update an exiting App. :param resource_group_name: The name of the resource group that @@ -178,11 +176,8 @@ def create_or_update( :type service_name: str :param app_name: The name of the App resource. :type app_name: str - :param properties: Properties of the App resource - :type properties: ~azure.mgmt.appplatform.models.AppResourceProperties - :param location: The GEO location of the application, always the same - with its parent resource - :type location: str + :param app_resource: Parameters for the create or update operation + :type app_resource: ~azure.mgmt.appplatform.models.AppResource :param dict custom_headers: headers that will be added to the request :param bool raw: The poller return type is ClientRawResponse, the direct response alongside the deserialized response @@ -200,8 +195,7 @@ def create_or_update( resource_group_name=resource_group_name, service_name=service_name, app_name=app_name, - properties=properties, - location=location, + app_resource=app_resource, custom_headers=custom_headers, raw=True, **operation_config @@ -285,9 +279,7 @@ def delete( def _update_initial( - self, resource_group_name, service_name, app_name, properties=None, location=None, custom_headers=None, raw=False, **operation_config): - app_resource = models.AppResource(properties=properties, location=location) - + self, resource_group_name, service_name, app_name, app_resource, custom_headers=None, raw=False, **operation_config): # Construct URL url = self.update.metadata['url'] path_format_arguments = { @@ -339,7 +331,7 @@ def _update_initial( return deserialized def update( - self, resource_group_name, service_name, app_name, properties=None, location=None, custom_headers=None, raw=False, polling=True, **operation_config): + self, resource_group_name, service_name, app_name, app_resource, custom_headers=None, raw=False, polling=True, **operation_config): """Operation to update an exiting App. :param resource_group_name: The name of the resource group that @@ -350,11 +342,8 @@ def update( :type service_name: str :param app_name: The name of the App resource. :type app_name: str - :param properties: Properties of the App resource - :type properties: ~azure.mgmt.appplatform.models.AppResourceProperties - :param location: The GEO location of the application, always the same - with its parent resource - :type location: str + :param app_resource: Parameters for the update operation + :type app_resource: ~azure.mgmt.appplatform.models.AppResource :param dict custom_headers: headers that will be added to the request :param bool raw: The poller return type is ClientRawResponse, the direct response alongside the deserialized response @@ -372,8 +361,7 @@ def update( resource_group_name=resource_group_name, service_name=service_name, app_name=app_name, - properties=properties, - location=location, + app_resource=app_resource, custom_headers=custom_headers, raw=True, **operation_config From 523e37016e7a87d2b3669f3574e0f6ee69dd209d Mon Sep 17 00:00:00 2001 From: Xiaoyun Ding Date: Wed, 8 Apr 2020 16:40:29 +0800 Subject: [PATCH 2/6] Refine text in help command --- src/spring-cloud/azext_spring_cloud/_help.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/spring-cloud/azext_spring_cloud/_help.py b/src/spring-cloud/azext_spring_cloud/_help.py index 370ab8ed524..8c1523cd48e 100644 --- a/src/spring-cloud/azext_spring_cloud/_help.py +++ b/src/spring-cloud/azext_spring_cloud/_help.py @@ -149,22 +149,22 @@ helps['spring-cloud app identity'] = """ type: group - short-summary: Operate on assign an Azure Active Directory Identity for this app for use with key management services like Azure KeyVault identity. + short-summary: manage an app's managed service identity """ helps['spring-cloud app identity assign'] = """ type: command - short-summary: Assign an Azure Active Directory Identity for this app. + short-summary: Enable managed service identity on an app. """ helps['spring-cloud app identity remove'] = """ type: command - short-summary: Remove an Azure Active Directory Identity for this app. + short-summary: Remove managed service identity from a app. """ helps['spring-cloud app identity show'] = """ type: command - short-summary: Show the Azure Active Directory Identity for this app. + short-summary: Display app's managed identity info. """ helps['spring-cloud app set-deployment'] = """ From cf9dbd417a20bf3ee7cfb43fe9c82e9ec7850c6e Mon Sep 17 00:00:00 2001 From: Xiaoyun Ding Date: Thu, 9 Apr 2020 09:50:37 +0800 Subject: [PATCH 3/6] Refine helper text & Group identity command --- src/spring-cloud/azext_spring_cloud/_help.py | 13 +++++++++++-- src/spring-cloud/azext_spring_cloud/_params.py | 2 +- src/spring-cloud/azext_spring_cloud/commands.py | 8 +++++--- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/spring-cloud/azext_spring_cloud/_help.py b/src/spring-cloud/azext_spring_cloud/_help.py index 8c1523cd48e..e762aadcd69 100644 --- a/src/spring-cloud/azext_spring_cloud/_help.py +++ b/src/spring-cloud/azext_spring_cloud/_help.py @@ -149,22 +149,31 @@ helps['spring-cloud app identity'] = """ type: group - short-summary: manage an app's managed service identity + short-summary: Manage an app's managed service identity. """ helps['spring-cloud app identity assign'] = """ type: command short-summary: Enable managed service identity on an app. + examples: + - name: Enable managed service identity on an app. + text: az spring-cloud app identity assign -n MyApp -s MyCluster -g MyResourceGroup """ helps['spring-cloud app identity remove'] = """ type: command - short-summary: Remove managed service identity from a app. + short-summary: Remove managed service identity from an app. + examples: + - name: Enable managed service identity on an app. + text: az spring-cloud app identity remove -n MyApp -s MyCluster -g MyResourceGroup """ helps['spring-cloud app identity show'] = """ type: command short-summary: Display app's managed identity info. + examples: + - name: Enable managed service identity on an app. + text: az spring-cloud app identity show -n MyApp -s MyCluster -g MyResourceGroup """ helps['spring-cloud app set-deployment'] = """ diff --git a/src/spring-cloud/azext_spring_cloud/_params.py b/src/spring-cloud/azext_spring_cloud/_params.py index c2abba39c8f..54e4d5f283b 100644 --- a/src/spring-cloud/azext_spring_cloud/_params.py +++ b/src/spring-cloud/azext_spring_cloud/_params.py @@ -46,7 +46,7 @@ def load_arguments(self, _): c.argument( 'is_public', arg_type=get_three_state_flag(), help='If true, assign public domain', default=False) c.argument('assign_identity', arg_type=get_three_state_flag(), - help='Generate and assign an Azure Active Directory Identity for this app for use with key management services like Azure KeyVault.') + help='Manage an app\'s managed service identity.') with self.argument_context('spring-cloud app update') as c: c.argument('is_public', arg_type=get_three_state_flag(), diff --git a/src/spring-cloud/azext_spring_cloud/commands.py b/src/spring-cloud/azext_spring_cloud/commands.py index 42cbb9a941b..f351fd1e263 100644 --- a/src/spring-cloud/azext_spring_cloud/commands.py +++ b/src/spring-cloud/azext_spring_cloud/commands.py @@ -57,9 +57,11 @@ def load_command_table(self, _): g.custom_command('stop', 'app_stop', supports_no_wait=True) g.custom_command('restart', 'app_restart', supports_no_wait=True) g.custom_command('logs', 'app_tail_log') - g.custom_command('identity assign', 'app_identity_assign') - g.custom_command('identity remove', 'app_identity_remove') - g.custom_command('identity show', 'app_identity_show') + + with self.command_group('spring-cloud app identity', client_factory=cf_spring_cloud) as g: + g.custom_command('assign', 'app_identity_assign') + g.custom_command('remove', 'app_identity_remove') + g.custom_show_command('show', 'app_identity_show') with self.command_group('spring-cloud app log', client_factory=cf_spring_cloud, deprecate_info=g.deprecate(redirect='az spring-cloud app logs', hide=True)) as g: From 5acc13adae46187f5b98dfcf13f6be3d038debd1 Mon Sep 17 00:00:00 2001 From: Xiaoyun Ding Date: Thu, 9 Apr 2020 09:55:06 +0800 Subject: [PATCH 4/6] Tiny fix --- src/spring-cloud/azext_spring_cloud/_help.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/spring-cloud/azext_spring_cloud/_help.py b/src/spring-cloud/azext_spring_cloud/_help.py index e762aadcd69..2b531c3ab04 100644 --- a/src/spring-cloud/azext_spring_cloud/_help.py +++ b/src/spring-cloud/azext_spring_cloud/_help.py @@ -164,7 +164,7 @@ type: command short-summary: Remove managed service identity from an app. examples: - - name: Enable managed service identity on an app. + - name: Remove managed service identity from an app. text: az spring-cloud app identity remove -n MyApp -s MyCluster -g MyResourceGroup """ @@ -172,7 +172,7 @@ type: command short-summary: Display app's managed identity info. examples: - - name: Enable managed service identity on an app. + - name: Display app's managed identity info. text: az spring-cloud app identity show -n MyApp -s MyCluster -g MyResourceGroup """ From a85fccb67274b5ffc997343e9a1c60ef21074328 Mon Sep 17 00:00:00 2001 From: Xiaoyun Ding Date: Thu, 9 Apr 2020 16:45:33 +0800 Subject: [PATCH 5/6] Add support for role assignment Refine text and examples --- src/spring-cloud/azext_spring_cloud/_help.py | 8 ++-- .../azext_spring_cloud/_params.py | 6 ++- src/spring-cloud/azext_spring_cloud/custom.py | 37 ++++++++++++++++++- 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/spring-cloud/azext_spring_cloud/_help.py b/src/spring-cloud/azext_spring_cloud/_help.py index 2b531c3ab04..de67d95840a 100644 --- a/src/spring-cloud/azext_spring_cloud/_help.py +++ b/src/spring-cloud/azext_spring_cloud/_help.py @@ -156,15 +156,17 @@ type: command short-summary: Enable managed service identity on an app. examples: - - name: Enable managed service identity on an app. + - name: Enable the system assigned identity. text: az spring-cloud app identity assign -n MyApp -s MyCluster -g MyResourceGroup + - name: Enable the system assigned identity on an app with the 'Reader' role. + text: az spring-cloud app identity assign -n MyApp --role Reader --scope /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/xxxxx/providers/Microsoft.KeyVault/vaults/xxxxx """ helps['spring-cloud app identity remove'] = """ type: command short-summary: Remove managed service identity from an app. examples: - - name: Remove managed service identity from an app. + - name: Remove the system assigned identity from an app. text: az spring-cloud app identity remove -n MyApp -s MyCluster -g MyResourceGroup """ @@ -172,7 +174,7 @@ type: command short-summary: Display app's managed identity info. examples: - - name: Display app's managed identity info. + - name: Display an app's managed identity info. text: az spring-cloud app identity show -n MyApp -s MyCluster -g MyResourceGroup """ diff --git a/src/spring-cloud/azext_spring_cloud/_params.py b/src/spring-cloud/azext_spring_cloud/_params.py index 54e4d5f283b..07c508f915b 100644 --- a/src/spring-cloud/azext_spring_cloud/_params.py +++ b/src/spring-cloud/azext_spring_cloud/_params.py @@ -46,7 +46,7 @@ def load_arguments(self, _): c.argument( 'is_public', arg_type=get_three_state_flag(), help='If true, assign public domain', default=False) c.argument('assign_identity', arg_type=get_three_state_flag(), - help='Manage an app\'s managed service identity.') + help='If true, assign managed service identity.') with self.argument_context('spring-cloud app update') as c: c.argument('is_public', arg_type=get_three_state_flag(), @@ -57,6 +57,10 @@ def load_arguments(self, _): c.argument('deployment', options_list=[ '--deployment', '-d'], help='Name of an existing deployment of the app. Default to the production deployment if not specified.', validator=validate_deployment_name) + with self.argument_context('spring-cloud app identity assign') as c: + c.argument('scope', help="The scope the managed identity has access to") + c.argument('role', help="Role name or id the managed identity will be assigned") + with self.argument_context('spring-cloud app logs') as c: c.argument('instance', options_list=['--instance', '-i'], help='Name of an existing instance of the deployment.') c.argument('lines', type=int, help='Number of lines to show. Maximum is 10000', validator=validate_log_lines) diff --git a/src/spring-cloud/azext_spring_cloud/custom.py b/src/spring-cloud/azext_spring_cloud/custom.py index 0b1540c451a..8a98ab96aed 100644 --- a/src/spring-cloud/azext_spring_cloud/custom.py +++ b/src/spring-cloud/azext_spring_cloud/custom.py @@ -16,6 +16,7 @@ from knack.log import get_logger from .azure_storage_file import FileService from azure.cli.core.util import sdk_no_wait +from azure.cli.core.profiles import ResourceType, get_sdk from ast import literal_eval from azure.cli.core.commands import cached_put from ._utils import _get_rg_location @@ -439,7 +440,7 @@ def app_tail_log(cmd, client, resource_group, service, name, instance=None, foll raise exceptions[0] -def app_identity_assign(cmd, client, resource_group, service, name): +def app_identity_assign(cmd, client, resource_group, service, name, role=None, scope=None): app_resource = models.AppResource() identity = models.ManagedIdentityProperties(type="systemassigned") properties = models.AppResourceProperties() @@ -449,7 +450,39 @@ def app_identity_assign(cmd, client, resource_group, service, name): app_resource.identity = identity app_resource.properties = properties app_resource.location = location - return client.apps.update(resource_group, service, name, app_resource) + client.apps.update(resource_group, service, name, app_resource) + app = client.apps.get(resource_group, service, name) + if role: + principal_id = app.identity.principal_id + + from azure.cli.core.commands import arm as _arm + identity_role_id = _arm.resolve_role_id(cmd.cli_ctx, role, scope) + from azure.cli.core.commands.client_factory import get_mgmt_service_client + assignments_client = get_mgmt_service_client(cmd.cli_ctx, ResourceType.MGMT_AUTHORIZATION).role_assignments + RoleAssignmentCreateParameters = get_sdk(cmd.cli_ctx, ResourceType.MGMT_AUTHORIZATION, + 'RoleAssignmentCreateParameters', mod='models', + operation_group='role_assignments') + parameters = RoleAssignmentCreateParameters(role_definition_id=identity_role_id, principal_id=principal_id) + logger.info("Creating an assignment with a role '%s' on the scope of '%s'", identity_role_id, scope) + retry_times = 36 + assignment_name = _arm._gen_guid() + for l in range(0, retry_times): + try: + assignments_client.create(scope=scope, role_assignment_name=assignment_name, + parameters=parameters) + break + except CloudError as ex: + if 'role assignment already exists' in ex.message: + logger.info('Role assignment already exists') + break + elif l < retry_times and ' does not exist in the directory ' in ex.message: + sleep(APP_CREATE_OR_UPDATE_SLEEP_INTERVAL) + logger.warning('Retrying role assignment creation: %s/%s', l + 1, + retry_times) + continue + else: + raise + return app def app_identity_remove(cmd, client, resource_group, service, name): From 859783a97c0df63ff5bd624d9157498a9acafdc0 Mon Sep 17 00:00:00 2001 From: Xiaoyun Ding Date: Thu, 9 Apr 2020 17:04:47 +0800 Subject: [PATCH 6/6] Fix linter check --- src/spring-cloud/azext_spring_cloud/_help.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spring-cloud/azext_spring_cloud/_help.py b/src/spring-cloud/azext_spring_cloud/_help.py index de67d95840a..806266638dc 100644 --- a/src/spring-cloud/azext_spring_cloud/_help.py +++ b/src/spring-cloud/azext_spring_cloud/_help.py @@ -159,7 +159,7 @@ - name: Enable the system assigned identity. text: az spring-cloud app identity assign -n MyApp -s MyCluster -g MyResourceGroup - name: Enable the system assigned identity on an app with the 'Reader' role. - text: az spring-cloud app identity assign -n MyApp --role Reader --scope /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/xxxxx/providers/Microsoft.KeyVault/vaults/xxxxx + text: az spring-cloud app identity assign -n MyApp -s MyCluster -g MyResourceGroup --role Reader --scope /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/xxxxx/providers/Microsoft.KeyVault/vaults/xxxxx """ helps['spring-cloud app identity remove'] = """