Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 0 additions & 10 deletions src/azure-cli/azure/cli/command_modules/vm/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -2317,16 +2317,6 @@
--security-posture-reference-exclude-extensions "c:\\tmp\\exclude_extensions.json"
"""

helps['vmss deallocate'] = """
type: command
short-summary: Deallocate VMs within a VMSS.
examples:
- name: Deallocate VMs within a VMSS. (autogenerated)
text: |
az vmss deallocate --instance-ids 1 --name MyScaleSet --resource-group MyResourceGroup
crafted: true
"""

helps['vmss delete-instances'] = """
type: command
short-summary: Delete VMs within a VMSS.
Expand Down
5 changes: 1 addition & 4 deletions src/azure-cli/azure/cli/command_modules/vm/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -705,14 +705,11 @@ def load_arguments(self, _):
c.argument('host_group', min_api='2020-06-01',
help='Name or ID of dedicated host group that the virtual machine scale set resides in')

for scope in ['vmss deallocate', 'vmss delete-instances', 'vmss restart', 'vmss start', 'vmss stop', 'vmss show', 'vmss update-instances', 'vmss simulate-eviction']:
for scope in ['vmss delete-instances', 'vmss restart', 'vmss start', 'vmss stop', 'vmss show', 'vmss update-instances', 'vmss simulate-eviction']:
with self.argument_context(scope) as c:
for dest in scaleset_name_aliases:
c.argument(dest, vmss_name_type, id_part=None) # due to instance-ids parameter

with self.argument_context('vmss deallocate', operation_group='virtual_machine_scale_sets') as c:
c.argument('hibernate', arg_type=get_three_state_flag(), help='Hibernate a virtual machine from the VM scale set. Available for VMSS with Flexible OrchestrationMode only.', min_api='2023-03-01')

