From ac5f3a8a74ffc6d5464f3a2f2839618c2f17db73 Mon Sep 17 00:00:00 2001 From: Travis Prescott Date: Thu, 5 May 2016 14:54:38 -0700 Subject: [PATCH] Updates to work with "patches vm" commands and VM create command. --- azure-cli.pyproj | 3 + src/azure/cli/commands/_auto_command.py | 11 +- .../cli/command_modules/storage/_params.py | 12 +- .../azure/cli/command_modules/vm/_actions.py | 29 ++++ .../azure/cli/command_modules/vm/_params.py | 102 +++++++++++++- .../azure/cli/command_modules/vm/custom.py | 99 +++++--------- .../azure/cli/command_modules/vm/generated.py | 127 ++---------------- 7 files changed, 183 insertions(+), 200 deletions(-) create mode 100644 src/command_modules/azure-cli-vm/azure/cli/command_modules/vm/_actions.py diff --git a/azure-cli.pyproj b/azure-cli.pyproj index d03725ea3..059f44a76 100644 --- a/azure-cli.pyproj +++ b/azure-cli.pyproj @@ -151,6 +151,9 @@ + + Code + Code diff --git a/src/azure/cli/commands/_auto_command.py b/src/azure/cli/commands/_auto_command.py index 320be63a3..6b08bbdf3 100644 --- a/src/azure/cli/commands/_auto_command.py +++ b/src/azure/cli/commands/_auto_command.py @@ -37,11 +37,8 @@ def _get_member(obj, path): def _make_func(client_factory, member_path, return_type_or_func, unbound_func, extra_parameters): def call_client(args): client = client_factory(**args) - for p in extra_parameters or []: - param_name = p['name'].split()[0] - param_name = re.sub('--', '', param_name) - param_name = re.sub('-', '_', param_name) - args.pop(param_name) + for param in extra_parameters.keys() if extra_parameters else []: + args.pop(param) ops_instance = _get_member(client, member_path) try: @@ -156,8 +153,8 @@ def build_operation(command_name, # append any 'extra' args needed (for example to obtain a client) that aren't required # by the SDK. if extra_parameters: - for arg in extra_parameters: - options.append(arg.copy()) + for arg in extra_parameters.keys(): + options.append(extra_parameters[arg].copy()) command_table[func] = { 'name': ' '.join([command_name, op.opname]), diff --git a/src/command_modules/azure-cli-storage/azure/cli/command_modules/storage/_params.py b/src/command_modules/azure-cli-storage/azure/cli/command_modules/storage/_params.py index 691f832e9..e53cf65b1 100644 --- a/src/command_modules/azure-cli-storage/azure/cli/command_modules/storage/_params.py +++ b/src/command_modules/azure-cli-storage/azure/cli/command_modules/storage/_params.py @@ -244,9 +244,9 @@ PARAMETER_ALIASES.update({ # SUPPLEMENTAL (EXTRA) PARAMETER SETS -STORAGE_DATA_CLIENT_ARGS = [ - PARAMETER_ALIASES['account_name'], - PARAMETER_ALIASES['account_key'], - PARAMETER_ALIASES['connection_string'], - PARAMETER_ALIASES['sas_token'] -] +STORAGE_DATA_CLIENT_ARGS = { + 'account_name': PARAMETER_ALIASES['account_name'], + 'account_key': PARAMETER_ALIASES['account_key'], + 'connection_string': PARAMETER_ALIASES['connection_string'], + 'sas_token': PARAMETER_ALIASES['sas_token'] +} diff --git a/src/command_modules/azure-cli-vm/azure/cli/command_modules/vm/_actions.py b/src/command_modules/azure-cli-vm/azure/cli/command_modules/vm/_actions.py new file mode 100644 index 000000000..84ab6e100 --- /dev/null +++ b/src/command_modules/azure-cli-vm/azure/cli/command_modules/vm/_actions.py @@ -0,0 +1,29 @@ +import argparse +import os +import re + +class VMImageFieldAction(argparse.Action): #pylint: disable=too-few-public-methods + def __call__(self, parser, namespace, values, option_string=None): + image = values + match = re.match('([^:]*):([^:]*):([^:]*):([^:]*)', image) + + if image.lower().endswith('.vhd'): + namespace.os_disk_uri = image + elif match: + namespace.os_type = 'Custom' + namespace.os_publisher = match.group(1) + namespace.os_offer = match.group(2) + namespace.os_sku = match.group(3) + namespace.os_version = match.group(4) + else: + namespace.os_type = image + +class VMSSHFieldAction(argparse.Action): #pylint: disable=too-few-public-methods + def __call__(self, parser, namespace, values, option_string=None): + ssh_value = values + + if os.path.exists(ssh_value): + with open(ssh_value, 'r') as f: + namespace.ssh_key_value = f.read() + else: + namespace.ssh_key_value = ssh_value diff --git a/src/command_modules/azure-cli-vm/azure/cli/command_modules/vm/_params.py b/src/command_modules/azure-cli-vm/azure/cli/command_modules/vm/_params.py index b52e3ba9e..a748f6bbc 100644 --- a/src/command_modules/azure-cli-vm/azure/cli/command_modules/vm/_params.py +++ b/src/command_modules/azure-cli-vm/azure/cli/command_modules/vm/_params.py @@ -1,10 +1,14 @@ +import argparse + from azure.mgmt.compute import ComputeManagementClient, ComputeManagementClientConfiguration from azure.mgmt.compute.models import VirtualHardDisk -from azure.cli.commands import (COMMON_PARAMETERS as GLOBAL_COMMON_PARAMETERS, extend_parameter) +from azure.cli.commands import COMMON_PARAMETERS as GLOBAL_COMMON_PARAMETERS, extend_parameter from azure.cli.commands._command_creation import get_mgmt_service_client -from azure.cli._locale import L from azure.cli.command_modules.vm._validators import MinMaxValue +from azure.cli.command_modules.vm._actions import VMImageFieldAction, VMSSHFieldAction +from azure.cli._help_files import helps +from azure.cli._locale import L # FACTORIES @@ -17,19 +21,16 @@ PARAMETER_ALIASES = GLOBAL_COMMON_PARAMETERS.copy() PARAMETER_ALIASES.update({ 'diskname': { 'name': '--name -n', - 'dest': 'name', 'help': L('Disk name'), }, 'disksize': { 'name': '--disksize', - 'dest': 'disksize', 'help': L('Size of disk (Gb)'), 'type': MinMaxValue(1, 1023), 'default': 1023 }, 'lun': { 'name': '--lun', - 'dest': 'lun', 'help': L('0-based logical unit number (LUN). Max value depends on the Virtual ' + \ 'Machine size'), 'type': int, @@ -38,4 +39,95 @@ PARAMETER_ALIASES.update({ 'name': '--vhd', 'type': VirtualHardDisk }, + 'vm_name': { + 'name': '--vm-name', + 'dest': 'vm_name', + 'help': 'Name of Virtual Machine to update', + } }) + +VM_CREATE_PARAMETER_ALIASES = { + 'name': { + 'name': '--name -n' + }, + 'os_disk_uri': { + 'name': '--os-disk-uri', + 'help': argparse.SUPPRESS + }, + 'os_offer': { + 'name': '--os_offer', + 'help': argparse.SUPPRESS + }, + 'os_publisher': { + 'name': '--os-publisher', + 'help': argparse.SUPPRESS + }, + 'os_sku': { + 'name': '--os-sku', + 'help': argparse.SUPPRESS + }, + 'os_type': { + 'name': '--os-type', + 'help': argparse.SUPPRESS + }, + 'os_version': { + 'name': '--os-version', + 'help': argparse.SUPPRESS + }, +} + +# EXTRA PARAMETER SETS + +VM_CREATE_EXTRA_PARAMETERS = { + 'image': { + 'name': '--image', + 'help': 'The OS image. Supported values: Common OS (e.g. Win2012R2Datacenter), ' + \ + 'URN (e.g. "publisher:offer:sku:version"), or existing VHD URI.', + 'action': VMImageFieldAction + }, + 'ssh_key_value': { + 'name': '--ssh-key-value', + 'action': VMSSHFieldAction + } +} + +VM_PATCH_EXTRA_PARAMETERS = { + 'resource_group_name': + extend_parameter(PARAMETER_ALIASES['resource_group_name'], required=True), + 'vm_name': + extend_parameter(PARAMETER_ALIASES['vm_name'], required=True) +} + +# HELP PARAMETERS + +helps['vm create'] = """ + type: command + short-summary: Create an Azure Virtual Machine + long-summary: See https://azure.microsoft.com/en-us/documentation/articles/virtual-machines-linux-quick-create-cli/ for an end-to-end tutorial + parameters: + - name: --image + type: string + required: false + short-summary: OS image + long-summary: | + Common OS types: CentOS, CoreOS, Debian, openSUSE, RHEL, SLES, UbuntuLTS, + Win2012R2Datacenter, Win2012Datacenter, Win2008R2SP1 + Example URN: canonical:Ubuntu_Snappy_Core:15.04:2016.0318.1949 + Example URI: http://.blob.core.windows.net/vhds/osdiskimage.vhd + populator-commands: + - az vm image list + - az vm image show + examples: + - name: Create a simple Windows Server VM with private IP address + text: > + az vm create --image Win2012R2Datacenter --admin-username myadmin --admin-password Admin_001 + -l "West US" -g myvms --name myvm001 + - name: Create a Linux VM with SSH key authentication, add a public DNS entry and add to an existing Virtual Network and Availability Set. + text: > + az vm create --image canonical:Ubuntu_Snappy_Core:15.04:2016.0318.1949 + --admin-username myadmin --admin-password Admin_001 --authentication-type sshkey + --virtual-network-type existing --virtual-network-name myvnet --subnet-name default + --availability-set-type existing --availability-set-id myavailset + --public-ip-address-type new --dns-name-for-public-ip myGloballyUniqueVmDnsName + -l "West US" -g myvms --name myvm18o --ssh-key-value "" + """ diff --git a/src/command_modules/azure-cli-vm/azure/cli/command_modules/vm/custom.py b/src/command_modules/azure-cli-vm/azure/cli/command_modules/vm/custom.py index e7d37227e..d2b2232e5 100644 --- a/src/command_modules/azure-cli-vm/azure/cli/command_modules/vm/custom.py +++ b/src/command_modules/azure-cli-vm/azure/cli/command_modules/vm/custom.py @@ -10,57 +10,32 @@ except ImportError: from azure.mgmt.compute.models import DataDisk from azure.mgmt.compute.models.compute_management_client_enums import DiskCreateOptionTypes -from azure.cli._locale import L from azure.cli.commands import CommandTable, LongRunningOperation, RESOURCE_GROUP_ARG_NAME from azure.cli.commands._command_creation import get_mgmt_service_client -from ._params import PARAMETER_ALIASES, _compute_client_factory +from ._params import PARAMETER_ALIASES, VM_PATCH_EXTRA_PARAMETERS, _compute_client_factory command_table = CommandTable() -def vm_getter(args): - ''' Retreive a VM based on the `args` passed in. - ''' - client = _compute_client_factory(**args) - result = client.virtual_machines.get(args.get(RESOURCE_GROUP_ARG_NAME), args.get('vm_name')) - return result +def _vm_get(**kwargs): + '''Retrieves a VM if a resource group and vm name are supplied.''' + vm_name = kwargs.get('vm_name') + resource_group_name = kwargs.get('resource_group_name') + client = _compute_client_factory() + return client.virtual_machines.get(resource_group_name, vm_name) \ + if resource_group_name and vm_name else None -def vm_setter(args, instance, start_msg, end_msg): - '''Update the given Virtual Machine instance - ''' +def _vm_set(instance, start_msg, end_msg): + '''Update the given Virtual Machine instance''' instance.resources = None # Issue: https://github.com/Azure/autorest/issues/934 - client = _compute_client_factory(**args) + client = _compute_client_factory() + parsed_id = _parse_rg_name(instance.id) poller = client.virtual_machines.create_or_update( - resource_group_name=args.get(RESOURCE_GROUP_ARG_NAME), - vm_name=args.get('vm_name'), + resource_group_name=parsed_id[0], + vm_name=parsed_id[1], parameters=instance) return LongRunningOperation(start_msg, end_msg)(poller) -def patches_vm(start_msg, finish_msg): - '''Decorator indicating that the decorated function modifies an existing Virtual Machine - in Azure. - It automatically adds arguments required to identify the Virtual Machine to be patched and - handles the actual put call to the compute service, leaving the decorated function to only - have to worry about the modifications it has to do. - ''' - def wrapped(func): - def invoke(args): - instance = vm_getter(args) - func(args, instance) - vm_setter(args, instance, start_msg, finish_msg) - - # All Virtual Machines are identified with a resource group name/name pair, so - # we add these parameters to all commands - command_table[invoke]['arguments'].append(PARAMETER_ALIASES['resource_group_name']) - command_table[invoke]['arguments'].append({ - 'name': '--vm-name -n', - 'dest': 'vm_name', - 'help': 'Name of Virtual Machine to update', - 'required': True - }) - return invoke - return wrapped - def _load_images_from_aliases_doc(publisher, offer, sku): target_url = ('https://raw.githubusercontent.com/Azure/azure-rest-api-specs/' 'master/arm-compute/quickstart-templates/aliases.json') @@ -134,7 +109,8 @@ def _create_image_instance(publisher, offer, sku, version): 'offer': offer, 'sku': sku, 'version': version - } + } + # # Composite convenience commands for the CLI # @@ -150,23 +126,17 @@ def _parse_rg_name(strid): class ConvenienceVmCommands(object): # pylint: disable=too-few-public-methods - def __init__(self, **_): - pass + def __init__(self, **kwargs): + self.vm = _vm_get(**kwargs) def list(self, resource_group_name): ''' List Virtual Machines. ''' ccf = _compute_client_factory() vm_list = ccf.virtual_machines.list(resource_group_name=resource_group_name) \ - if group else ccf.virtual_machines.list_all() + if resource_group_name else ccf.virtual_machines.list_all() return list(vm_list) - - def list_vm_images(self, - image_location=None, - publisher=None, - offer=None, - sku=None, - all=False): + def list_vm_images(self, image_location=None, publisher=None, offer=None, sku=None, all=False): # pylint: disable=redefined-builtin '''vm image list :param str location:Image location :param str publisher:Image publisher name @@ -247,34 +217,31 @@ class ConvenienceVmCommands(object): # pylint: disable=too-few-public-methods return result - #@command_table.command('vm disk attach-new', - @patches_vm('Attaching disk', 'Disk attached') - def attach_new_disk(self, lun, diskname, vhd, disksize=1023, **kwargs): + def attach_new_disk(self, lun, diskname, vhd, disksize=1023, **kwargs): ''' Attach a new disk to an existing Virtual Machine''' - disk = DataDisk(lun=lun, vhd=vhd, name=kwargs.get('name'), + disk = DataDisk(lun=lun, vhd=vhd, name=diskname, create_option=DiskCreateOptionTypes.empty, disk_size_gb=disksize) - kwargs.get('instance').storage_profile.data_disks.append(disk) + self.vm.storage_profile.data_disks.append(disk) + _vm_set(self.vm, 'Attaching disk', 'Disk attached') - #@command_table.command('vm disk attach-existing', - @patches_vm('Attaching disk', 'Disk attached') def attach_existing_disk(self, lun, diskname, vhd, disksize=1023, **kwargs): ''' Attach an existing disk to an existing Virtual Machine ''' # TODO: figure out size of existing disk instead of making the default value 1023 - disk = DataDisk(lun=lun, vhd=vhd, name=kwargs.get('name'), + disk = DataDisk(lun=lun, vhd=vhd, name=diskname, create_option=DiskCreateOptionTypes.attach, disk_size_gb=disksize) - kwargs.get('instance').storage_profile.data_disks.append(disk) + self.vm.storage_profile.data_disks.append(disk) + _vm_set(self.vm, 'Attaching disk', 'Disk attached') - #@command_table.command('vm disk detach') - @patches_vm('Detaching disk', 'Disk detached') def detach_disk(self, diskname, **kwargs): + ''' Detach a disk from a Virtual Machine ''' # Issue: https://github.com/Azure/autorest/issues/934 - instance = kwargs.get('instance') - instance.resources = None + self.vm.resources = None try: - disk = next(d for d in instance.storage_profile.data_disks + disk = next(d for d in self.vm.storage_profile.data_disks if d.name == kwargs.get('name')) - instance.storage_profile.data_disks.remove(disk) + self.vm.storage_profile.data_disks.remove(disk) except StopIteration: - raise RuntimeError("No disk with the name '%s' found" % args.get('name')) + raise RuntimeError("No disk with the name '{}' found".format(diskname)) + _vm_set(self.vm, 'Detaching disk', 'Disk detached') diff --git a/src/command_modules/azure-cli-vm/azure/cli/command_modules/vm/generated.py b/src/command_modules/azure-cli-vm/azure/cli/command_modules/vm/generated.py index a56c987cf..70038680a 100644 --- a/src/command_modules/azure-cli-vm/azure/cli/command_modules/vm/generated.py +++ b/src/command_modules/azure-cli-vm/azure/cli/command_modules/vm/generated.py @@ -1,7 +1,3 @@ -import argparse -import os -import re - from azure.mgmt.compute.operations import (AvailabilitySetsOperations, VirtualMachineExtensionImagesOperations, VirtualMachineExtensionsOperations, @@ -15,14 +11,14 @@ from azure.mgmt.compute.operations import (AvailabilitySetsOperations, from azure.cli.commands._auto_command import build_operation, AutoCommandDefinition from azure.cli.commands._command_creation import get_mgmt_service_client from azure.cli.commands import CommandTable, LongRunningOperation -from azure.cli._locale import L from azure.cli.command_modules.vm.mgmt.lib import (VMCreationClient as VMClient, VMCreationClientConfiguration as VMClientConfig) from azure.cli.command_modules.vm.mgmt.lib.operations import VMOperations -from azure.cli._help_files import helps +from azure.cli._locale import L -from ._params import PARAMETER_ALIASES, _compute_client_factory +from ._params import (PARAMETER_ALIASES, VM_CREATE_EXTRA_PARAMETERS, VM_CREATE_PARAMETER_ALIASES, + VM_PATCH_EXTRA_PARAMETERS, _compute_client_factory) from .custom import ConvenienceVmCommands command_table = CommandTable() @@ -62,7 +58,7 @@ build_operation( AutoCommandDefinition(ConvenienceVmCommands.attach_existing_disk, 'Object', 'attach-existing'), AutoCommandDefinition(ConvenienceVmCommands.detach_disk, 'Object', 'detach'), ], - command_table, PARAMETER_ALIASES) + command_table, PARAMETER_ALIASES, VM_PATCH_EXTRA_PARAMETERS) build_operation( 'vm extension', 'virtual_machine_extensions', _compute_client_factory, @@ -86,7 +82,7 @@ build_operation( command_table, PARAMETER_ALIASES) build_operation( - 'vm usage', 'usage',_compute_client_factory, + 'vm usage', 'usage', _compute_client_factory, [ AutoCommandDefinition(UsageOperations.list, '[Usage]'), ], @@ -161,116 +157,15 @@ build_operation( ], command_table, PARAMETER_ALIASES) -vm_param_aliases = { - 'name': { - 'name': '--name -n' - }, - 'os_disk_uri': { - 'name': '--os-disk-uri', - 'help': argparse.SUPPRESS - }, - 'os_offer': { - 'name': '--os_offer', - 'help': argparse.SUPPRESS - }, - 'os_publisher': { - 'name': '--os-publisher', - 'help': argparse.SUPPRESS - }, - 'os_sku': { - 'name': '--os-sku', - 'help': argparse.SUPPRESS - }, - 'os_type': { - 'name': '--os-type', - 'help': argparse.SUPPRESS - }, - 'os_version': { - 'name': '--os-version', - 'help': argparse.SUPPRESS - }, - } - -class VMImageFieldAction(argparse.Action): #pylint: disable=too-few-public-methods - def __call__(self, parser, namespace, values, option_string=None): - image = values - match = re.match('([^:]*):([^:]*):([^:]*):([^:]*)', image) - - if image.lower().endswith('.vhd'): - namespace.os_disk_uri = image - elif match: - namespace.os_type = 'Custom' - namespace.os_publisher = match.group(1) - namespace.os_offer = match.group(2) - namespace.os_sku = match.group(3) - namespace.os_version = match.group(4) - else: - namespace.os_type = image - -class VMSSHFieldAction(argparse.Action): #pylint: disable=too-few-public-methods - def __call__(self, parser, namespace, values, option_string=None): - ssh_value = values - - if os.path.exists(ssh_value): - with open(ssh_value, 'r') as f: - namespace.ssh_key_value = f.read() - else: - namespace.ssh_key_value = ssh_value - -extra_parameters = [ - { - 'name': '--image', - 'help': 'The OS image. Supported values: Common OS (e.g. Win2012R2Datacenter), URN (e.g. "publisher:offer:sku:version"), or existing VHD URI.', - 'action': VMImageFieldAction - }, - { - 'name': '--ssh-key-value', - 'action': VMSSHFieldAction - } - ] - -helps['vm create'] = """ - type: command - short-summary: Create an Azure Virtual Machine - long-summary: See https://azure.microsoft.com/en-us/documentation/articles/virtual-machines-linux-quick-create-cli/ for an end-to-end tutorial - parameters: - - name: --image - type: string - required: false - short-summary: OS image - long-summary: | - Common OS types: CentOS, CoreOS, Debian, openSUSE, RHEL, SLES, UbuntuLTS, - Win2012R2Datacenter, Win2012Datacenter, Win2008R2SP1 - Example URN: canonical:Ubuntu_Snappy_Core:15.04:2016.0318.1949 - Example URI: http://.blob.core.windows.net/vhds/osdiskimage.vhd - populator-commands: - - az vm image list - - az vm image show - examples: - - name: Create a simple Windows Server VM with private IP address - text: > - az vm create --image Win2012R2Datacenter --admin-username myadmin --admin-password Admin_001 - -l "West US" -g myvms --name myvm001 - - name: Create a Linux VM with SSH key authentication, add a public DNS entry and add to an existing Virtual Network and Availability Set. - text: > - az vm create --image canonical:Ubuntu_Snappy_Core:15.04:2016.0318.1949 - --admin-username myadmin --admin-password Admin_001 --authentication-type sshkey - --virtual-network-type existing --virtual-network-name myvnet --subnet-name default - --availability-set-type existing --availability-set-id myavailset - --public-ip-address-type new --dns-name-for-public-ip myGloballyUniqueVmDnsName - -l "West US" -g myvms --name myvm18o --ssh-key-value "" - """ - build_operation( - 'vm', 'vm', lambda _: get_mgmt_service_client(VMClient, VMClientConfig), + 'vm', 'vm', lambda **_: get_mgmt_service_client(VMClient, VMClientConfig), [ - AutoCommandDefinition(VMOperations.create_or_update, - LongRunningOperation(L('Creating virtual machine'), L('Virtual machine created')), - 'create') + AutoCommandDefinition( + VMOperations.create_or_update, + LongRunningOperation(L('Creating virtual machine'), L('Virtual machine created')), + 'create') ], - command_table, - vm_param_aliases, - extra_parameters) + command_table, VM_CREATE_PARAMETER_ALIASES, VM_CREATE_EXTRA_PARAMETERS) build_operation( 'vm image', None, ConvenienceVmCommands,