From 0ba19afe065e47293781e998eff2d505e1644ba9 Mon Sep 17 00:00:00 2001 From: william Date: Wed, 14 Oct 2026 14:32:57 +0800 Subject: [PATCH 1/7] Export latest aaz 2026-04-01 for vm operations --- .../vm/aaz/latest/vm/_create.py | 26 +++++++++++++++++++ .../command_modules/vm/aaz/latest/vm/_list.py | 9 +++++++ .../vm/aaz/latest/vm/_list_all.py | 9 +++++++ .../command_modules/vm/aaz/latest/vm/_show.py | 9 +++++++ .../vm/aaz/latest/vm/_update.py | 26 +++++++++++++++++++ .../command_modules/vm/aaz/latest/vm/_wait.py | 9 +++++++ 6 files changed, 88 insertions(+) diff --git a/src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_create.py b/src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_create.py index 8281cf51aba..7d9e263bbae 100644 --- a/src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_create.py +++ b/src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_create.py @@ -213,6 +213,11 @@ def _build_arguments_schema(cls, *args, **kwargs): arg_group="Properties", help="Specifies the time alloted for all extensions to start. The time duration should be between 15 minutes and 120 minutes (inclusive) and should be specified in ISO 8601 format. The default value is 90 minutes (PT1H30M). Minimum api-version: 2020-06-01.", ) + _args_schema.flex_profile = AAZObjectArg( + options=["--flex-profile"], + arg_group="Properties", + help="[Preview Feature] Specifies a FlexProfile for VM size. Azure may deploy a compatible alternative VM size based on capacity and placement considerations, and returns the deployed size in hardwareProfile.vmSize. A request that adds flexProfile must omit hardwareProfile.vmSize. For a virtual machine that uses FlexProfile, a hardwareProfile.vmSize equal to the deployed size is ignored, so a GET response can be sent back unchanged. A PATCH request that omits flexProfile keeps it. A PUT request that omits flexProfile and sets hardwareProfile.vmSize clears it. This property cannot exist along with a non-null properties.availabilitySet, properties.capacityReservation, properties.host, properties.hostGroup, properties.proximityPlacementGroup or properties.virtualMachineScaleSet.", + ) _args_schema.hardware_profile = AAZObjectArg( options=["--hardware-profile"], arg_group="Properties", @@ -387,6 +392,13 @@ def _build_arguments_schema(cls, *args, **kwargs): help="Uri of the storage account to use for placing the console output and screenshot. If storageUri is not specified while enabling boot diagnostics, managed storage will be used.", ) + flex_profile = cls._args_schema.flex_profile + flex_profile.preferred_vm_size = AAZStrArg( + options=["preferred-vm-size"], + help="Specifies the preferred VM size, for example Standard_D4s_v5. Azure may deploy a compatible alternative size based on capacity and placement considerations. Supported VM families for FlexProfile may vary by region. The recommended way to get the list of available sizes is using these APIs: [List all available virtual machine sizes in a region](https://docs.microsoft.com/rest/api/compute/resourceskus/list), [List all available virtual machine sizes for resizing](https://docs.microsoft.com/rest/api/compute/virtualmachines/listavailablesizes).", + required=True, + ) + hardware_profile = cls._args_schema.hardware_profile hardware_profile.processor_mode = AAZStrArg( options=["processor-mode"], @@ -1743,6 +1755,7 @@ def content(self): properties.set_prop("diagnosticsProfile", AAZObjectType, ".diagnostics_profile") properties.set_prop("evictionPolicy", AAZStrType, ".eviction_policy") properties.set_prop("extensionsTimeBudget", AAZStrType, ".extensions_time_budget") + properties.set_prop("flexProfile", AAZObjectType, ".flex_profile") properties.set_prop("hardwareProfile", AAZObjectType, ".hardware_profile") _CreateHelper._build_schema_sub_resource_create(properties.set_prop("host", AAZObjectType, ".host")) _CreateHelper._build_schema_sub_resource_create(properties.set_prop("hostGroup", AAZObjectType, ".host_group")) @@ -1802,6 +1815,10 @@ def content(self): boot_diagnostics.set_prop("enabled", AAZBoolType, ".enabled") boot_diagnostics.set_prop("storageUri", AAZStrType, ".storage_uri") + flex_profile = _builder.get(".properties.flexProfile") + if flex_profile is not None: + flex_profile.set_prop("preferredVMSize", AAZStrType, ".preferred_vm_size", typ_kwargs={"flags": {"required": True}}) + hardware_profile = _builder.get(".properties.hardwareProfile") if hardware_profile is not None: hardware_profile.set_prop("processorMode", AAZStrType, ".processor_mode") @@ -2373,6 +2390,9 @@ def _build_schema_on_200_201(cls): properties.extensions_time_budget = AAZStrType( serialized_name="extensionsTimeBudget", ) + properties.flex_profile = AAZObjectType( + serialized_name="flexProfile", + ) properties.hardware_profile = AAZObjectType( serialized_name="hardwareProfile", ) @@ -2502,6 +2522,12 @@ def _build_schema_on_200_201(cls): serialized_name="storageUri", ) + flex_profile = cls._schema_on_200_201.properties.flex_profile + flex_profile.preferred_vm_size = AAZStrType( + serialized_name="preferredVMSize", + flags={"required": True}, + ) + hardware_profile = cls._schema_on_200_201.properties.hardware_profile hardware_profile.processor_mode = AAZStrType( serialized_name="processorMode", diff --git a/src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_list.py b/src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_list.py index 5a43da8c8c6..0cf9490dcb6 100644 --- a/src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_list.py +++ b/src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_list.py @@ -286,6 +286,9 @@ def _build_schema_on_200(cls): properties.extensions_time_budget = AAZStrType( serialized_name="extensionsTimeBudget", ) + properties.flex_profile = AAZObjectType( + serialized_name="flexProfile", + ) properties.hardware_profile = AAZObjectType( serialized_name="hardwareProfile", ) @@ -415,6 +418,12 @@ def _build_schema_on_200(cls): serialized_name="storageUri", ) + flex_profile = cls._schema_on_200.value.Element.properties.flex_profile + flex_profile.preferred_vm_size = AAZStrType( + serialized_name="preferredVMSize", + flags={"required": True}, + ) + hardware_profile = cls._schema_on_200.value.Element.properties.hardware_profile hardware_profile.processor_mode = AAZStrType( serialized_name="processorMode", diff --git a/src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_list_all.py b/src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_list_all.py index 8b13df3d351..6022892c07e 100644 --- a/src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_list_all.py +++ b/src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_list_all.py @@ -286,6 +286,9 @@ def _build_schema_on_200(cls): properties.extensions_time_budget = AAZStrType( serialized_name="extensionsTimeBudget", ) + properties.flex_profile = AAZObjectType( + serialized_name="flexProfile", + ) properties.hardware_profile = AAZObjectType( serialized_name="hardwareProfile", ) @@ -415,6 +418,12 @@ def _build_schema_on_200(cls): serialized_name="storageUri", ) + flex_profile = cls._schema_on_200.value.Element.properties.flex_profile + flex_profile.preferred_vm_size = AAZStrType( + serialized_name="preferredVMSize", + flags={"required": True}, + ) + hardware_profile = cls._schema_on_200.value.Element.properties.hardware_profile hardware_profile.processor_mode = AAZStrType( serialized_name="processorMode", diff --git a/src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_show.py b/src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_show.py index 4cdf4196be0..82c29e2df97 100644 --- a/src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_show.py +++ b/src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_show.py @@ -276,6 +276,9 @@ def _build_schema_on_200(cls): properties.extensions_time_budget = AAZStrType( serialized_name="extensionsTimeBudget", ) + properties.flex_profile = AAZObjectType( + serialized_name="flexProfile", + ) properties.hardware_profile = AAZObjectType( serialized_name="hardwareProfile", ) @@ -405,6 +408,12 @@ def _build_schema_on_200(cls): serialized_name="storageUri", ) + flex_profile = cls._schema_on_200.properties.flex_profile + flex_profile.preferred_vm_size = AAZStrType( + serialized_name="preferredVMSize", + flags={"required": True}, + ) + hardware_profile = cls._schema_on_200.properties.hardware_profile hardware_profile.processor_mode = AAZStrType( serialized_name="processorMode", diff --git a/src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_update.py b/src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_update.py index 9ca395eb02d..f62f0cb3ad3 100644 --- a/src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_update.py +++ b/src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_update.py @@ -222,6 +222,12 @@ def _build_arguments_schema(cls, *args, **kwargs): help="Specifies the time alloted for all extensions to start. The time duration should be between 15 minutes and 120 minutes (inclusive) and should be specified in ISO 8601 format. The default value is 90 minutes (PT1H30M). Minimum api-version: 2020-06-01.", nullable=True, ) + _args_schema.flex_profile = AAZObjectArg( + options=["--flex-profile"], + arg_group="Properties", + help="[Preview Feature] Specifies a FlexProfile for VM size. Azure may deploy a compatible alternative VM size based on capacity and placement considerations, and returns the deployed size in hardwareProfile.vmSize. A request that adds flexProfile must omit hardwareProfile.vmSize. For a virtual machine that uses FlexProfile, a hardwareProfile.vmSize equal to the deployed size is ignored, so a GET response can be sent back unchanged. A PATCH request that omits flexProfile keeps it. A PUT request that omits flexProfile and sets hardwareProfile.vmSize clears it. This property cannot exist along with a non-null properties.availabilitySet, properties.capacityReservation, properties.host, properties.hostGroup, properties.proximityPlacementGroup or properties.virtualMachineScaleSet.", + nullable=True, + ) _args_schema.hardware_profile = AAZObjectArg( options=["--hardware-profile"], arg_group="Properties", @@ -424,6 +430,12 @@ def _build_arguments_schema(cls, *args, **kwargs): nullable=True, ) + flex_profile = cls._args_schema.flex_profile + flex_profile.preferred_vm_size = AAZStrArg( + options=["preferred-vm-size"], + help="Specifies the preferred VM size, for example Standard_D4s_v5. Azure may deploy a compatible alternative size based on capacity and placement considerations. Supported VM families for FlexProfile may vary by region. The recommended way to get the list of available sizes is using these APIs: [List all available virtual machine sizes in a region](https://docs.microsoft.com/rest/api/compute/resourceskus/list), [List all available virtual machine sizes for resizing](https://docs.microsoft.com/rest/api/compute/virtualmachines/listavailablesizes).", + ) + hardware_profile = cls._args_schema.hardware_profile hardware_profile.processor_mode = AAZStrArg( options=["processor-mode"], @@ -2118,6 +2130,7 @@ def _update_instance(self, instance): properties.set_prop("diagnosticsProfile", AAZObjectType, ".diagnostics_profile") properties.set_prop("evictionPolicy", AAZStrType, ".eviction_policy") properties.set_prop("extensionsTimeBudget", AAZStrType, ".extensions_time_budget") + properties.set_prop("flexProfile", AAZObjectType, ".flex_profile") properties.set_prop("hardwareProfile", AAZObjectType, ".hardware_profile") _UpdateHelper._build_schema_sub_resource_update(properties.set_prop("host", AAZObjectType, ".host")) _UpdateHelper._build_schema_sub_resource_update(properties.set_prop("hostGroup", AAZObjectType, ".host_group")) @@ -2177,6 +2190,10 @@ def _update_instance(self, instance): boot_diagnostics.set_prop("enabled", AAZBoolType, ".enabled") boot_diagnostics.set_prop("storageUri", AAZStrType, ".storage_uri") + flex_profile = _builder.get(".properties.flexProfile") + if flex_profile is not None: + flex_profile.set_prop("preferredVMSize", AAZStrType, ".preferred_vm_size", typ_kwargs={"flags": {"required": True}}) + hardware_profile = _builder.get(".properties.hardwareProfile") if hardware_profile is not None: hardware_profile.set_prop("processorMode", AAZStrType, ".processor_mode") @@ -3222,6 +3239,9 @@ def _build_schema_virtual_machine_read(cls, _schema): properties.extensions_time_budget = AAZStrType( serialized_name="extensionsTimeBudget", ) + properties.flex_profile = AAZObjectType( + serialized_name="flexProfile", + ) properties.hardware_profile = AAZObjectType( serialized_name="hardwareProfile", ) @@ -3351,6 +3371,12 @@ def _build_schema_virtual_machine_read(cls, _schema): serialized_name="storageUri", ) + flex_profile = _schema_virtual_machine_read.properties.flex_profile + flex_profile.preferred_vm_size = AAZStrType( + serialized_name="preferredVMSize", + flags={"required": True}, + ) + hardware_profile = _schema_virtual_machine_read.properties.hardware_profile hardware_profile.processor_mode = AAZStrType( serialized_name="processorMode", diff --git a/src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_wait.py b/src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_wait.py index f5edb383229..bfe50e38fe3 100644 --- a/src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_wait.py +++ b/src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_wait.py @@ -278,6 +278,9 @@ def _build_schema_on_200(cls): properties.extensions_time_budget = AAZStrType( serialized_name="extensionsTimeBudget", ) + properties.flex_profile = AAZObjectType( + serialized_name="flexProfile", + ) properties.hardware_profile = AAZObjectType( serialized_name="hardwareProfile", ) @@ -407,6 +410,12 @@ def _build_schema_on_200(cls): serialized_name="storageUri", ) + flex_profile = cls._schema_on_200.properties.flex_profile + flex_profile.preferred_vm_size = AAZStrType( + serialized_name="preferredVMSize", + flags={"required": True}, + ) + hardware_profile = cls._schema_on_200.properties.hardware_profile hardware_profile.processor_mode = AAZStrType( serialized_name="processorMode", From 9086035c66effd74cbec746eac6cda3039d575f5 Mon Sep 17 00:00:00 2001 From: william Date: Wed, 14 Oct 2026 14:36:18 +0800 Subject: [PATCH 2/7] Add conversion for new param --- .../azure/cli/command_modules/vm/operations/vm.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/azure-cli/azure/cli/command_modules/vm/operations/vm.py b/src/azure-cli/azure/cli/command_modules/vm/operations/vm.py index f83f66ae9d3..ab0cdc45cbf 100644 --- a/src/azure-cli/azure/cli/command_modules/vm/operations/vm.py +++ b/src/azure-cli/azure/cli/command_modules/vm/operations/vm.py @@ -331,6 +331,8 @@ def convert_show_result_to_snake_case(result): new_result["eviction_policy"] = result["evictionPolicy"] if "extensionsTimeBudget" in result: new_result["extensions_time_budget"] = result["extensionsTimeBudget"] + if "flexProfile" in result: + new_result["flex_profile"] = result["flexProfile"] if "hardwareProfile" in result: new_result["hardware_profile"] = result["hardwareProfile"] if "host" in result: @@ -417,6 +419,11 @@ def convert_show_result_to_snake_case(result): boot_diagnostics["storage_uri"] = boot_diagnostics["storageUri"] boot_diagnostics.pop("storageUri") + flex_profile = new_result.get("flex_profile", {}) or {} + if "preferredVMSize" in flex_profile: + flex_profile["preferred_vm_size"] = flex_profile["preferredVMSize"] + flex_profile.pop("preferredVMSize") + hardware_profile = new_result.get("hardware_profile", {}) or {} if "processorMode" in hardware_profile: hardware_profile["processor_mode"] = hardware_profile["processorMode"] From 132a5c3cfcdc99164383f562083aa12d13866b76 Mon Sep 17 00:00:00 2001 From: william Date: Thu, 15 Oct 2026 23:17:09 +0800 Subject: [PATCH 3/7] Update code --- .../azure/cli/command_modules/vm/_help.py | 8 ++++ .../azure/cli/command_modules/vm/_params.py | 1 + .../command_modules/vm/_template_builder.py | 11 +++++- .../cli/command_modules/vm/_validators.py | 25 ++++++++++-- .../azure/cli/command_modules/vm/custom.py | 38 +++++++++++++------ 5 files changed, 67 insertions(+), 16 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/vm/_help.py b/src/azure-cli/azure/cli/command_modules/vm/_help.py index 1eef085e152..67a9f8b4c20 100644 --- a/src/azure-cli/azure/cli/command_modules/vm/_help.py +++ b/src/azure-cli/azure/cli/command_modules/vm/_help.py @@ -1084,6 +1084,10 @@ - name: Create a default Ubuntu2204 VM with automatic SSH authentication. text: > az vm create -n MyVm -g MyResourceGroup --image Ubuntu2204 + - name: Create a Flow VM with a preferred size in a supported region. + text: > + az vm create -n MyVm -g MyResourceGroup --location eastus2 --image Ubuntu2204 + --preferred-size Standard_D2s_v3 --generate-ssh-keys --nsg-rule NONE --public-ip-address "" - name: Create a default RedHat VM with automatic SSH authentication using an image URN. text: > az vm create -n MyVm -g MyResourceGroup --image RedHat:RHEL:7-RAW:7.4.2018010506 @@ -2140,6 +2144,10 @@ text: az vm update -n name -g group --set tags.tagName=tagValue - name: Remove a tag. text: az vm update -n name -g group --remove tags.tagName + - name: Opt in to Flow VMs or change the preferred size. + text: az vm update -n MyVm -g MyResourceGroup --preferred-size Standard_D2s_v3 + - name: Opt out of Flow VMs and pin a fixed size. + text: az vm update -n MyVm -g MyResourceGroup --size Standard_D2s_v3 --remove flexProfile - name: Set the primary NIC of a VM. text: az vm update -n name -g group --set networkProfile.networkInterfaces[1].primary=false networkProfile.networkInterfaces[0].primary=true - name: Add a new non-primary NIC to a VM. diff --git a/src/azure-cli/azure/cli/command_modules/vm/_params.py b/src/azure-cli/azure/cli/command_modules/vm/_params.py index 15d73962cd6..e86d65d9d6c 100644 --- a/src/azure-cli/azure/cli/command_modules/vm/_params.py +++ b/src/azure-cli/azure/cli/command_modules/vm/_params.py @@ -496,6 +496,7 @@ def load_arguments(self, _): for scope in ['vm create', 'vm update']: with self.argument_context(scope) as c: + c.argument('preferred_size', completer=get_vm_size_completion_list, is_preview=True, help='Preferred VM size for Flow VMs. Azure may deploy a compatible alternative size. Cannot be used with --size.') c.argument('additional_scheduled_events', options_list=['--additional-scheduled-events', '--additional-events'], arg_type=get_three_state_flag(), help='The configuration parameter used while creating event grid and resource graph scheduled event setting.') c.argument('enable_user_reboot_scheduled_events', options_list=['--enable-user-reboot-scheduled-events', '--enable-reboot'], arg_type=get_three_state_flag(), help='The configuration parameter used while publishing scheduled events additional publishing targets.') c.argument('enable_user_redeploy_scheduled_events', options_list=['--enable-user-redeploy-scheduled-events', '--enable-redeploy'], arg_type=get_three_state_flag(), help='The configuration parameter used while creating user initiated redeploy scheduled event setting creation.') diff --git a/src/azure-cli/azure/cli/command_modules/vm/_template_builder.py b/src/azure-cli/azure/cli/command_modules/vm/_template_builder.py index c1a743a61c1..ed72c3a112b 100644 --- a/src/azure-cli/azure/cli/command_modules/vm/_template_builder.py +++ b/src/azure-cli/azure/cli/command_modules/vm/_template_builder.py @@ -325,7 +325,8 @@ def build_vm_resource( # pylint: disable=too-many-locals, too-many-statements, wire_server_access_control_profile_reference_id=None, imds_access_control_profile_reference_id=None, key_incarnation_id=None, add_proxy_agent_extension=None, disk_iops_read_write=None, disk_mbps_read_write=None, zone_movement=None, - os_disk_storage_fault_domain_alignment=None, data_disk_storage_fault_domain_alignment=None): + os_disk_storage_fault_domain_alignment=None, data_disk_storage_fault_domain_alignment=None, + preferred_size=None): os_caching = disk_info['os'].get('caching') @@ -602,8 +603,12 @@ def _build_storage_profile(): return profile - vm_properties = {'hardwareProfile': {'vmSize': size}, 'networkProfile': {'networkInterfaces': nics}, + vm_properties = {'hardwareProfile': {}, 'networkProfile': {'networkInterfaces': nics}, 'storageProfile': _build_storage_profile()} + if preferred_size is None: + vm_properties['hardwareProfile']['vmSize'] = size + else: + vm_properties['flexProfile'] = {'preferredVMSize': preferred_size} if processor_mode is not None: vm_properties['hardwareProfile']['processorMode'] = processor_mode @@ -659,6 +664,8 @@ def _build_storage_profile(): if vm_size_properties: vm_properties['hardwareProfile']['vmSizeProperties'] = vm_size_properties + if not vm_properties['hardwareProfile']: + del vm_properties['hardwareProfile'] if availability_set_id: vm_properties['availabilitySet'] = {'id': availability_set_id} diff --git a/src/azure-cli/azure/cli/command_modules/vm/_validators.py b/src/azure-cli/azure/cli/command_modules/vm/_validators.py index 41473497536..5e2c0f7e1e4 100644 --- a/src/azure-cli/azure/cli/command_modules/vm/_validators.py +++ b/src/azure-cli/azure/cli/command_modules/vm/_validators.py @@ -677,7 +677,8 @@ def _validate_vm_create_storage_profile(cmd, namespace, for_scale_set=False): from ._vm_utils import normalize_disk_info # attach_data_disks are not exposed yet for VMSS, so use 'getattr' to avoid crash - vm_size = (getattr(namespace, 'size', None) or getattr(namespace, 'vm_sku', None)) + vm_size = (getattr(namespace, 'preferred_size', None) or + getattr(namespace, 'size', None) or getattr(namespace, 'vm_sku', None)) # pylint: disable=line-too-long namespace.disk_info = normalize_disk_info(size=vm_size, @@ -953,7 +954,8 @@ def _subnet_capacity_check(subnet_mask, vmss_instance_count, over_provision): def _validate_vm_vmss_accelerated_networking(cli_ctx, namespace): if namespace.accelerated_networking is None: - size = getattr(namespace, 'size', None) or getattr(namespace, 'vm_sku', None) + size = (getattr(namespace, 'preferred_size', None) or + getattr(namespace, 'size', None) or getattr(namespace, 'vm_sku', None)) size = size.lower() # Use the following code to refresh the list @@ -1654,9 +1656,25 @@ def _resolve_role_id(cli_ctx, role, scope): return role_id +def _validate_vm_size_options(namespace): + from azure.cli.core.azclierror import InvalidArgumentValueError + + preferred_size = getattr(namespace, 'preferred_size', None) + size = getattr(namespace, 'size', None) + + if size is not None and preferred_size is not None: + raise MutuallyExclusiveArgumentError("Specify either --size or --preferred-size, not both.") + if preferred_size is not None and not preferred_size.strip(): + raise InvalidArgumentValueError("--preferred-size must not be empty or whitespace.") + + def process_vm_create_namespace(cmd, namespace): + _validate_vm_size_options(namespace) + preferred_size = getattr(namespace, 'preferred_size', None) + if namespace.size is None and preferred_size is None: + namespace.size = 'Standard_D2s_v5' validate_tags(namespace) - _validate_location(cmd, namespace, namespace.zone, namespace.size) + _validate_location(cmd, namespace, namespace.zone, preferred_size or namespace.size) # Currently, only `az vm create` supports this feature, so it is temporarily placed in process_vm_create_namespace() from ._vm_utils import display_region_recommendation @@ -1700,6 +1718,7 @@ def process_vm_create_namespace(cmd, namespace): def process_vm_update_namespace(cmd, namespace): + _validate_vm_size_options(namespace) _validate_vm_create_dedicated_host(cmd, namespace) _validate_capacity_reservation_group(cmd, namespace) _validate_vm_vmss_update_ephemeral_placement(cmd, namespace) diff --git a/src/azure-cli/azure/cli/command_modules/vm/custom.py b/src/azure-cli/azure/cli/command_modules/vm/custom.py index a4fdc0f3e28..880b491be45 100644 --- a/src/azure-cli/azure/cli/command_modules/vm/custom.py +++ b/src/azure-cli/azure/cli/command_modules/vm/custom.py @@ -889,7 +889,7 @@ def setter(vm, external_identities=external_identities): # region VirtualMachines # pylint: disable=too-many-locals, unused-argument, too-many-statements, too-many-branches, broad-except -def create_vm(cmd, vm_name, resource_group_name, image=None, size='Standard_D2s_v5', location=None, tags=None, +def create_vm(cmd, vm_name, resource_group_name, image=None, size=None, location=None, tags=None, no_wait=False, authentication_type=None, admin_password=None, computer_name=None, admin_username=None, ssh_dest_key_path=None, ssh_key_value=None, generate_ssh_keys=False, availability_set=None, nics=None, nsg=None, nsg_rule=None, accelerated_networking=None, @@ -929,7 +929,8 @@ def create_vm(cmd, vm_name, resource_group_name, image=None, size='Standard_D2s_ wire_server_access_control_profile_reference_id=None, imds_access_control_profile_reference_id=None, key_incarnation_id=None, add_proxy_agent_extension=None, disk_iops_read_write=None, disk_mbps_read_write=None, zone_movement=None, - os_disk_storage_fault_domain_alignment=None, data_disk_storage_fault_domain_alignment=None): + os_disk_storage_fault_domain_alignment=None, data_disk_storage_fault_domain_alignment=None, + preferred_size=None): from azure.cli.core.util import random_string, hash_string from azure.cli.core.commands.arm import ArmTemplateBuilder @@ -1165,7 +1166,8 @@ def create_vm(cmd, vm_name, resource_group_name, image=None, size='Standard_D2s_ key_incarnation_id=key_incarnation_id, add_proxy_agent_extension=add_proxy_agent_extension, disk_iops_read_write=disk_iops_read_write, disk_mbps_read_write=disk_mbps_read_write, zone_movement=zone_movement, os_disk_storage_fault_domain_alignment=os_disk_storage_fault_domain_alignment, - data_disk_storage_fault_domain_alignment=data_disk_storage_fault_domain_alignment) + data_disk_storage_fault_domain_alignment=data_disk_storage_fault_domain_alignment, + preferred_size=preferred_size) vm_resource['dependsOn'] = vm_dependencies @@ -1770,11 +1772,12 @@ def update_vm(cmd, resource_group_name, vm_name, os_disk=None, disk_caching=None enable_all_instance_down=None, align_regional_disks_to_vm_zone=None, wire_server_mode=None, wire_server_use_local_file_rules=None, imds_mode=None, add_proxy_agent_extension=None, wire_server_access_control_profile_reference_id=None, imds_access_control_profile_reference_id=None, - key_incarnation_id=None, zone_movement=None, zone=None, **kwargs): + key_incarnation_id=None, zone_movement=None, zone=None, preferred_size=None, **kwargs): from azure.mgmt.core.tools import parse_resource_id, resource_id, is_valid_resource_id from ._vm_utils import update_write_accelerator_settings, update_disk_caching_by_aaz from .operations.vm import convert_show_result_to_snake_case as vm_convert_show_result_to_snake_case vm = kwargs['parameters'] + flex_profile_removed = 'flexProfile' not in vm vm = vm_convert_show_result_to_snake_case(vm) if wire_server_access_control_profile_reference_id is not None or \ @@ -1984,13 +1987,8 @@ def _output(self, *args, **kwargs): if vm.get("storage_profile", {}).get("os_disk", {}).get("os_type", None) is not None else None _set_data_source_for_workspace(cmd, os_type, resource_group_name, workspace_name) - if size is not None: - if vm.get("hardware_profile", {}).get("vm_size", None) == size: - logger.warning("VM size is already %s", size) - else: - if vm.get("hardware_profile", None) is None: - vm["hardware_profile"] = {} - vm["hardware_profile"]["vm_size"] = size + if size is not None and vm.get("hardware_profile", {}).get("vm_size", None) == size: + logger.warning("VM size is already %s", size) if v_cpus_available is not None: if vm.get("hardware_profile", None) is None: @@ -2081,6 +2079,24 @@ def _output(self, *args, **kwargs): vm["resiliency_profile"]["zone_movement"] = {} vm["resiliency_profile"]["zone_movement"]["is_enabled"] = zone_movement + if flex_profile_removed: + vm.pop("flex_profile", None) + + if size is not None: + if vm.get("hardware_profile") is None: + vm["hardware_profile"] = {} + vm["hardware_profile"]["vm_size"] = size + + if preferred_size is not None: + if vm.get("flex_profile") is None: + vm["flex_profile"] = {} + vm["flex_profile"]["preferred_vm_size"] = preferred_size + hardware_profile = vm.get("hardware_profile") + if hardware_profile is not None: + hardware_profile.pop("vm_size", None) + if not hardware_profile: + vm.pop("hardware_profile", None) + # Zone move orchestration: force deallocate → PUT with new zone → start zone_change = False if zone is not None: From 8b4899191c767c607b74fd25b0e55ba3cb3dc8b8 Mon Sep 17 00:00:00 2001 From: william Date: Thu, 15 Oct 2026 23:17:22 +0800 Subject: [PATCH 4/7] Add test case --- .../vm/tests/latest/test_vm_commands.py | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/src/azure-cli/azure/cli/command_modules/vm/tests/latest/test_vm_commands.py b/src/azure-cli/azure/cli/command_modules/vm/tests/latest/test_vm_commands.py index 2f6ee373bbb..bee1bfbfba3 100644 --- a/src/azure-cli/azure/cli/command_modules/vm/tests/latest/test_vm_commands.py +++ b/src/azure-cli/azure/cli/command_modules/vm/tests/latest/test_vm_commands.py @@ -2819,6 +2819,104 @@ def test_vm_create_ephemeral_os_disk_placement(self, resource_group, resource_gr ]) +class VMFlexProfileScenarioTests(ScenarioTest): + + @AllowLargeResponse() + @ResourceGroupPreparer(name_prefix='cli_test_vm_flex_profile_main_', location='westus2') + def test_vm_flex_profile_create_and_update(self, resource_group, resource_group_location): + self.kwargs.update({ + 'vm': self.create_random_name('flowvm', 15), + 'loc': resource_group_location, + 'image': 'Canonical:0001-com-ubuntu-server-jammy:22_04-lts-gen2:latest', + 'preferred_size': 'Standard_D2s_v3', + 'updated_preferred_size': 'Standard_D4s_v3', + 'ssh_key': TEST_SSH_KEY_PUB, + }) + + self.cmd( + 'vm create -g {rg} -n {vm} --location {loc} --image {image} ' + '--preferred-size {preferred_size} --admin-username clitest --ssh-key-values "{ssh_key}" ' + '--security-type Standard --nsg-rule NONE --public-ip-address ""') + vm = self.cmd('vm show -g {rg} -n {vm}', checks=[ + self.check('provisioningState', 'Succeeded'), + self.check('flexProfile.preferredVMSize', '{preferred_size}'), + ]).get_output_in_json() + self.assertTrue(vm['hardwareProfile']['vmSize']) + self.kwargs['deployed_size'] = vm['hardwareProfile']['vmSize'] + + self.cmd('vm update -g {rg} -n {vm} --set tags.flow=preserved') + self.cmd('vm show -g {rg} -n {vm}', checks=[ + self.check('flexProfile.preferredVMSize', '{preferred_size}'), + self.check('hardwareProfile.vmSize', '{deployed_size}'), + self.check('tags.flow', 'preserved'), + ]) + + self.cmd('vm update -g {rg} -n {vm} --preferred-size {updated_preferred_size}') + vm = self.cmd('vm show -g {rg} -n {vm}', checks=[ + self.check('provisioningState', 'Succeeded'), + self.check('flexProfile.preferredVMSize', '{updated_preferred_size}'), + self.check('tags.flow', 'preserved'), + ]).get_output_in_json() + self.assertTrue(vm['hardwareProfile']['vmSize']) + self.kwargs['deployed_size'] = vm['hardwareProfile']['vmSize'] + self.cmd('vm list -g {rg}', checks=[ + self.check("[?name=='{vm}'].flexProfile.preferredVMSize | [0]", '{updated_preferred_size}'), + self.check("[?name=='{vm}'].hardwareProfile.vmSize | [0]", '{deployed_size}'), + ]) + + @AllowLargeResponse() + @ResourceGroupPreparer(name_prefix='cli_test_vm_flex_profile_edge_', location='westus2') + def test_vm_flex_profile_opt_in_opt_out(self, resource_group, resource_group_location): + self.kwargs.update({ + 'vm': self.create_random_name('fixedvm', 15), + 'loc': resource_group_location, + 'image': 'Canonical:0001-com-ubuntu-server-jammy:22_04-lts-gen2:latest', + 'preferred_size': 'Standard_D2s_v3', + 'ssh_key': TEST_SSH_KEY_PUB, + }) + + self.cmd( + 'vm create -g {rg} -n {vm} --location {loc} --image {image} ' + '--size Standard_D2s_v3 --admin-username clitest --ssh-key-values "{ssh_key}" ' + '--security-type Standard --nsg-rule NONE --public-ip-address ""') + self.cmd('vm show -g {rg} -n {vm}', checks=[ + self.check('flexProfile', None), + self.check('hardwareProfile.vmSize', 'Standard_D2s_v3'), + ]) + + self.cmd('vm update -g {rg} -n {vm} --preferred-size {preferred_size}') + vm = self.cmd('vm show -g {rg} -n {vm}', checks=[ + self.check('flexProfile.preferredVMSize', '{preferred_size}'), + ]).get_output_in_json() + self.assertTrue(vm['hardwareProfile']['vmSize']) + deployed_size = vm['hardwareProfile']['vmSize'] + self.kwargs.update({ + 'deployed_size': deployed_size, + 'different_size': 'Standard_D4s_v3' if deployed_size != 'Standard_D4s_v3' else 'Standard_D2s_v3', + }) + + self.cmd('vm update -g {rg} -n {vm} --size {deployed_size}') + self.cmd('vm show -g {rg} -n {vm}', checks=[ + self.check('flexProfile.preferredVMSize', '{preferred_size}'), + self.check('hardwareProfile.vmSize', '{deployed_size}'), + ]) + for command in ( + 'vm update -g {rg} -n {vm} --size {different_size}', + 'vm resize -g {rg} -n {vm} --size {different_size}'): + with self.assertRaisesRegex(HttpResponseError, 'InvalidParameter'): + self.cmd(command) + self.cmd('vm show -g {rg} -n {vm}', checks=[ + self.check('flexProfile.preferredVMSize', '{preferred_size}'), + self.check('hardwareProfile.vmSize', '{deployed_size}'), + ]) + + self.cmd('vm update -g {rg} -n {vm} --size {deployed_size} --remove flexProfile') + self.cmd('vm show -g {rg} -n {vm}', checks=[ + self.check('flexProfile', None), + self.check('hardwareProfile.vmSize', '{deployed_size}'), + ]) + + class VMUpdateTests(ScenarioTest): @AllowLargeResponse() From 152b812d4155573cbdf294d2f42897d076d83b75 Mon Sep 17 00:00:00 2001 From: william Date: Fri, 16 Oct 2026 08:48:51 +0800 Subject: [PATCH 5/7] Update test cases --- .../cli/command_modules/vm/tests/latest/test_vm_commands.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/vm/tests/latest/test_vm_commands.py b/src/azure-cli/azure/cli/command_modules/vm/tests/latest/test_vm_commands.py index bee1bfbfba3..1a639a4c277 100644 --- a/src/azure-cli/azure/cli/command_modules/vm/tests/latest/test_vm_commands.py +++ b/src/azure-cli/azure/cli/command_modules/vm/tests/latest/test_vm_commands.py @@ -2821,7 +2821,8 @@ def test_vm_create_ephemeral_os_disk_placement(self, resource_group, resource_gr class VMFlexProfileScenarioTests(ScenarioTest): - @AllowLargeResponse() + # add live_only: Currently only enabled in canary region where BAMI sub is not accessible + @live_only() @ResourceGroupPreparer(name_prefix='cli_test_vm_flex_profile_main_', location='westus2') def test_vm_flex_profile_create_and_update(self, resource_group, resource_group_location): self.kwargs.update({ @@ -2864,7 +2865,8 @@ def test_vm_flex_profile_create_and_update(self, resource_group, resource_group_ self.check("[?name=='{vm}'].hardwareProfile.vmSize | [0]", '{deployed_size}'), ]) - @AllowLargeResponse() + # add live_only: Currently only enabled in canary region where BAMI sub is not accessible + @live_only() @ResourceGroupPreparer(name_prefix='cli_test_vm_flex_profile_edge_', location='westus2') def test_vm_flex_profile_opt_in_opt_out(self, resource_group, resource_group_location): self.kwargs.update({ From e18003c0fe18b0f66e97089c90f03b3288fd0c84 Mon Sep 17 00:00:00 2001 From: william Date: Fri, 16 Oct 2026 08:59:33 +0800 Subject: [PATCH 6/7] Update code --- src/azure-cli/azure/cli/command_modules/vm/custom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure-cli/azure/cli/command_modules/vm/custom.py b/src/azure-cli/azure/cli/command_modules/vm/custom.py index 880b491be45..2a81d2feb13 100644 --- a/src/azure-cli/azure/cli/command_modules/vm/custom.py +++ b/src/azure-cli/azure/cli/command_modules/vm/custom.py @@ -889,7 +889,7 @@ def setter(vm, external_identities=external_identities): # region VirtualMachines # pylint: disable=too-many-locals, unused-argument, too-many-statements, too-many-branches, broad-except -def create_vm(cmd, vm_name, resource_group_name, image=None, size=None, location=None, tags=None, +def create_vm(cmd, vm_name, resource_group_name, image=None, size='Standard_D2s_v5', location=None, tags=None, no_wait=False, authentication_type=None, admin_password=None, computer_name=None, admin_username=None, ssh_dest_key_path=None, ssh_key_value=None, generate_ssh_keys=False, availability_set=None, nics=None, nsg=None, nsg_rule=None, accelerated_networking=None, From 4f36410758b2aad24be14cb6bb9d76f9bf779c94 Mon Sep 17 00:00:00 2001 From: william Date: Fri, 16 Oct 2026 09:16:24 +0800 Subject: [PATCH 7/7] Update code --- src/azure-cli/azure/cli/command_modules/vm/custom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure-cli/azure/cli/command_modules/vm/custom.py b/src/azure-cli/azure/cli/command_modules/vm/custom.py index 2a81d2feb13..880b491be45 100644 --- a/src/azure-cli/azure/cli/command_modules/vm/custom.py +++ b/src/azure-cli/azure/cli/command_modules/vm/custom.py @@ -889,7 +889,7 @@ def setter(vm, external_identities=external_identities): # region VirtualMachines # pylint: disable=too-many-locals, unused-argument, too-many-statements, too-many-branches, broad-except -def create_vm(cmd, vm_name, resource_group_name, image=None, size='Standard_D2s_v5', location=None, tags=None, +def create_vm(cmd, vm_name, resource_group_name, image=None, size=None, location=None, tags=None, no_wait=False, authentication_type=None, admin_password=None, computer_name=None, admin_username=None, ssh_dest_key_path=None, ssh_key_value=None, generate_ssh_keys=False, availability_set=None, nics=None, nsg=None, nsg_rule=None, accelerated_networking=None,