Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
c7cb17f
use 2022-03-01-preview sdk for app command and app identity command,…
jiec-msft Mar 20, 2022
de0e5b5
Add a temp version number
jiec-msft Mar 21, 2022
55c648b
Add params definition for identity remove
jiec-msft Mar 20, 2022
32bc31a
refine help and example for command group
jiec-msft Mar 20, 2022
1970509
refine help and example for command group
jiec-msft Mar 20, 2022
b3489f8
refine help and example for command group
jiec-msft Mar 20, 2022
7ee755a
add support to remove user-assigned managed identities.
jiec-msft Mar 21, 2022
f580a8b
mark user-assigned as preview for identity remove
jiec-msft Mar 22, 2022
a6887c3
refine identity remove
jiec-msft Mar 22, 2022
5ef4853
For identity assign
jiec-msft Mar 22, 2022
65b56dc
For identity assign: implement
jiec-msft Mar 22, 2022
f5f6ecf
refactor and add UT and add scenario test
jiec-msft Mar 22, 2022
0c87272
refine help info for app identity
jiec-msft Mar 22, 2022
ec22e89
hide role and scope from help info
jiec-msft Mar 22, 2022
9e7341d
For app create with managed identity:
jiec-msft Mar 22, 2022
94256b8
For app create with managed identity:
jiec-msft Mar 22, 2022
a0b7553
For app create with managed identity:
jiec-msft Mar 22, 2022
29df6b3
For app create with managed identity:
jiec-msft Mar 22, 2022
9ba7038
For app create with managed identity:
jiec-msft Mar 22, 2022
7a72f2d
For app create with managed identity:
jiec-msft Mar 22, 2022
310160e
For app identity force-set managed identity:
jiec-msft Mar 22, 2022
6202d84
For app identity force-set managed identity:
jiec-msft Mar 22, 2022
467ccf9
For app identity force-set managed identity:
jiec-msft Mar 22, 2022
4fc72df
remove not used import
jiec-msft Mar 23, 2022
243f0c4
fix role assignment bugs
jiec-msft Mar 23, 2022
71e3b0e
refine payload for force-set identities
jiec-msft Mar 23, 2022
3d6d9de
switch app show to use 2022-03-01-preview api version
jiec-msft Mar 23, 2022
8c0a61b
update deprecation info for role param
jiec-msft Mar 23, 2022
7b2eca7
refactor code
jiec-msft Mar 23, 2022
6d9975a
simplify force-set
jiec-msft Mar 23, 2022
125b6de
add version number
jiec-msft Mar 23, 2022
041b50a
refactor "updating" and "deleting"
jiec-msft Mar 24, 2022
218ca02
fix bug for async operation
jiec-msft Mar 24, 2022
4b74aaf
update version info
jiec-msft Mar 24, 2022
77ad4a2
Update version number
jiec-msft Mar 24, 2022
f5bf0cd
Update version to build a extension for testing team
jiec-msft Mar 24, 2022
69e9b17
refine warning: in the future -> in future
jiec-msft Mar 29, 2022
06a2cb3
refine help info for force-set params
jiec-msft Mar 29, 2022
16de216
refine example description for command force-set
jiec-msft Mar 29, 2022
37e67a2
refactor _format_identity in _app_factory
jiec-msft Mar 29, 2022
6510f0c
add scope and role from deprecated back to supported status in help i…
jiec-msft Mar 29, 2022
4906437
add example for role assignment for system-assigned managed identity.
jiec-msft Mar 29, 2022
0ff6f55
update version number for testing
jiec-msft Mar 29, 2022
50e7280
update version number and history.
jiec-msft Mar 30, 2022
5e3e36e
fix lint
jiec-msft Mar 30, 2022
d7a9b04
refine the CLIError, and categorize into different types
jiec-msft Mar 30, 2022
30b56dd
refine the CLIError, and categorize into different types
jiec-msft Mar 30, 2022
64b93ff
fix lint 2
jiec-msft Mar 30, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/spring-cloud/HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
Release History
===============
3.1.0
---
* Add support for user-assigned managed identity on App (Preview).

