From b89eb7a8c9e53470e817f6456113ace5fb2e1b27 Mon Sep 17 00:00:00 2001 From: Zim Kalinowski Date: Mon, 10 Dec 2018 16:21:09 +0800 Subject: [PATCH] Support for generalizing VMs (#49704) --- .../cloud/azure/azure_rm_virtualmachine.py | 32 ++++++++++++++++++- .../azure/azure_rm_virtualmachine_facts.py | 20 ++++++++---- .../tasks/virtualmachine.yml | 15 +++++++++ 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine.py b/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine.py index b1ba5fc6e73..876cac9478f 100644 --- a/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine.py +++ b/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine.py @@ -68,6 +68,12 @@ options: - Toggle that controls if the machine is allocated/deallocated, only useful with state='present'. default: True type: bool + generalized: + description: + - Use with state 'present' to generalize the machine. Set to true to generalize the machine. + - Please note that this operation is irreversible. + type: bool + version_added: "2.8" restarted: description: - Use with state 'present' to restart a running VM. @@ -496,7 +502,7 @@ EXAMPLES = ''' RETURN = ''' powerstate: - description: Indicates if the state is running, stopped, deallocated + description: Indicates if the state is running, stopped, deallocated, generalized returned: always type: string example: running @@ -666,6 +672,7 @@ import re try: from msrestazure.azure_exceptions import CloudError from msrestazure.tools import parse_resource_id + from msrest.polling import LROPoller except ImportError: # This is handled in azure_rm_common pass @@ -727,6 +734,7 @@ class AzureRMVirtualMachine(AzureRMModuleBase): allocated=dict(type='bool', default=True), restarted=dict(type='bool', default=False), started=dict(type='bool', default=True), + generalized=dict(type='bool', default=False), data_disks=dict(type='list'), plan=dict(type='dict'), accept_terms=dict(type='bool', default=False) @@ -765,6 +773,7 @@ class AzureRMVirtualMachine(AzureRMModuleBase): self.allocated = None self.restarted = None self.started = None + self.generalized = None self.differences = None self.data_disks = None self.plan = None @@ -954,6 +963,10 @@ class AzureRMVirtualMachine(AzureRMModuleBase): self.log("CHANGED: virtual machine {0} running and requested state 'stopped'".format(self.name)) changed = True powerstate_change = 'poweroff' + elif self.generalized and vm_dict['powerstate'] != 'generalized': + self.log("CHANGED: virtual machine {0} requested to be 'generalized'".format(self.name)) + changed = True + powerstate_change = 'generalized' self.differences = differences @@ -1298,6 +1311,9 @@ class AzureRMVirtualMachine(AzureRMModuleBase): elif powerstate_change == 'deallocated': self.deallocate_vm() + elif powerstate_change == 'generalized': + self.power_off_vm() + self.generalize_vm() self.results['ansible_facts']['azure_vm'] = self.serialize_vm(self.get_vm()) @@ -1343,6 +1359,9 @@ class AzureRMVirtualMachine(AzureRMModuleBase): if vm.instance_view: result['powerstate'] = next((s.code.replace('PowerState/', '') for s in vm.instance_view.statuses if s.code.startswith('PowerState')), None) + for s in vm.instance_view.statuses: + if s.code.lower() == "osstate/generalized": + result['powerstate'] = 'generalized' # Expand network interfaces to include config properties for interface in vm.network_profile.network_interfaces: @@ -1414,6 +1433,17 @@ class AzureRMVirtualMachine(AzureRMModuleBase): self.fail("Error deallocating virtual machine {0} - {1}".format(self.name, str(exc))) return True + def generalize_vm(self): + self.results['actions'].append("Generalize virtual machine {0}".format(self.name)) + self.log("Generalize virtual machine {0}".format(self.name)) + try: + response = self.compute_client.virtual_machines.generalize(self.resource_group, self.name) + if isinstance(response, LROPoller): + self.get_poller_result(response) + except Exception as exc: + self.fail("Error generalizing virtual machine {0} - {1}".format(self.name, str(exc))) + return True + def delete_vm(self, vm): vhd_uris = [] managed_disk_ids = [] diff --git a/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine_facts.py b/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine_facts.py index 6deb3060db5..bb649d56715 100644 --- a/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine_facts.py +++ b/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine_facts.py @@ -305,6 +305,9 @@ class AzureRMVirtualMachineFacts(AzureRMModuleBase): code = instance['statuses'][index]['code'].split('/') if code[0] == 'PowerState': power_state = code[1] + elif code[0] == 'OSState' and code[1] == 'generalized': + power_state = 'generalized' + break new_result = {} new_result['power_state'] = power_state @@ -317,12 +320,17 @@ class AzureRMVirtualMachineFacts(AzureRMModuleBase): new_result['admin_username'] = result['properties']['osProfile']['adminUsername'] image = result['properties']['storageProfile'].get('imageReference') if image is not None: - new_result['image'] = { - 'publisher': image['publisher'], - 'sku': image['sku'], - 'offer': image['offer'], - 'version': image['version'] - } + if image.get('publisher', None) is not None: + new_result['image'] = { + 'publisher': image['publisher'], + 'sku': image['sku'], + 'offer': image['offer'], + 'version': image['version'] + } + else: + new_result['image'] = { + 'id': image.get('id', None) + } vhd = result['properties']['storageProfile']['osDisk'].get('vhd') if vhd is not None: diff --git a/test/integration/targets/azure_rm_virtualmachine/tasks/virtualmachine.yml b/test/integration/targets/azure_rm_virtualmachine/tasks/virtualmachine.yml index f0f6fc9a940..a1776e4fd63 100644 --- a/test/integration/targets/azure_rm_virtualmachine/tasks/virtualmachine.yml +++ b/test/integration/targets/azure_rm_virtualmachine/tasks/virtualmachine.yml @@ -382,6 +382,21 @@ - assert: that: not output.changed +- name: Generalize VM + azure_rm_virtualmachine: + resource_group: "{{ resource_group }}" + name: "{{ vm_name2 }}" + generalized: yes + +- name: Gather facts and check if machine is generalized + azure_rm_virtualmachine_facts: + resource_group: "{{ resource_group }}" + name: "{{ vm_name2 }}" + register: generalized_output + +- assert: + that: generalized_output.vms[0].power_state == 'generalized' + - name: Delete dual NIC VM azure_rm_virtualmachine: resource_group: "{{ resource_group }}"