with self.argument_context('vmss reimage') as c:
c.argument('instance_ids', nargs='+',
help='Space-separated list of VM instance ID. If missing, reimage all instances.',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
# flake8: noqa

from .__cmd_group import *
from ._deallocate import *
from ._delete import *
from ._get_os_upgrade_history import *
from ._list import *
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
#
# Code generated by aaz-dev-tools
# --------------------------------------------------------------------------------------------

# pylint: skip-file
# flake8: noqa

from azure.cli.core.aaz import *


@register_command(
"vmss deallocate",
)
class Deallocate(AAZCommand):
"""Deallocate VMs within a VMSS.

:example: Deallocate VMs within a VMSS.
az vmss deallocate --instance-ids 1 --name MyScaleSet --resource-group MyResourceGroup
"""

_aaz_info = {
"version": "2024-11-01",
"resources": [
["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.compute/virtualmachinescalesets/{}/deallocate", "2024-11-01"],
]
}

AZ_SUPPORT_NO_WAIT = True

def _handler(self, command_args):
super()._handler(command_args)
return self.build_lro_poller(self._execute_operations, None)

_args_schema = None

@classmethod
def _build_arguments_schema(cls, *args, **kwargs):
if cls._args_schema is not None:
return cls._args_schema
cls._args_schema = super()._build_arguments_schema(*args, **kwargs)

# define Arg Group ""

_args_schema = cls._args_schema
_args_schema.resource_group = AAZResourceGroupNameArg(
required=True,
)
_args_schema.name = AAZStrArg(
options=["-n", "--name"],
help="Scale set name. You can configure the default using `az configure --defaults vmss=<name>`",
required=True,
id_part="name",
)
_args_schema.hibernate = AAZBoolArg(
options=["--hibernate"],
help="Hibernate a virtual machine from the VM scale set. Available for VMSS with Flexible OrchestrationMode only. Allowed values:false, true",
)

# define Arg Group "VmInstanceIDs"

_args_schema = cls._args_schema
_args_schema.instance_ids = AAZListArg(
options=["--instance-ids"],
arg_group="VmInstanceIDs",
help="The virtual machine scale set instance ids. Omitting the virtual machine scale set instance ids will result in the operation being performed on all virtual machines in the virtual machine scale set.",
)

instance_ids = cls._args_schema.instance_ids
instance_ids.Element = AAZStrArg()
return cls._args_schema

def _execute_operations(self):
self.pre_operations()
yield self.VirtualMachineScaleSetsDeallocate(ctx=self.ctx)()
self.post_operations()

@register_callback
def pre_operations(self):
pass

@register_callback
def post_operations(self):
pass

class VirtualMachineScaleSetsDeallocate(AAZHttpOperation):
CLIENT_TYPE = "MgmtClient"

def __call__(self, *args, **kwargs):
request = self.make_request()
session = self.client.send_request(request=request, stream=False, **kwargs)
if session.http_response.status_code in [202]:
return self.client.build_lro_polling(
self.ctx.args.no_wait,
session,
self.on_200,
self.on_error,
lro_options={"final-state-via": "location"},
path_format_arguments=self.url_parameters,
)
if session.http_response.status_code in [200]:
return self.client.build_lro_polling(
self.ctx.args.no_wait,
session,
self.on_200,
self.on_error,
lro_options={"final-state-via": "location"},
path_format_arguments=self.url_parameters,
)

return self.on_error(session.http_response)

@property
def url(self):
return self.client.format_url(
"/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/virtualMachineScaleSets/{vmScaleSetName}/deallocate",
**self.url_parameters
)

@property
def method(self):
return "POST"

@property
def error_format(self):
return "ODataV4Format"

@property
def url_parameters(self):
parameters = {
**self.serialize_url_param(
"resourceGroupName", self.ctx.args.resource_group,
required=True,
),
**self.serialize_url_param(
"subscriptionId", self.ctx.subscription_id,
required=True,
),
**self.serialize_url_param(
"vmScaleSetName", self.ctx.args.name,
required=True,
),
}
return parameters

@property
def query_parameters(self):
parameters = {
**self.serialize_query_param(
"hibernate", self.ctx.args.hibernate,
),
**self.serialize_query_param(
"api-version", "2024-11-01",
required=True,
),
}
return parameters

@property
def header_parameters(self):
parameters = {
**self.serialize_header_param(
"Content-Type", "application/json",
),
}
return parameters

@property
def content(self):
_content_value, _builder = self.new_content_builder(
self.ctx.args,
typ=AAZObjectType,
typ_kwargs={"flags": {"client_flatten": True}}
)
_builder.set_prop("instanceIds", AAZListType, ".instance_ids")

instance_ids = _builder.get(".instanceIds")
if instance_ids is not None:
instance_ids.set_elements(AAZStrType, ".")

return self.serialize_content(_content_value)

def on_200(self, session):
pass


class _DeallocateHelper:
"""Helper class for Deallocate"""


__all__ = ["Deallocate"]
1 change: 0 additions & 1 deletion src/azure-cli/azure/cli/command_modules/vm/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,6 @@ def load_command_table(self, _):
g.custom_command('application set', 'set_vmss_applications', validator=process_set_applications_namespace, min_api='2021-07-01')
g.custom_command('application list', 'list_vmss_applications', min_api='2021-07-01')
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)
g.custom_command('deallocate', 'deallocate_vmss', supports_no_wait=True)
g.custom_command('delete-instances', 'delete_vmss_instances', supports_no_wait=True)
g.custom_command('get-instance-view', 'get_vmss_instance_view', table_transformer='{ProvisioningState:statuses[0].displayStatus, PowerState:statuses[1].displayStatus}')
g.custom_command('list-instance-connection-info', 'list_vmss_instance_connection_info')
Expand Down
18 changes: 0 additions & 18 deletions src/azure-cli/azure/cli/command_modules/vm/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -3802,24 +3802,6 @@ def _build_identities_info(identities):
return (info, identity_types, external_identities, 'SystemAssigned' in identity_types)


def deallocate_vmss(cmd, resource_group_name, vm_scale_set_name, instance_ids=None, no_wait=False, hibernate=None):
client = _compute_client_factory(cmd.cli_ctx)
# This is a walkaround because the REST service of `VirtualMachineScaleSetVMs#begin_deallocate`
# does not accept `hibernate` at present
if instance_ids and len(instance_ids) == 1 and hibernate is None:
return sdk_no_wait(no_wait, client.virtual_machine_scale_set_vms.begin_deallocate,
resource_group_name, vm_scale_set_name, instance_ids[0])

VirtualMachineScaleSetVMInstanceIDs = cmd.get_models('VirtualMachineScaleSetVMInstanceIDs')
vm_instance_i_ds = VirtualMachineScaleSetVMInstanceIDs(instance_ids=instance_ids)
if hibernate is not None:
return sdk_no_wait(no_wait, client.virtual_machine_scale_sets.begin_deallocate,
resource_group_name, vm_scale_set_name, vm_instance_i_ds, hibernate=hibernate)
else:
return sdk_no_wait(no_wait, client.virtual_machine_scale_sets.begin_deallocate,
resource_group_name, vm_scale_set_name, vm_instance_i_ds)