3.0.1
---
* `az spring-cloud app deploy` has new preview argument "--build-env" to specify build module and jvm version and so on.
Expand Down
34 changes: 30 additions & 4 deletions src/spring-cloud/azext_spring_cloud/_app_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# pylint: disable=wrong-import-order
from azure.cli.core.azclierror import FileOperationError, InvalidArgumentValueError
from .vendored_sdks.appplatform.v2022_01_01_preview import models
from .vendored_sdks.appplatform.v2022_03_01_preview import models as models_20220301preview
from azure.cli.core.util import get_file_json


Expand All @@ -23,10 +24,35 @@ def _format_properties(self, **kwargs):
kwargs['temporary_disk'] = self._load_temp_disk(**kwargs)
return models.AppResourceProperties(**kwargs)

def _format_identity(self, assign_identity=None, **_):
if assign_identity is not None:
assign_type = 'systemassigned' if assign_identity else 'None'
return models.ManagedIdentityProperties(type=assign_type)
def _format_identity(self, system_assigned=None, user_assigned=None, **_):
target_identity_type = self._get_identity_assign_type(system_assigned, user_assigned)
user_identity_payload = self._get_user_identity_payload(user_assigned)
identity_props = None
if target_identity_type != models_20220301preview.ManagedIdentityType.NONE:
identity_props = models_20220301preview.ManagedIdentityProperties()
identity_props.type = target_identity_type
identity_props.user_assigned_identities = user_identity_payload
return identity_props

def _get_identity_assign_type(self, system_assigned=None, user_assigned=None):
target_identity_type = models_20220301preview.ManagedIdentityType.NONE
if system_assigned and user_assigned:
target_identity_type = models_20220301preview.ManagedIdentityType.SYSTEM_ASSIGNED_USER_ASSIGNED
elif system_assigned:
target_identity_type = models_20220301preview.ManagedIdentityType.SYSTEM_ASSIGNED
elif user_assigned:
target_identity_type = models_20220301preview.ManagedIdentityType.USER_ASSIGNED
return target_identity_type

def _get_user_identity_payload(self, user_assigned=None):
if not user_assigned:
return None
user_identity_payload = {}
for user_identity_resource_id in user_assigned:
user_identity_payload[user_identity_resource_id] = models_20220301preview.UserAssignedManagedIdentity()
if len(user_identity_payload) == 0:
user_identity_payload = None
return user_identity_payload

def _load_temp_disk(self, enable_temporary_disk=None, **_):
if enable_temporary_disk is not None:
Expand Down
137 changes: 137 additions & 0 deletions src/spring-cloud/azext_spring_cloud/_app_managed_identity_validator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------


from azure.cli.core.azclierror import InvalidArgumentValueError
from azure.mgmt.core.tools import is_valid_resource_id
from knack.log import get_logger


logger = get_logger(__name__)


OBSOLETE_APP_IDENTITY_REMOVE = "Remove managed identities without \"system-assigned\" or \"user-assigned\" parameters is obsolete, will only remove system-assigned managed identity, and will not be supported in a future release."
WARNING_NO_USER_IDENTITY_RESOURCE_ID = "No resource ID of user-assigned managed identity is given for parameter \"user-assigned\", will remove ALL user-assigned managed identities."
OBSOLETE_APP_IDENTITY_ASSIGN = "Assign managed identities without \"system-assigned\" or \"user-assigned\" parameters is obsolete, will only enable system-assigned managed identity, and will not be supported in a future release."
ENABLE_LOWER = "enable"
DISABLE_LOWER = "disable"


def validate_app_identity_remove_or_warning(namespace):
if namespace.system_assigned is None and namespace.user_assigned is None:
logger.warning(OBSOLETE_APP_IDENTITY_REMOVE)
if namespace.user_assigned is not None:
if not isinstance(namespace.user_assigned, list):
raise InvalidArgumentValueError("Parameter value for \"user-assigned\" should be empty or a list of space-separated managed identity resource ID.")
if len(namespace.user_assigned) == 0:
logger.warning(WARNING_NO_USER_IDENTITY_RESOURCE_ID)
namespace.user_assigned = _normalized_user_identitiy_resource_id_list(namespace.user_assigned)
for resource_id in namespace.user_assigned:
is_valid = _is_valid_user_assigned_managed_identity_resource_id(resource_id)
if not is_valid:
raise InvalidArgumentValueError("Invalid user-assigned managed identity resource ID \"{}\".".format(resource_id))


def _normalized_user_identitiy_resource_id_list(user_identity_resource_id_list):
result = []
if not user_identity_resource_id_list:
return result
for id in user_identity_resource_id_list:
result.append(id.strip().lower())
return result


