From 2ea017a794920e6a93cbaba0dae0a7dbd687ef08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Serta=C3=A7=20=C3=96zercan?= Date: Tue, 29 Aug 2017 14:14:15 -0700 Subject: [PATCH] Azure Virtual Machine Scale Set support (azure_rm_virtualmachine_scaleset) (#28381) * initial virtual machine scaleset implementation * added docs * fixed formatting * added update feature for os disk caching and capacity * add tier * added return * add integration tests * add vmss facts * add data disk support * added data disks docs * update integration test --- .../azure/azure_rm_virtualmachine_scaleset.py | 760 ++++++++++++++++++ .../azure_rm_virtualmachine_scaleset_facts.py | 238 ++++++ .../azure_rm_virtualmachine_scaleset/aliases | 3 + .../meta/main.yml | 2 + .../tasks/main.yml | 88 ++ 5 files changed, 1091 insertions(+) create mode 100644 lib/ansible/modules/cloud/azure/azure_rm_virtualmachine_scaleset.py create mode 100644 lib/ansible/modules/cloud/azure/azure_rm_virtualmachine_scaleset_facts.py create mode 100644 test/integration/targets/azure_rm_virtualmachine_scaleset/aliases create mode 100644 test/integration/targets/azure_rm_virtualmachine_scaleset/meta/main.yml create mode 100644 test/integration/targets/azure_rm_virtualmachine_scaleset/tasks/main.yml diff --git a/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine_scaleset.py b/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine_scaleset.py new file mode 100644 index 00000000000..776c7340dd8 --- /dev/null +++ b/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine_scaleset.py @@ -0,0 +1,760 @@ +#!/usr/bin/python +# +# Copyright (c) 2016 Sertac Ozercan, +# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = ''' +--- +module: azure_rm_virtualmachine_scaleset + +version_added: "2.4" + +short_description: Manage Azure virtual machine scale sets. + +description: + - Create and update a virtual machine scale set. + +options: + resource_group: + description: + - Name of the resource group containing the virtual machine scale set. + required: true + name: + description: + - Name of the virtual machine. + required: true + state: + description: + - Assert the state of the virtual machine scale set. + - State 'present' will check that the machine exists with the requested configuration. If the configuration + of the existing machine does not match, the machine will be updated. + state. + - State 'absent' will remove the virtual machine scale set. + default: present + choices: + - absent + - present + location: + description: + - Valid Azure location. Defaults to location of the resource group. + vm_size: + description: + - A valid Azure VM size value. For example, 'Standard_D4'. The list of choices varies depending on the + subscription and location. Check your subscription for available choices. + required: true + capacity: + description: + - Capacity of VMSS + required: true + tier: + description: + - SKU Tier + choices: + - Basic + - Standard + upgrade_policy: + description: + - Upgrade policy + choices: + - Manual + - Automatic + admin_username: + description: + - Admin username used to access the host after it is created. Required when creating a VM. + admin_password: + description: + - Password for the admin username. Not required if the os_type is Linux and SSH password authentication + is disabled by setting ssh_password_enabled to false. + ssh_password_enabled: + description: + - When the os_type is Linux, setting ssh_password_enabled to false will disable SSH password authentication + and require use of SSH keys. + default: true + ssh_public_keys: + description: + - "For os_type Linux provide a list of SSH keys. Each item in the list should be a dictionary where the + dictionary contains two keys: path and key_data. Set the path to the default location of the + authorized_keys files. On an Enterprise Linux host, for example, the path will be + /home//.ssh/authorized_keys. Set key_data to the actual value of the public key." + image: + description: + - "A dictionary describing the Marketplace image used to build the VM. Will contain keys: publisher, + offer, sku and version. NOTE: set image.version to 'latest' to get the most recent version of a given + image." + required: true + os_disk_caching: + description: + - Type of OS disk caching. + choices: + - ReadOnly + - ReadWrite + default: ReadOnly + aliases: + - disk_caching + os_type: + description: + - Base type of operating system. + choices: + - Windows + - Linux + default: + - Linux + managed_disk_type: + description: + - Managed disk type + choices: + - Standard_LRS + - Premium_LRS + data_disks: + description: + - Describes list of data disks. + required: false + default: null + version_added: "2.4" + suboptions: + lun: + description: + - The logical unit number for data disk + default: 0 + version_added: "2.4" + disk_size_gb: + description: + - The initial disk size in GB for blank data disks + version_added: "2.4" + managed_disk_type: + description: + - Managed data disk type + choices: + - Standard_LRS + - Premium_LRS + version_added: "2.4" + caching: + description: + - Type of data disk caching. + choices: + - ReadOnly + - ReadWrite + default: ReadOnly + version_added: "2.4" + virtual_network_name: + description: + - Virtual Network name + aliases: + - virtual_network + subnet_name: + description: + - Subnet name + aliases: + - subnet + remove_on_absent: + description: + - When removing a VM using state 'absent', also remove associated resources + - "It can be 'all' or a list with any of the following: ['network_interfaces', 'virtual_storage', 'public_ips']" + - Any other input will be ignored + default: ['all'] + +extends_documentation_fragment: + - azure + - azure_tags + +author: + - "Sertac Ozercan (@sozercan)" + +''' +EXAMPLES = ''' + +- name: Create VMSS + azure_rm_virtualmachine_scaleset: + resource_group: Testing + name: testvmss + vm_size: Standard_DS1_v2 + capacity: 2 + virtual_network_name: testvnet + subnet_name: testsubnet + admin_username: adminUser + ssh_password_enabled: false + ssh_public_keys: + - path: /home/adminUser/.ssh/authorized_keys + key_data: < insert yor ssh public key here... > + managed_disk_type: Standard_LRS + image: + offer: CoreOS + publisher: CoreOS + sku: Stable + version: latest + data_disks: + - lun: 0 + disk_size_gb: 64 + caching: ReadWrite + managed_disk_type: Standard_LRS +''' + +RETURN = ''' +azure_vmss: + description: Facts about the current state of the object. Note that facts are not part of the registered output but available directly. + returned: always + type: complex + contains: { + "properties": { + "overprovision": true, + "singlePlacementGroup": true, + "upgradePolicy": { + "mode": "Manual" + }, + "virtualMachineProfile": { + "networkProfile": { + "networkInterfaceConfigurations": [ + { + "name": "testvmss", + "properties": { + "dnsSettings": { + "dnsServers": [] + }, + "enableAcceleratedNetworking": false, + "ipConfigurations": [ + { + "name": "default", + "properties": { + "privateIPAddressVersion": "IPv4", + "subnet": { + "id": "/subscriptions/XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX/resourceGroups/Testing/providers/Microsoft.Network/virtualNetworks/testvnet/subnets/testsubnet" + } + } + } + ], + "primary": true + } + } + ] + }, + "osProfile": { + "adminUsername": "testuser", + "computerNamePrefix": "testvmss", + "linuxConfiguration": { + "disablePasswordAuthentication": true, + "ssh": { + "publicKeys": [ + { + "keyData": "", + "path": "/home/testuser/.ssh/authorized_keys" + } + ] + } + }, + "secrets": [] + }, + "storageProfile": { + "dataDisks": [ + { + "caching": "ReadWrite", + "createOption": "empty", + "diskSizeGB": 64, + "lun": 0, + "managedDisk": { + "storageAccountType": "Standard_LRS" + } + } + ], + "imageReference": { + "offer": "CoreOS", + "publisher": "CoreOS", + "sku": "Stable", + "version": "899.17.0" + }, + "osDisk": { + "caching": "ReadWrite", + "createOption": "fromImage", + "managedDisk": { + "storageAccountType": "Standard_LRS" + } + } + } + } + }, + "sku": { + "capacity": 2, + "name": "Standard_DS1_v2", + "tier": "Standard" + }, + "tags": null, + "type": "Microsoft.Compute/virtualMachineScaleSets" + } +''' # NOQA + +import random +import re + +try: + from msrestazure.azure_exceptions import CloudError + from azure.mgmt.compute.models import VirtualMachineScaleSet, \ + VirtualMachineScaleSetStorageProfile, \ + VirtualMachineScaleSetOSProfile, \ + VirtualMachineScaleSetOSDisk, VirtualMachineScaleSetDataDisk, \ + VirtualMachineScaleSetManagedDiskParameters, \ + VirtualMachineScaleSetNetworkProfile, LinuxConfiguration, \ + SshConfiguration, SshPublicKey, VirtualMachineSizeTypes, \ + DiskCreateOptionTypes, CachingTypes, \ + VirtualMachineScaleSetVMProfile, VirtualMachineScaleSetIdentity, \ + VirtualMachineScaleSetIPConfiguration, \ + VirtualMachineScaleSetPublicIPAddressConfigurationDnsSettings, \ + VirtualMachineScaleSetPublicIPAddressConfiguration, Sku, \ + UpgradePolicy, VirtualMachineScaleSetNetworkConfiguration, \ + ApiEntityReference, ImageReference + + from azure.mgmt.network.models import PublicIPAddress, \ + NetworkSecurityGroup, NetworkInterface, \ + NetworkInterfaceIPConfiguration, Subnet, VirtualNetwork + +except ImportError: + # This is handled in azure_rm_common + pass + +from ansible.module_utils.azure_rm_common import AzureRMModuleBase, azure_id_to_dict + + +AZURE_OBJECT_CLASS = 'VirtualMachineScaleSet' + +AZURE_ENUM_MODULES = ['azure.mgmt.compute.models'] + + +class AzureRMVirtualMachineScaleSet(AzureRMModuleBase): + + def __init__(self): + + self.module_arg_spec = dict( + resource_group=dict(type='str', required=True), + name=dict(type='str', required=True), + state=dict(choices=['present', 'absent'], default='present', type='str'), + location=dict(type='str'), + short_hostname=dict(type='str'), + vm_size=dict(type='str', required=True), + tier=dict(type='str', choices=['Basic', 'Standard']), + capacity=dict(type='int', default=1), + upgrade_policy=dict(type='str', choices=['Automatic', 'Manual']), + admin_username=dict(type='str'), + admin_password=dict(type='str', no_log=True), + ssh_password_enabled=dict(type='bool', default=True), + ssh_public_keys=dict(type='list'), + image=dict(type='dict'), + os_disk_caching=dict(type='str', aliases=['disk_caching'], choices=['ReadOnly', 'ReadWrite'], + default='ReadOnly'), + os_type=dict(type='str', choices=['Linux', 'Windows'], default='Linux'), + managed_disk_type=dict(type='str', choices=['Standard_LRS', 'Premium_LRS']), + data_disks=dict(type='list'), + subnet_name=dict(type='str', aliases=['subnet']), + virtual_network_name=dict(type='str', aliases=['virtual_network']), + remove_on_absent=dict(type='list', default=['all']), + ) + + self.resource_group = None + self.name = None + self.state = None + self.location = None + self.short_hostname = None + self.vm_size = None + self.capacity = None + self.tier = None + self.upgrade_policy = None + self.admin_username = None + self.admin_password = None + self.ssh_password_enabled = None + self.ssh_public_keys = None + self.image = None + self.os_disk_caching = None + self.managed_disk_type = None + self.data_disks = None + self.os_type = None + self.subnet_name = None + self.virtual_network_name = None + self.tags = None + self.differences = None + + self.results = dict( + changed=False, + actions=[], + ansible_facts=dict(azure_vmss=None) + ) + + super(AzureRMVirtualMachineScaleSet, self).__init__( + derived_arg_spec=self.module_arg_spec, + supports_check_mode=True + ) + + def exec_module(self, **kwargs): + + for key in list(self.module_arg_spec.keys()) + ['tags']: + setattr(self, key, kwargs[key]) + + # make sure options are lower case + self.remove_on_absent = set([resource.lower() for resource in self.remove_on_absent]) + + changed = False + results = dict() + vmss = None + disable_ssh_password = None + vmss_dict = None + virtual_network = None + subnet = None + + resource_group = self.get_resource_group(self.resource_group) + if not self.location: + # Set default location + self.location = resource_group.location + + if self.state == 'present': + # Verify parameters and resolve any defaults + + if self.vm_size and not self.vm_size_is_valid(): + self.fail("Parameter error: vm_size {0} is not valid for your subscription and location.".format( + self.vm_size + )) + + # if self.virtual_network_name: + # virtual_network = self.get_virtual_network(self.virtual_network_name) + + if self.ssh_public_keys: + msg = "Parameter error: expecting ssh_public_keys to be a list of type dict where " \ + "each dict contains keys: path, key_data." + for key in self.ssh_public_keys: + if not isinstance(key, dict): + self.fail(msg) + if not key.get('path') or not key.get('key_data'): + self.fail(msg) + + if self.image: + if not self.image.get('publisher') or not self.image.get('offer') or not self.image.get('sku') \ + or not self.image.get('version'): + self.error("parameter error: expecting image to contain publisher, offer, sku and version keys.") + image_version = self.get_image_version() + if self.image['version'] == 'latest': + self.image['version'] = image_version.name + self.log("Using image version {0}".format(self.image['version'])) + + disable_ssh_password = not self.ssh_password_enabled + + try: + self.log("Fetching virtual machine scale set {0}".format(self.name)) + vmss = self.compute_client.virtual_machine_scale_sets.get(self.resource_group, self.name) + self.check_provisioning_state(vmss, self.state) + vmss_dict = self.serialize_vmss(vmss) + + if self.state == 'present': + differences = [] + results = vmss_dict + + if self.os_disk_caching and \ + self.os_disk_caching != vmss_dict['properties']['virtualMachineProfile']['storageProfile']['osDisk']['caching']: + self.log('CHANGED: virtual machine scale set {0} - OS disk caching'.format(self.name)) + differences.append('OS Disk caching') + changed = True + vmss_dict['properties']['virtualMachineProfile']['storageProfile']['osDisk']['caching'] = self.os_disk_caching + + if self.capacity and \ + self.capacity != vmss_dict['sku']['capacity']: + self.log('CHANGED: virtual machine scale set {0} - Capacity'.format(self.name)) + differences.append('Capacity') + changed = True + vmss_dict['sku']['capacity'] = self.capacity + + if self.data_disks and \ + len(self.data_disks) != len(vmss_dict['properties']['virtualMachineProfile']['storageProfile']['dataDisks']): + self.log('CHANGED: virtual machine scale set {0} - Data Disks'.format(self.name)) + differences.append('Data Disks') + changed = True + + update_tags, vmss_dict['tags'] = self.update_tags(vmss_dict.get('tags', dict())) + if update_tags: + differences.append('Tags') + changed = True + + self.differences = differences + + elif self.state == 'absent': + self.log("CHANGED: virtual machine scale set {0} exists and requested state is 'absent'".format(self.name)) + results = dict() + changed = True + + except CloudError: + self.log('Virtual machine scale set {0} does not exist'.format(self.name)) + if self.state == 'present': + self.log("CHANGED: virtual machine scale set {0} does not exist but state is 'present'.".format(self.name)) + changed = True + + self.results['changed'] = changed + self.results['ansible_facts']['azure_vmss'] = results + + if self.check_mode: + return self.results + + if changed: + if self.state == 'present': + if not vmss: + # Create the VMSS + self.log("Create virtual machine scale set {0}".format(self.name)) + self.results['actions'].append('Created VMSS {0}'.format(self.name)) + + # Validate parameters + if not self.admin_username: + self.fail("Parameter error: admin_username required when creating a virtual machine scale set.") + + if self.os_type == 'Linux': + if disable_ssh_password and not self.ssh_public_keys: + self.fail("Parameter error: ssh_public_keys required when disabling SSH password.") + + if self.subnet_name: + subnet = self.get_subnet(self.virtual_network_name, self.subnet_name) + + if not self.virtual_network_name: + default_vnet = self.create_default_vnet() + virtual_network = default_vnet.id + + if not self.short_hostname: + self.short_hostname = self.name + + managed_disk = VirtualMachineScaleSetManagedDiskParameters(storage_account_type=self.managed_disk_type) + + vmss_resource = VirtualMachineScaleSet( + self.location, + tags=self.tags, + upgrade_policy=UpgradePolicy( + mode=self.upgrade_policy + ), + sku=Sku( + name=self.vm_size, + capacity=self.capacity, + tier=self.tier, + ), + virtual_machine_profile=VirtualMachineScaleSetVMProfile( + os_profile=VirtualMachineScaleSetOSProfile( + admin_username=self.admin_username, + computer_name_prefix=self.short_hostname, + ), + storage_profile=VirtualMachineScaleSetStorageProfile( + os_disk=VirtualMachineScaleSetOSDisk( + managed_disk=managed_disk, + create_option=DiskCreateOptionTypes.from_image, + caching=self.os_disk_caching, + ), + image_reference=ImageReference( + publisher=self.image['publisher'], + offer=self.image['offer'], + sku=self.image['sku'], + version=self.image['version'], + ), + ), + network_profile=VirtualMachineScaleSetNetworkProfile( + network_interface_configurations=[ + VirtualMachineScaleSetNetworkConfiguration( + name=self.name, + primary=True, + ip_configurations=[ + VirtualMachineScaleSetIPConfiguration( + name='default', + subnet=ApiEntityReference( + id=subnet.id + ) + ) + ] + ) + ] + ) + ) + ) + + if self.admin_password: + vmss_resource.virtual_machine_profile.os_profile.admin_password = self.admin_password + + if self.os_type == 'Linux': + vmss_resource.virtual_machine_profile.os_profile.linux_configuration = LinuxConfiguration( + disable_password_authentication=disable_ssh_password + ) + + if self.ssh_public_keys: + ssh_config = SshConfiguration() + ssh_config.public_keys = \ + [SshPublicKey(path=key['path'], key_data=key['key_data']) for key in self.ssh_public_keys] + vmss_resource.virtual_machine_profile.os_profile.linux_configuration.ssh = ssh_config + + if self.data_disks: + data_disks = [] + + for data_disk in self.data_disks: + data_disk_managed_disk = VirtualMachineScaleSetManagedDiskParameters( + storage_account_type=data_disk['managed_disk_type'] + ) + + data_disk['caching'] = data_disk.get( + 'caching', + CachingTypes.read_only + ) + + data_disks.append(VirtualMachineScaleSetDataDisk( + lun=data_disk['lun'], + caching=data_disk['caching'], + create_option=DiskCreateOptionTypes.empty, + disk_size_gb=data_disk['disk_size_gb'], + managed_disk=data_disk_managed_disk, + )) + + vmss_resource.virtual_machine_profile.storage_profile.data_disks = data_disks + + self.log("Create virtual machine with parameters:") + self.create_or_update_vmss(vmss_resource) + + elif self.differences and len(self.differences) > 0: + self.log("Update virtual machine scale set {0}".format(self.name)) + self.results['actions'].append('Updated VMSS {0}'.format(self.name)) + + vmss_resource = self.get_vmss() + vmss_resource.virtual_machine_profile.storage_profile.os_disk.caching = self.os_disk_caching + vmss_resource.sku.capacity = self.capacity + + data_disks = [] + for data_disk in self.data_disks: + data_disks.append(VirtualMachineScaleSetDataDisk( + lun=data_disk['lun'], + caching=data_disk['caching'], + create_option=DiskCreateOptionTypes.empty, + disk_size_gb=data_disk['disk_size_gb'], + managed_disk=VirtualMachineScaleSetManagedDiskParameters( + storage_account_type=data_disk['managed_disk_type'] + ), + )) + vmss_resource.virtual_machine_profile.storage_profile.data_disks = data_disks + + self.log("Update virtual machine with parameters:") + self.create_or_update_vmss(vmss_resource) + + self.results['ansible_facts']['azure_vmss'] = self.serialize_vmss(self.get_vmss()) + + elif self.state == 'absent': + # delete the VM + self.log("Delete virtual machine scale set {0}".format(self.name)) + self.results['ansible_facts']['azure_vmss'] = None + self.delete_vmss(vmss) + + # until we sort out how we want to do this globally + del self.results['actions'] + + return self.results + + def get_vmss(self): + ''' + Get the VMSS + + :return: VirtualMachineScaleSet object + ''' + try: + vmss = self.compute_client.virtual_machine_scale_sets.get(self.resource_group, self.name) + return vmss + except Exception as exc: + self.fail("Error getting virtual machine scale set {0} - {1}".format(self.name, str(exc))) + + def get_virtual_network(self, name): + try: + vnet = self.network_client.virtual_networks.get(self.resource_group, name) + return vnet + except Exception as exc: + self.fail("Error fetching virtual network {0} - {1}".format(name, str(exc))) + + def get_subnet(self, vnet_name, subnet_name): + self.log("Fetching subnet {0} in virtual network {1}".format(subnet_name, vnet_name)) + try: + subnet = self.network_client.subnets.get(self.resource_group, vnet_name, subnet_name) + except Exception as exc: + self.fail("Error: fetching subnet {0} in virtual network {1} - {2}".format( + subnet_name, + vnet_name, + str(exc))) + return subnet + + def serialize_vmss(self, vmss): + ''' + Convert a VirtualMachineScaleSet object to dict. + + :param vm: VirtualMachineScaleSet object + :return: dict + ''' + + result = self.serialize_obj(vmss, AZURE_OBJECT_CLASS, enum_modules=AZURE_ENUM_MODULES) + result['id'] = vmss.id + result['name'] = vmss.name + result['type'] = vmss.type + result['location'] = vmss.location + result['tags'] = vmss.tags + + return result + + def delete_vmss(self, vmss): + self.log("Deleting virtual machine scale set {0}".format(self.name)) + self.results['actions'].append("Deleted virtual machine scale set {0}".format(self.name)) + try: + poller = self.compute_client.virtual_machine_scale_sets.delete(self.resource_group, self.name) + # wait for the poller to finish + self.get_poller_result(poller) + except Exception as exc: + self.fail("Error deleting virtual machine scale set {0} - {1}".format(self.name, str(exc))) + + return True + + def get_image_version(self): + try: + versions = self.compute_client.virtual_machine_images.list(self.location, + self.image['publisher'], + self.image['offer'], + self.image['sku']) + except Exception as exc: + self.fail("Error fetching image {0} {1} {2} - {3}".format(self.image['publisher'], + self.image['offer'], + self.image['sku'], + str(exc))) + if versions and len(versions) > 0: + if self.image['version'] == 'latest': + return versions[len(versions) - 1] + for version in versions: + if version.name == self.image['version']: + return version + + self.fail("Error could not find image {0} {1} {2} {3}".format(self.image['publisher'], + self.image['offer'], + self.image['sku'], + self.image['version'])) + + def create_or_update_vmss(self, params): + try: + poller = self.compute_client.virtual_machine_scale_sets.create_or_update(self.resource_group, self.name, params) + self.get_poller_result(poller) + except Exception as exc: + self.fail("Error creating or updating virtual machine {0} - {1}".format(self.name, str(exc))) + + def vm_size_is_valid(self): + ''' + Validate self.vm_size against the list of virtual machine sizes available for the account and location. + + :return: boolean + ''' + try: + sizes = self.compute_client.virtual_machine_sizes.list(self.location) + except Exception as exc: + self.fail("Error retrieving available machine sizes - {0}".format(str(exc))) + for size in sizes: + if size.name == self.vm_size: + return True + return False + + +def main(): + AzureRMVirtualMachineScaleSet() + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine_scaleset_facts.py b/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine_scaleset_facts.py new file mode 100644 index 00000000000..3a41cb12a1f --- /dev/null +++ b/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine_scaleset_facts.py @@ -0,0 +1,238 @@ +#!/usr/bin/python +# +# Copyright (c) 2017 Sertac Ozercan, + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = ''' +--- +module: azure_rm_virtualmachine_scaleset_facts + +version_added: "2.4" + +short_description: Get Virtual Machine Scale Set facts + +description: + - Get facts for a virtual machine scale set + +options: + name: + description: + - Limit results to a specific virtual machine scale set + required: false + default: null + resource_group: + description: + - The resource group to search for the desired virtual machine scale set + required: false + default: null + +extends_documentation_fragment: + - azure + +author: + - "Sertac Ozercan (@sozercan)" +''' + +EXAMPLES = ''' + - name: Get facts for a virtual machine scale set + azure_rm_virtualmachine_scaleset_facts: + resource_group: Testing + name: testvmss001 + + - name: Get facts for all virtual networks + azure_rm_virtualmachine_scaleset_facts: + resource_group: Testing + + - name: Get facts by tags + azure_rm_virtualmachine_scaleset_facts: + resource_group: Testing + tags: + - testing +''' + +RETURN = ''' +azure_vmss: + description: List of virtual machine scale sets + returned: always + type: list + example: [{ + "location": "eastus", + "properties": { + "overprovision": true, + "singlePlacementGroup": true, + "upgradePolicy": { + "mode": "Manual" + }, + "virtualMachineProfile": { + "networkProfile": { + "networkInterfaceConfigurations": [ + { + "name": "testvmss", + "properties": { + "dnsSettings": { + "dnsServers": [] + }, + "enableAcceleratedNetworking": false, + "ipConfigurations": [ + { + "name": "default", + "properties": { + "privateIPAddressVersion": "IPv4", + "subnet": { + "id": "/subscriptions/XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX/resourceGroups/Testing/providers/Microsoft.Network/virtualNetworks/testvnet/subnets/testsubnet" + } + } + } + ], + "primary": true + } + } + ] + }, + "osProfile": { + "adminUsername": "testuser", + "computerNamePrefix": "testvmss", + "linuxConfiguration": { + "disablePasswordAuthentication": true, + "ssh": { + "publicKeys": [ + { + "keyData": "", + "path": "/home/testuser/.ssh/authorized_keys" + } + ] + } + }, + "secrets": [] + }, + "storageProfile": { + "imageReference": { + "offer": "CoreOS", + "publisher": "CoreOS", + "sku": "Stable", + "version": "899.17.0" + }, + "osDisk": { + "caching": "ReadWrite", + "createOption": "fromImage", + "managedDisk": { + "storageAccountType": "Standard_LRS" + } + } + } + } + }, + "sku": { + "capacity": 1, + "name": "Standard_DS1_v2", + "tier": "Standard" + } + }] +''' # NOQA + +from ansible.module_utils.azure_rm_common import AzureRMModuleBase + +try: + from msrestazure.azure_exceptions import CloudError +except: + # handled in azure_rm_common + pass + +AZURE_OBJECT_CLASS = 'VirtualMachineScaleSet' + +AZURE_ENUM_MODULES = ['azure.mgmt.compute.models'] + + +class AzureRMVirtualMachineScaleSetFacts(AzureRMModuleBase): + """Utility class to get virtual machine scale set facts""" + + def __init__(self): + + self.module_args = dict( + name=dict(type='str'), + resource_group=dict(type='str'), + tags=dict(type='list') + ) + + self.results = dict( + changed=False, + ansible_facts=dict( + azure_vmss=[] + ) + ) + + self.name = None + self.resource_group = None + self.tags = None + + super(AzureRMVirtualMachineScaleSetFacts, self).__init__( + derived_arg_spec=self.module_args, + supports_tags=False, + facts_module=True + ) + + def exec_module(self, **kwargs): + + for key in self.module_args: + setattr(self, key, kwargs[key]) + + if self.name and not self.resource_group: + self.fail("Parameter error: resource group required when filtering by name.") + if self.name: + self.results['ansible_facts']['azure_vmss'] = self.get_item() + else: + self.results['ansible_facts']['azure_vmss'] = self.list_items() + + return self.results + + def get_item(self): + """Get a single virtual machine scale set""" + + self.log('Get properties for {}'.format(self.name)) + + item = None + results = [] + + try: + item = self.compute_client.virtual_machine_scale_sets.get(self.resource_group, self.name) + except CloudError: + pass + + if item and self.has_tags(item.tags, self.tags): + results = [self.serialize_obj(item, AZURE_OBJECT_CLASS, enum_modules=AZURE_ENUM_MODULES)] + + return results + + def list_items(self): + """Get all virtual machine scale sets""" + + self.log('List all virtual machine scale sets') + + try: + response = self.compute_client.virtual_machine_scale_sets.list(self.resource_group) + except CloudError as exc: + self.fail('Failed to list all items - {}'.format(str(exc))) + + results = [] + for item in response: + if self.has_tags(item.tags, self.tags): + results.append(self.serialize_obj(item, AZURE_OBJECT_CLASS, enum_modules=AZURE_ENUM_MODULES)) + + return results + + +def main(): + """Main module execution code path""" + + AzureRMVirtualMachineScaleSetFacts() + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/azure_rm_virtualmachine_scaleset/aliases b/test/integration/targets/azure_rm_virtualmachine_scaleset/aliases new file mode 100644 index 00000000000..d6ff84111cd --- /dev/null +++ b/test/integration/targets/azure_rm_virtualmachine_scaleset/aliases @@ -0,0 +1,3 @@ +cloud/azure +posix/ci/cloud/azure +destructive diff --git a/test/integration/targets/azure_rm_virtualmachine_scaleset/meta/main.yml b/test/integration/targets/azure_rm_virtualmachine_scaleset/meta/main.yml new file mode 100644 index 00000000000..95e1952f989 --- /dev/null +++ b/test/integration/targets/azure_rm_virtualmachine_scaleset/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_azure diff --git a/test/integration/targets/azure_rm_virtualmachine_scaleset/tasks/main.yml b/test/integration/targets/azure_rm_virtualmachine_scaleset/tasks/main.yml new file mode 100644 index 00000000000..586cbdb7192 --- /dev/null +++ b/test/integration/targets/azure_rm_virtualmachine_scaleset/tasks/main.yml @@ -0,0 +1,88 @@ +- name: Create virtual network + azure_rm_virtualnetwork: + resource_group: "{{ resource_group }}" + name: testVnet + address_prefixes: "10.0.0.0/16" + +- name: Add subnet + azure_rm_subnet: + resource_group: "{{ resource_group }}" + name: testSubnet + address_prefix: "10.0.1.0/24" + virtual_network: testVnet + +- name: Create public IP address + azure_rm_publicipaddress: + resource_group: "{{ resource_group }}" + allocation_method: Dynamic + name: testPublicIP + +- name: Create VMSS + azure_rm_virtualmachine_scaleset: + resource_group: "{{ resource_group }}" + name: testVMSS + vm_size: Standard_DS1_v2 + admin_username: testuser + ssh_password_enabled: true + admin_password: "Password1234!" + capacity: 2 + virtual_network_name: testVnet + subnet_name: testSubnet + upgrade_policy: Manual + tier: Standard + managed_disk_type: Standard_LRS + os_disk_caching: ReadWrite + image: + offer: CoreOS + publisher: CoreOS + sku: Stable + version: latest + data_disks: + - lun: 0 + disk_size_gb: 64 + caching: ReadWrite + managed_disk_type: Standard_LRS + register: results + +- name: Assert that VMSS ran + assert: + that: results.changed + +- name: Delete VMSS + azure_rm_virtualmachine_scaleset: + resource_group: "{{ resource_group }}" + name: testVMSS + state: absent + remove_on_absent: ['all'] + vm_size: Standard_DS1_v2 + admin_username: testuser + capacity: 2 + virtual_network_name: testVnet + subnet_name: testSubnet + upgrade_policy: Manual + tier: Standard + os_disk_caching: ReadWrite + image: + offer: CoreOS + publisher: CoreOS + sku: Stable + version: latest + data_disks: + - lun: 0 + disk_size_gb: 64 + caching: ReadWrite + managed_disk_type: Standard_LRS + +- name: Delete public IP address + azure_rm_publicipaddress: + resource_group: "{{ resource_group }}" + allocation_method: Dynamic + state: absent + name: testPublicIP + +- name: Delete virtual network + azure_rm_virtualnetwork: + resource_group: "{{ resource_group }}" + name: testVnet + state: absent + address_prefixes: "10.0.0.0/16"