Skip to content

Commit

Permalink
spring-cloud: add support to enable/disable MSI (Azure#1523)
Browse files Browse the repository at this point in the history
  • Loading branch information
leonard520 authored Apr 16, 2020
1 parent e0700d8 commit 666bab7
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 28 deletions.
31 changes: 31 additions & 0 deletions src/spring-cloud/azext_spring_cloud/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,37 @@
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: 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 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 -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'] = """
type: command
short-summary: Remove managed service identity from an app.
examples:
- name: Remove the system assigned identity from 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: Display an app's managed identity info.
text: az spring-cloud app identity show -n MyApp -s MyCluster -g MyResourceGroup
"""

helps['spring-cloud app set-deployment'] = """
type: command
short-summary: Set production deployment of an app.
Expand Down
6 changes: 6 additions & 0 deletions src/spring-cloud/azext_spring_cloud/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -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='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(),
Expand All @@ -55,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)
Expand Down
5 changes: 5 additions & 0 deletions src/spring-cloud/azext_spring_cloud/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ def load_command_table(self, _):
g.custom_command('restart', 'app_restart', supports_no_wait=True)
g.custom_command('logs', 'app_tail_log')

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:
g.custom_command('tail', 'app_tail_log')
Expand Down
92 changes: 87 additions & 5 deletions src/spring-cloud/azext_spring_cloud/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -100,7 +101,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))
Expand All @@ -119,8 +121,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)

Expand All @@ -147,7 +155,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():
Expand Down Expand Up @@ -178,9 +189,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)

Expand Down Expand Up @@ -425,6 +440,69 @@ 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, role=None, scope=None):
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
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):
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(
Expand All @@ -441,7 +519,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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -90,6 +92,7 @@
ProvisioningState,
ConfigServerState,
TraceProxyState,
ManagedIdentityType,
TestKeyType,
AppResourceProvisioningState,
UserSourceType,
Expand All @@ -115,6 +118,7 @@
'GitPatternRepository',
'LogFileUrlResponse',
'LogSpecification',
'ManagedIdentityProperties',
'MetricDimension',
'MetricSpecification',
'NameAvailability',
Expand Down Expand Up @@ -142,6 +146,7 @@
'ProvisioningState',
'ConfigServerState',
'TraceProxyState',
'ManagedIdentityType',
'TestKeyType',
'AppResourceProvisioningState',
'UserSourceType',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)


Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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


Expand Down Expand Up @@ -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.
Expand Down
Loading

0 comments on commit 666bab7

Please sign in to comment.