def _is_valid_user_assigned_managed_identity_resource_id(resource_id):
if not is_valid_resource_id(resource_id.lower()):
return False
if "/providers/Microsoft.ManagedIdentity/userAssignedIdentities/".lower() not in resource_id.lower():
return False
return True


def validate_app_identity_assign_or_warning(namespace):
_warn_if_no_identity_type_params(namespace)
_validate_role_and_scope_should_use_together(namespace)
_validate_role_and_scope_should_not_use_with_user_identity(namespace)
_validate_user_identity_resource_id(namespace)
_normalize_user_identity_resource_id(namespace)


def _warn_if_no_identity_type_params(namespace):
if namespace.system_assigned is None and namespace.user_assigned is None:
logger.warning(OBSOLETE_APP_IDENTITY_ASSIGN)


def _validate_role_and_scope_should_use_together(namespace):
if _has_role_or_scope(namespace) and not _has_role_and_scope(namespace):
raise InvalidArgumentValueError("Parameter \"role\" and \"scope\" should be used together.")


def _validate_role_and_scope_should_not_use_with_user_identity(namespace):
if _has_role_and_scope(namespace) and _only_has_user_assigned(namespace):
raise InvalidArgumentValueError("Invalid to use parameter \"role\" and \"scope\" with \"user-assigned\" parameter.")


def _has_role_and_scope(namespace):
return namespace.role and namespace.scope


def _has_role_or_scope(namespace):
return namespace.role or namespace.scope


def _only_has_user_assigned(namespace):
return (namespace.user_assigned) and (not namespace.system_assigned)


def _validate_user_identity_resource_id(namespace):
if namespace.user_assigned:
for resource_id in namespace.user_assigned:
if not _is_valid_user_assigned_managed_identity_resource_id(resource_id):
raise InvalidArgumentValueError("Invalid user-assigned managed identity resource ID \"{}\".".format(resource_id))


def _normalize_user_identity_resource_id(namespace):
if namespace.user_assigned:
namespace.user_assigned = _normalized_user_identitiy_resource_id_list(namespace.user_assigned)


def validate_create_app_with_user_identity_or_warning(namespace):
_validate_user_identity_resource_id(namespace)
_normalize_user_identity_resource_id(namespace)


def validate_create_app_with_system_identity_or_warning(namespace):
"""
Note: assign_identity is deprecated, use system_assigned instead.
"""
if namespace.system_assigned is not None and namespace.assign_identity is not None:
raise InvalidArgumentValueError('Parameter "system-assigned" should not use together with "assign-identity".')
if namespace.assign_identity is not None:
namespace.system_assigned = namespace.assign_identity


def validate_app_force_set_system_identity_or_warning(namespace):
if namespace.system_assigned is None:
raise InvalidArgumentValueError('Parameter "system-assigned" expected at least one argument.')
namespace.system_assigned = namespace.system_assigned.strip().lower()
if namespace.system_assigned.strip().lower() not in (ENABLE_LOWER, DISABLE_LOWER):
raise InvalidArgumentValueError('Allowed values for "system-assigned" are: {}, {}.'.format(ENABLE_LOWER, DISABLE_LOWER))


def validate_app_force_set_user_identity_or_warning(namespace):
if namespace.user_assigned is None or len(namespace.user_assigned) == 0:
raise InvalidArgumentValueError('Parameter "user-assigned" expected at least one argument.')
if len(namespace.user_assigned) == 1:
single_element = namespace.user_assigned[0].strip().lower()
if single_element != DISABLE_LOWER and not _is_valid_user_assigned_managed_identity_resource_id(single_element):
raise InvalidArgumentValueError('Allowed values for "user-assigned" are: {}, space-separated user-assigned managed identity resource IDs.'.format(DISABLE_LOWER))
elif single_element == DISABLE_LOWER:
namespace.user_assigned = [DISABLE_LOWER]
else:
_normalize_user_identity_resource_id(namespace)
else:
_validate_user_identity_resource_id(namespace)
_normalize_user_identity_resource_id(namespace)
7 changes: 7 additions & 0 deletions src/spring-cloud/azext_spring_cloud/_client_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
from .vendored_sdks.appplatform.v2022_01_01_preview import (
AppPlatformManagementClient as AppPlatformManagementClient_20220101preview
)
from .vendored_sdks.appplatform.v2022_03_01_preview import (
AppPlatformManagementClient as AppPlatformManagementClient_20220301preview
)
from .vendored_sdks.appplatform.v2021_06_01_preview import (
AppPlatformManagementClient as AppPlatformManagementClient_20210601preview
)
Expand All @@ -19,6 +22,10 @@
)