def delete_vmss_instances(cmd, resource_group_name, vm_scale_set_name, instance_ids, no_wait=False):
client = _compute_client_factory(cmd.cli_ctx)
VirtualMachineScaleSetVMInstanceRequiredIDs = cmd.get_models('VirtualMachineScaleSetVMInstanceRequiredIDs')
Expand Down
12 changes: 10 additions & 2 deletions src/azure-cli/azure/cli/command_modules/vm/operations/vmss.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------
# pylint: disable=no-self-use, line-too-long, protected-access, too-few-public-methods, unused-argument
from azure.cli.core.aaz import AAZUndefined, has_value
from knack.log import get_logger

from ..aaz.latest.vmss import ListInstances as _VMSSListInstances
from ..aaz.latest.vmss import Deallocate as _VMSSDeallocate

logger = get_logger(__name__)


class VMSSListInstances(_VMSSListInstances):
def _output(self, *args, **kwargs):
from azure.cli.core.aaz import AAZUndefined, has_value

# Resolve flatten conflict
# When the type field conflicts, the type in inner layer is ignored and the outer layer is applied
Expand All @@ -25,3 +25,11 @@ def _output(self, *args, **kwargs):
result = self.deserialize_output(self.ctx.vars.instance.value, client_flatten=True)
next_link = self.deserialize_output(self.ctx.vars.instance.next_link)
return result, next_link


class VMSSDeallocate(_VMSSDeallocate):
def pre_operations(self):
args = self.ctx.args

if has_value(args.instance_ids) and len(args.instance_ids) == 1 and not has_value(args.hibernate):
self.ctx.args.instance_ids = args.instance_ids[0]
Original file line number Diff line number Diff line change
Expand Up @@ -5587,17 +5587,49 @@ def _check_vms_power_state(self, *args):
self.assertTrue(result['statuses'][1]['code'] in args)

@ResourceGroupPreparer(name_prefix='cli_test_vmss_vms')
@AllowLargeResponse(size_kb=99999)
def test_vmss_vms(self, resource_group):

self.kwargs.update({
'vmss': self.create_random_name('clitestvmss', 20),
'flex_vmss': self.create_random_name('clitestflexvms', 20),
'count': 2,
'instance_ids': []
'instance_ids': [],
'nsg': 'nsg1',
'pubip1': 'pubip1',

})

self.cmd('network nsg create -g {rg} -n {nsg}')
# Create a public IP resource with service tag

# self.cmd("feature register --namespace Microsoft.Network --name AllowBringYourOwnPublicIpAddress")
# self.cmd("provider register -n Microsoft.Network")

self.cmd('network public-ip create --name {pubip1} -g {rg} --ip-tags FirstPartyUsage=/NonProd')

self.cmd('vmss create -g {rg} -n {vmss} --image Canonical:ubuntu-24_04-lts:server:latest --authentication-type password --lb-sku Standard '
'--admin-username admin123 --admin-password TestTest12#$ --instance-count {count} --orchestration-mode Uniform --public-ip-address {pubip1} --nsg {nsg}')




# # Create NSG
# self.cmd('network nsg create -g {rg} -n {nsg}')

# # Add rule to deny high-risk ports (SSH and RDP)
# self.cmd('network nsg rule create -g {rg} --nsg-name {nsg} -n DenyHighRiskPorts --priority 101 '
# '--access Deny --protocol Tcp --direction Inbound --source-address-prefix Internet '
# '--source-port-range "*" --destination-address-prefix "*" --destination-port-ranges 22 3389')

# # Create VMSS with the NSG attached
# self.cmd('vmss create -g {rg} -n {vmss} --image Canonical:ubuntu-24_04-lts:server:latest '
# '--authentication-type password --lb-sku Standard '
# '--admin-username admin123 --admin-password TestTest12#$ '
# '--instance-count {count} --orchestration-mode Uniform --nsg {nsg}')



self.cmd('vmss create -g {rg} -n {vmss} --image Canonical:UbuntuServer:18.04-LTS:latest --authentication-type password --lb-sku Standard '
'--admin-username admin123 --admin-password TestTest12#$ --instance-count {count} --orchestration-mode Uniform')

instance_list = self.cmd('vmss list-instances --resource-group {rg} --name {vmss}', checks=[
self.check('type(@)', 'array'),
Expand Down
Loading