Skip to content

Commit 3fbbdfc

Browse files
{Compute} az vmss identity: Migrate commands to aaz-based implementation (#32685)
1 parent 69461fa commit 3fbbdfc

File tree

8 files changed

+3018
-2951
lines changed

8 files changed

+3018
-2951
lines changed

src/azure-cli/azure/cli/command_modules/vm/_vm_utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,3 +780,9 @@ class IdentityType(Enum):
780780
USER_ASSIGNED = 'UserAssigned'
781781
SYSTEM_ASSIGNED_USER_ASSIGNED = 'SystemAssigned, UserAssigned'
782782
NONE = 'None'
783+
784+
785+
class UpgradeMode(Enum):
786+
AUTOMATIC = 'Automatic'
787+
MANUAL = 'Manual'
788+
ROLLING = 'Rolling'

src/azure-cli/azure/cli/command_modules/vm/commands.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,10 +396,12 @@ def load_command_table(self, _):
396396
g.custom_command('create', 'create_dedicated_host_group')
397397
g.generic_update_command('update')
398398

399-
with self.command_group('vmss', compute_vmss_sdk, operation_group='virtual_machine_scale_sets') as g:
399+
with self.command_group('vmss') as g:
400400
g.custom_command('identity assign', 'assign_vmss_identity', validator=process_assign_identity_namespace)
401-
g.custom_command('identity remove', 'remove_vmss_identity', validator=process_remove_identity_namespace, min_api='2017-12-01', is_preview=True)
401+
g.custom_command('identity remove', 'remove_vmss_identity', validator=process_remove_identity_namespace, is_preview=True)
402402
g.custom_show_command('identity show', 'show_vmss_identity')
403+
404+
with self.command_group('vmss', compute_vmss_sdk, operation_group='virtual_machine_scale_sets') as g:
403405
g.custom_command('application set', 'set_vmss_applications', validator=process_set_applications_namespace, min_api='2021-07-01')
404406
g.custom_command('application list', 'list_vmss_applications', min_api='2021-07-01')
405407
g.custom_command('create', 'create_vmss', transform=DeploymentOutputLongRunningOperation(self.cli_ctx, 'Starting vmss create'), supports_no_wait=True, table_transformer=deployment_validate_table_format, validator=process_vmss_create_namespace, exception_handler=handle_template_based_exception)

src/azure-cli/azure/cli/command_modules/vm/custom.py

Lines changed: 92 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -842,8 +842,8 @@ def show_vm_identity(cmd, resource_group_name, vm_name):
842842

843843

844844
def show_vmss_identity(cmd, resource_group_name, vm_name):
845-
client = _compute_client_factory(cmd.cli_ctx)
846-
return client.virtual_machine_scale_sets.get(resource_group_name, vm_name).identity
845+
vm = get_vmss_by_aaz(cmd, resource_group_name, vm_name)
846+
return vm.get("identity", {}) if vm else None
847847

848848

849849
def assign_vm_identity(cmd, resource_group_name, vm_name, assign_identity=None, identity_role=None,
@@ -2564,45 +2564,6 @@ def list_vm_extension_images(
25642564

25652565

25662566
# region VirtualMachines Identity
2567-
def _remove_identities(cmd, resource_group_name, name, identities, getter, setter):
2568-
from ._vm_utils import MSI_LOCAL_ID
2569-
ResourceIdentityType = cmd.get_models('ResourceIdentityType', operation_group='virtual_machines')
2570-
remove_system_assigned_identity = False
2571-
if MSI_LOCAL_ID in identities:
2572-
remove_system_assigned_identity = True
2573-
identities.remove(MSI_LOCAL_ID)
2574-
resource = getter(cmd, resource_group_name, name)
2575-
if resource.identity is None:
2576-
return None
2577-
emsis_to_remove = []
2578-
if identities:
2579-
existing_emsis = {x.lower() for x in (resource.identity.user_assigned_identities or {}).keys()}
2580-
emsis_to_remove = {x.lower() for x in identities}
2581-
non_existing = emsis_to_remove.difference(existing_emsis)
2582-
if non_existing:
2583-
raise CLIError("'{}' are not associated with '{}'".format(','.join(non_existing), name))
2584-
if not list(existing_emsis - emsis_to_remove): # if all emsis are gone, we need to update the type
2585-
if resource.identity.type == ResourceIdentityType.user_assigned:
2586-
resource.identity.type = ResourceIdentityType.none
2587-
elif resource.identity.type == ResourceIdentityType.system_assigned_user_assigned:
2588-
resource.identity.type = ResourceIdentityType.system_assigned
2589-
2590-
resource.identity.user_assigned_identities = None
2591-
if remove_system_assigned_identity:
2592-
resource.identity.type = (ResourceIdentityType.none
2593-
if resource.identity.type == ResourceIdentityType.system_assigned
2594-
else ResourceIdentityType.user_assigned)
2595-
2596-
if emsis_to_remove:
2597-
if resource.identity.type not in [ResourceIdentityType.none, ResourceIdentityType.system_assigned]:
2598-
resource.identity.user_assigned_identities = {}
2599-
for identity in emsis_to_remove:
2600-
resource.identity.user_assigned_identities[identity] = None
2601-
2602-
result = LongRunningOperation(cmd.cli_ctx)(setter(resource_group_name, name, resource))
2603-
return result.identity
2604-
2605-
26062567
def _remove_identities_by_aaz(cmd, resource_group_name, name, identities, getter, setter):
26072568
from ._vm_utils import MSI_LOCAL_ID
26082569

@@ -2647,6 +2608,10 @@ def _remove_identities_by_aaz(cmd, resource_group_name, name, identities, getter
26472608
existing_identity['type'] = IdentityType.NONE.value
26482609

26492610
result = LongRunningOperation(cmd.cli_ctx)(setter(resource_group_name, name, resource))
2611+
2612+
if not result:
2613+
return None
2614+
26502615
return result.get('identity') or None
26512616

26522617

@@ -3589,49 +3554,64 @@ def reset_linux_ssh(cmd, resource_group_name, vm_name, no_wait=False):
35893554
# region VirtualMachineScaleSets
35903555
def assign_vmss_identity(cmd, resource_group_name, vmss_name, assign_identity=None, identity_role=None,
35913556
identity_role_id=None, identity_scope=None):
3592-
VirtualMachineScaleSetIdentity, UpgradeMode, ResourceIdentityType, VirtualMachineScaleSetUpdate = cmd.get_models(
3593-
'VirtualMachineScaleSetIdentity', 'UpgradeMode', 'ResourceIdentityType', 'VirtualMachineScaleSetUpdate')
3594-
IdentityUserAssignedIdentitiesValue = cmd.get_models(
3595-
'VirtualMachineScaleSetIdentityUserAssignedIdentitiesValue') or cmd.get_models('UserAssignedIdentitiesValue')
3596-
from azure.cli.core.commands.arm import assign_identity as assign_identity_helper
3597-
client = _compute_client_factory(cmd.cli_ctx)
3598-
_, _, external_identities, enable_local_identity = _build_identities_info(assign_identity)
3557+
identity, _, external_identities, enable_local_identity = _build_identities_info(assign_identity)
3558+
from ._vm_utils import assign_identity as assign_identity_helper, UpgradeMode
3559+
3560+
command_args = {'resource_group': resource_group_name, 'vm_scale_set_name': vmss_name}
35993561

36003562
def getter():
3601-
return client.virtual_machine_scale_sets.get(resource_group_name, vmss_name)
3563+
return get_vmss_by_aaz(cmd, resource_group_name, vmss_name)
36023564

36033565
def setter(vmss, external_identities=external_identities):
3604-
3605-
if vmss.identity and vmss.identity.type == ResourceIdentityType.system_assigned_user_assigned:
3606-
identity_types = ResourceIdentityType.system_assigned_user_assigned
3607-
elif vmss.identity and vmss.identity.type == ResourceIdentityType.system_assigned and external_identities:
3608-
identity_types = ResourceIdentityType.system_assigned_user_assigned
3609-
elif vmss.identity and vmss.identity.type == ResourceIdentityType.user_assigned and enable_local_identity:
3610-
identity_types = ResourceIdentityType.system_assigned_user_assigned
3566+
if vmss.get('identity', {}).get('type', None) == IdentityType.SYSTEM_ASSIGNED_USER_ASSIGNED.value:
3567+
identity_types = IdentityType.SYSTEM_ASSIGNED_USER_ASSIGNED.value
3568+
elif vmss.get('identity', {}).get('type', None) == IdentityType.SYSTEM_ASSIGNED.value and external_identities:
3569+
identity_types = IdentityType.SYSTEM_ASSIGNED_USER_ASSIGNED.value
3570+
elif vmss.get('identity', {}).get('type', None) == IdentityType.USER_ASSIGNED.value and enable_local_identity:
3571+
identity_types = IdentityType.SYSTEM_ASSIGNED_USER_ASSIGNED.value
36113572
elif external_identities and enable_local_identity:
3612-
identity_types = ResourceIdentityType.system_assigned_user_assigned
3573+
identity_types = IdentityType.SYSTEM_ASSIGNED_USER_ASSIGNED.value
36133574
elif external_identities:
3614-
identity_types = ResourceIdentityType.user_assigned
3575+
identity_types = IdentityType.USER_ASSIGNED.value
36153576
else:
3616-
identity_types = ResourceIdentityType.system_assigned
3617-
vmss.identity = VirtualMachineScaleSetIdentity(type=identity_types)
3618-
if external_identities:
3619-
vmss.identity.user_assigned_identities = {}
3620-
for identity in external_identities:
3621-
vmss.identity.user_assigned_identities[identity] = IdentityUserAssignedIdentitiesValue()
3622-
vmss_patch = VirtualMachineScaleSetUpdate()
3623-
vmss_patch.identity = vmss.identity
3624-
poller = client.virtual_machine_scale_sets.begin_update(resource_group_name, vmss_name, vmss_patch)
3625-
return LongRunningOperation(cmd.cli_ctx)(poller)
3577+
identity_types = IdentityType.SYSTEM_ASSIGNED.value
3578+
3579+
if identity_types == IdentityType.SYSTEM_ASSIGNED_USER_ASSIGNED.value:
3580+
command_args['mi_system_assigned'] = "True"
3581+
command_args['mi_user_assigned'] = []
3582+
elif identity_types == IdentityType.USER_ASSIGNED.value:
3583+
command_args['mi_user_assigned'] = []
3584+
else:
3585+
command_args['mi_system_assigned'] = "True"
3586+
command_args['mi_user_assigned'] = []
3587+
3588+
if vmss.get('identity', {}).get('userAssignedIdentities', None):
3589+
for key in vmss.get('identity').get('userAssignedIdentities').keys():
3590+
command_args['mi_user_assigned'].append(key)
3591+
3592+
if identity.get('userAssignedIdentities'):
3593+
for key in identity.get('userAssignedIdentities', {}).keys():
3594+
if key not in command_args['mi_user_assigned']:
3595+
command_args['mi_user_assigned'].append(key)
3596+
3597+
from .operations.vmss import VMSSPatch
3598+
update_vmss_identity = VMSSPatch(cli_ctx=cmd.cli_ctx)(command_args=command_args)
3599+
LongRunningOperation(cmd.cli_ctx)(update_vmss_identity)
3600+
result = update_vmss_identity.result()
3601+
return result
36263602

36273603
assign_identity_helper(cmd.cli_ctx, getter, setter, identity_role=identity_role_id, identity_scope=identity_scope)
3628-
vmss = client.virtual_machine_scale_sets.get(resource_group_name, vmss_name)
3629-
if vmss.upgrade_policy.mode == UpgradeMode.manual:
3604+
3605+
vmss = getter()
3606+
if vmss.get('upgradePolicy', {}).get('mode', '') == UpgradeMode.MANUAL.value:
36303607
logger.warning("With manual upgrade mode, you will need to run 'az vmss update-instances -g %s -n %s "
36313608
"--instance-ids *' to propagate the change", resource_group_name, vmss_name)
36323609

3633-
return _construct_identity_info(identity_scope, identity_role, vmss.identity.principal_id,
3634-
vmss.identity.user_assigned_identities)
3610+
return _construct_identity_info(
3611+
identity_scope,
3612+
identity_role,
3613+
vmss.get('identity').get('principalId') if vmss.get('identity') else None,
3614+
vmss.get('identity').get('userAssignedIdentities') if vmss.get('identity') else None)
36353615

36363616

36373617
# pylint: disable=too-many-locals, too-many-statements
@@ -4201,6 +4181,24 @@ def get_vmss(cmd, resource_group_name, name, instance_id=None, include_user_data
42014181
return client.virtual_machine_scale_sets.get(resource_group_name, name)
42024182

42034183

4184+
def get_vmss_by_aaz(cmd, resource_group_name, name, instance_id=None, include_user_data=False):
4185+
from .operations.vmss import VMSSShow
4186+
from .operations.vmss_vms import VMSSVMSShow
4187+
4188+
command_args = {
4189+
'resource_group': resource_group_name,
4190+
'vm_scale_set_name': name,
4191+
}
4192+
4193+
if include_user_data:
4194+
command_args['expand'] = 'userData'
4195+
4196+
if instance_id is not None:
4197+
command_args['instance_id'] = instance_id
4198+
return VMSSVMSShow(cli_ctx=cmd.cli_ctx)(command_args=command_args)
4199+
return VMSSShow(cli_ctx=cmd.cli_ctx)(command_args=command_args)
4200+
4201+
42044202
def _check_vmss_hyper_v_generation(cli_ctx, vmss):
42054203
hyper_v_generation = get_hyper_v_generation_from_vmss(
42064204
cli_ctx, vmss.virtual_machine_profile.storage_profile.image_reference, vmss.location)
@@ -5405,24 +5403,35 @@ def vmss_run_command_show(cmd,
54055403

54065404
# region VirtualMachineScaleSets Identity
54075405
def remove_vmss_identity(cmd, resource_group_name, vmss_name, identities=None):
5408-
client = _compute_client_factory(cmd.cli_ctx)
5406+
def setter(resource_group_name, vmss_name, vmss):
5407+
command_args = {
5408+
'resource_group': resource_group_name,
5409+
'vm_scale_set_name': vmss_name
5410+
}
54095411

5410-
def _get_vmss(_, resource_group_name, vmss_name):
5411-
return client.virtual_machine_scale_sets.get(resource_group_name, vmss_name)
5412+
if vmss.get('identity') and vmss['identity'].get('type') == IdentityType.USER_ASSIGNED.value:
5413+
# NOTE: The literal 'UserAssigned' is intentionally appended as a marker for
5414+
# VMSSIdentityRemove._format_content, which uses it to apply special handling
5415+
# for purely user-assigned identities. It is not a real identity resource ID.
5416+
command_args['mi_user_assigned'] = \
5417+
list(vmss.get('identity', {}).get('userAssignedIdentities', {}).keys()) + ['UserAssigned']
5418+
elif vmss.get('identity') and vmss['identity'].get('type') == IdentityType.SYSTEM_ASSIGNED.value:
5419+
command_args['mi_user_assigned'] = []
5420+
command_args['mi_system_assigned'] = 'True'
5421+
elif vmss.get('identity') and vmss['identity'].get('type') == IdentityType.SYSTEM_ASSIGNED_USER_ASSIGNED.value:
5422+
command_args['mi_user_assigned'] = list(vmss.get('identity', {}).get('userAssignedIdentities', {}).keys())
5423+
command_args['mi_system_assigned'] = 'True'
5424+
else:
5425+
command_args['mi_user_assigned'] = []
54125426

5413-
def _set_vmss(resource_group_name, name, vmss_instance):
5414-
VirtualMachineScaleSetUpdate = cmd.get_models('VirtualMachineScaleSetUpdate',
5415-
operation_group='virtual_machine_scale_sets')
5416-
vmss_update = VirtualMachineScaleSetUpdate(identity=vmss_instance.identity)
5417-
return client.virtual_machine_scale_sets.begin_update(resource_group_name, vmss_name, vmss_update)
5427+
from .operations.vmss import VMSSIdentityRemove
5428+
return VMSSIdentityRemove(cli_ctx=cmd.cli_ctx)(command_args=command_args)
54185429

54195430
if identities is None:
54205431
from ._vm_utils import MSI_LOCAL_ID
54215432
identities = [MSI_LOCAL_ID]
54225433

5423-
return _remove_identities(cmd, resource_group_name, vmss_name, identities,
5424-
_get_vmss,
5425-
_set_vmss)
5434+
return _remove_identities_by_aaz(cmd, resource_group_name, vmss_name, identities, get_vmss_by_aaz, setter)
54265435
# endregion
54275436

54285437

src/azure-cli/azure/cli/command_modules/vm/operations/vmss.py

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33
# Licensed under the MIT License. See License.txt in the project root for license information.
44
# --------------------------------------------------------------------------------------------
55
# pylint: disable=no-self-use, line-too-long, protected-access, too-few-public-methods, unused-argument, too-many-statements, too-many-branches, too-many-locals
6+
import json
67
from knack.log import get_logger
78

89
from ..aaz.latest.vmss import (ListInstances as _VMSSListInstances,
910
Start as _Start,
1011
Create as _VMSSCreate,
11-
Show as _VMSSShow)
12+
Show as _VMSSShow,
13+
Patch as _VMSSPatch)
1214
from azure.cli.core.aaz import AAZUndefined, has_value
15+
from .._vm_utils import IdentityType
1316

1417
logger = get_logger(__name__)
1518

@@ -66,6 +69,70 @@ def _output(self, *args, **kwargs):
6669
return result
6770

6871

72+
class VMSSPatch(_VMSSPatch):
73+
74+
def _output(self, *args, **kwargs):
75+
# Resolve flatten conflict
76+
# When the type field conflicts, the type in inner layer is ignored and the outer layer is applied
77+
if has_value(self.ctx.vars.instance.properties.virtual_machine_profile.extension_profile.extensions):
78+
for extension in self.ctx.vars.instance.properties.virtual_machine_profile.extension_profile.extensions:
79+
if has_value(extension.type):
80+
extension.type = AAZUndefined
81+
82+
result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True)
83+
return result
84+
85+
86+
class VMSSIdentityRemove(_VMSSPatch):
87+
def _output(self, *args, **kwargs):
88+
# Resolve flatten conflict
89+
# When the type field conflicts, the type in inner layer is ignored and the outer layer is applied
90+
if has_value(self.ctx.vars.instance.properties.virtual_machine_profile.extension_profile.extensions):
91+
for extension in self.ctx.vars.instance.properties.virtual_machine_profile.extension_profile.extensions:
92+
if has_value(extension.type):
93+
extension.type = AAZUndefined
94+
95+
return self.deserialize_output(self.ctx.vars.instance, client_flatten=True)
96+
97+
class VirtualMachineScaleSetsUpdate(_VMSSPatch.VirtualMachineScaleSetsUpdate):
98+
def _format_content(self, content):
99+
if isinstance(content, str):
100+
content = json.loads(content)
101+
102+
if not content.get('identity'):
103+
content['identity'] = {
104+
'userAssignedIdentities': None,
105+
'type': IdentityType.NONE.value
106+
}
107+
return json.dumps(content)
108+
109+
identities = content.get('identity', {}).get('userAssignedIdentities')
110+
if identities:
111+
if 'UserAssigned' in identities.keys():
112+
identities.pop('UserAssigned')
113+
114+
for key in list(identities.keys()):
115+
identities[key] = None
116+
117+
return json.dumps(content)
118+
119+
def __call__(self, *args, **kwargs):
120+
request = self.make_request()
121+
request.data = self._format_content(request.data)
122+
session = self.client.send_request(request=request, stream=False, **kwargs)
123+
if session.http_response.status_code in [200, 202]:
124+
return self.client.build_lro_polling(
125+
self.ctx.args.no_wait,
126+
session,
127+
self.on_200,
128+
self.on_error,
129+
lro_options={"final-state-via": "azure-async-operation"},
130+
path_format_arguments=self.url_parameters,
131+
)
132+
133+
return self.on_error(session.http_response)
134+
135+
69136
def convert_show_result_to_snake_case(result):
70137
new_result = {}
71138
if "extendedLocation" in result:

0 commit comments

Comments
 (0)