def cf_spring_cloud_20220301preview(cli_ctx, *_):
return get_mgmt_service_client(cli_ctx, AppPlatformManagementClient_20220301preview)


def cf_spring_cloud_20220101preview(cli_ctx, *_):
return get_mgmt_service_client(cli_ctx, AppPlatformManagementClient_20220101preview)

Expand Down
11 changes: 11 additions & 0 deletions src/spring-cloud/azext_spring_cloud/_clierror.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from azure.cli.core.azclierror import UserFault


class ConflictRequestError(UserFault):
""" Conflict request: 409 error """
pass
32 changes: 25 additions & 7 deletions src/spring-cloud/azext_spring_cloud/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,25 +256,31 @@

helps['spring-cloud app identity'] = """
type: group
short-summary: Manage an app's managed service identity.
short-summary: Manage an app's managed identities.
"""

helps['spring-cloud app identity assign'] = """
type: command
short-summary: Enable managed service identity on an app.
short-summary: Enable system-assigned managed identity or assign user-assigned managed identities to an app.
examples:
- name: Enable the system assigned identity.
text: az spring-cloud app identity assign -n MyApp -s MyCluster -g MyResourceGroup
text: az spring-cloud app identity assign -n MyApp -s MyCluster -g MyResourceGroup --system-assigned
- 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
text: az spring-cloud app identity assign -n MyApp -s MyCluster -g MyResourceGroup --system-assigned --role Reader --scope /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/xxxxx/providers/Microsoft.KeyVault/vaults/xxxxx
- name: Assign two user-assigned managed identities to an app.
text: az spring-cloud app identity assign -n MyApp -s MyCluster -g MyResourceGroup --user-assigned IdentityResourceId1 IdentityResourceId2
"""

helps['spring-cloud app identity remove'] = """
type: command
short-summary: Remove managed service identity from an app.
short-summary: Remove managed 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
- name: Remove the system-assigned managed identity from an app.
text: az spring-cloud app identity remove -n MyApp -s MyCluster -g MyResourceGroup --system-assigned
- name: Remove the system-assigned and user-assigned managed identities from an app.
text: az spring-cloud app identity remove -n MyApp -s MyCluster -g MyResourceGroup --system-assigned --user-assigned IdentityResourceId1 IdentityResourceId2
- name: Remove ALL user-assigned managed identities from an app.
text: az spring-cloud app identity remove -n MyApp -s MyCluster -g MyResourceGroup --user-assigned
"""

helps['spring-cloud app identity show'] = """
Expand All @@ -285,6 +291,18 @@
text: az spring-cloud app identity show -n MyApp -s MyCluster -g MyResourceGroup
"""

helps['spring-cloud app identity force-set'] = """
type: command
short-summary: Force set managed identities on an app.
examples:
- name: Force remove all managed identities on an app.
text: az spring-cloud app identity force-set -n MyApp -s MyCluster -g MyResourceGroup --system-assigned disable --user-assigned disable
- name: Force remove all user-assigned managed identities on an app, and enable or keep system-assigned managed identity.
text: az spring-cloud app identity force-set -n MyApp -s MyCluster -g MyResourceGroup --system-assigned enable --user-assigned disable
- name: Force remove system-assigned managed identity on an app, and assign or keep user-assigned managed identities.
text: az spring-cloud app identity force-set -n MyApp -s MyCluster -g MyResourceGroup --system-assigned disable --user-assigned IdentityResourceId1 IdentityResourceId2
"""
Comment on lines +294 to +304
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this command? Can its function be replaced by the spring-cloud app identity remove command?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally we don't need these commands. It's related to a data consistency problem among managed identity RP and my RP. There can be case when user identity is assigned to an app in my side, but deleted in managed identity RP due to data in-consistency. These commands leverage put method to reset data back with one command.

app identity remove/assign is done with patch http method, there can be case, there is no identity assigned to an app in my RP, but managed identity RP still has some identity assigned to the app.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thanks~


helps['spring-cloud app set-deployment'] = """
type: command
short-summary: Set production deployment of an app.
Expand Down
52 changes: 48 additions & 4 deletions src/spring-cloud/azext_spring_cloud/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
from ._app_validator import (fulfill_deployment_param, active_deployment_exist,
ensure_not_active_deployment, validate_deloy_path, validate_deloyment_create_path,
validate_cpu, validate_memory, fulfill_deployment_param_or_warning, active_deployment_exist_or_warning)
from ._app_managed_identity_validator import (validate_create_app_with_user_identity_or_warning,
validate_create_app_with_system_identity_or_warning,
validate_app_force_set_system_identity_or_warning,
validate_app_force_set_user_identity_or_warning)
from ._utils import ApiType


Expand Down Expand Up @@ -186,8 +190,21 @@ def load_arguments(self, _):
c.argument('assign_endpoint', arg_type=get_three_state_flag(),
help='If true, assign endpoint URL for direct access.', default=False,
options_list=['--assign-endpoint', c.deprecate(target='--is-public', redirect='--assign-endpoint', hide=True)])
c.argument('assign_identity', arg_type=get_three_state_flag(),
help='If true, assign managed service identity.')
c.argument('assign_identity',
arg_type=get_three_state_flag(),
validator=validate_create_app_with_system_identity_or_warning,
deprecate_info=c.deprecate(target='--assign-identity',
redirect='--system-assigned',
hide=True),
help='Enable system-assigned managed identity.')
c.argument('system_assigned',
arg_type=get_three_state_flag(),
help='Enable system-assigned managed identity.')
c.argument('user_assigned',
is_preview=True,
nargs='+',
validator=validate_create_app_with_user_identity_or_warning,
help="Space-separated user-assigned managed identity resource IDs to assgin to an app.")
c.argument('cpu', arg_type=cpu_type, default="1")
c.argument('memory', arg_type=memort_type, default="1Gi")
c.argument('instance_count', type=int,
Expand Down Expand Up @@ -236,8 +253,35 @@ def load_arguments(self, _):
c.argument('name', name_type, help='Name of app.', validator=active_deployment_exist_or_warning)

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")
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")
c.argument('system_assigned',
arg_type=get_three_state_flag(),
help="Enable system-assigned managed identity on an app.")
c.argument('user_assigned',
is_preview=True,
nargs='+',
help="Space-separated user-assigned managed identity resource IDs to assgin to an app.")

with self.argument_context('spring-cloud app identity remove') as c:
c.argument('system_assigned',
arg_type=get_three_state_flag(),
help="Remove system-assigned managed identity.")
c.argument('user_assigned',
is_preview=True,
nargs='*',
help="Space-separated user-assigned managed identity resource IDs to remove. If no ID is provided, remove ALL user-assigned managed identities.")

with self.argument_context('spring-cloud app identity force-set') as c:
c.argument('system_assigned',
validator=validate_app_force_set_system_identity_or_warning,
help="Allowed values: [\"enable\", \"disable\"]. Use \"enable\" to enable or keep system-assigned managed identity. Use \"disable\" to remove system-assigned managed identity.")
c.argument('user_assigned',
nargs='+',
validator=validate_app_force_set_user_identity_or_warning,
help="Allowed values: [\"disable\", space-separated user-assigned managed identity resource IDs]. Use \"disable\" to remove all user-assigned managed identities, use resource IDs to assign or keep user-assigned managed identities.")

def prepare_logs_argument(c):
'''`app log tail` is deprecated. `app logs` is the new choice. They share the same command processor.'''
Expand Down
5 changes: 4 additions & 1 deletion src/spring-cloud/azext_spring_cloud/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ def app_create(cmd, client, resource_group, service, name,
jvm_options=None,
# app.create
assign_identity=None,
system_assigned=None,
user_assigned=None,
# app.update
enable_persistent_storage=None,
persistent_storage=None,
Expand All @@ -71,7 +73,8 @@ def app_create(cmd, client, resource_group, service, name,
}

create_app_kwargs = {
'assign_identity': assign_identity,
'system_assigned': system_assigned,
'user_assigned': user_assigned,
'enable_temporary_disk': True,
'enable_persistent_storage': enable_persistent_storage,
'persistent_storage': persistent_storage,
Expand Down
Loading