Automatically removing all resources allocated by VM (#50652)

pull/51375/head
Zim Kalinowski 6 years ago committed by GitHub
parent 1a105a99dc
commit be859a9f8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -276,9 +276,11 @@ options:
- subnet - subnet
remove_on_absent: remove_on_absent:
description: description:
- When removing a VM using state 'absent', also remove associated resources - "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']" - "It can be 'all' or 'all_autocreated' or a list with any of the following: ['network_interfaces', 'virtual_storage', 'public_ips']."
- Any other input will be ignored - "To remove all resources referred by VM use 'all'."
- "To remove all resources that were automatically created while provisioning VM use 'all_autocreated'."
- Any other input will be ignored.
default: ['all'] default: ['all']
plan: plan:
description: description:
@ -497,15 +499,6 @@ EXAMPLES = '''
name: testvm002 name: testvm002
restarted: yes restarted: yes
- name: remove vm and all resources except public ips
azure_rm_virtualmachine:
resource_group: Testing
name: testvm002
state: absent
remove_on_absent:
- network_interfaces
- virtual_storage
- name: Create a VM with an Availability Zone - name: Create a VM with an Availability Zone
azure_rm_virtualmachine: azure_rm_virtualmachine:
resource_group: Testing resource_group: Testing
@ -515,6 +508,13 @@ EXAMPLES = '''
admin_password: password01 admin_password: password01
image: customimage001 image: customimage001
zones: [1] zones: [1]
- name: Remove a VM and all resources that were autocreated
azure_rm_virtualmachine:
resource_group: Testing
name: testvm002
remove_on_absent: all_autocreated
state: absent
''' '''
RETURN = ''' RETURN = '''
@ -1210,7 +1210,7 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
"from the marketplace. - {2}").format(self.name, self.plan, str(exc))) "from the marketplace. - {2}").format(self.name, self.plan, str(exc)))
self.log("Create virtual machine with parameters:") self.log("Create virtual machine with parameters:")
self.create_or_update_vm(vm_resource) self.create_or_update_vm(vm_resource, 'all_autocreated' in self.remove_on_absent)
elif self.differences and len(self.differences) > 0: elif self.differences and len(self.differences) > 0:
# Update the VM based on detected config differences # Update the VM based on detected config differences
@ -1342,7 +1342,7 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
vm_resource.storage_profile.data_disks = data_disks vm_resource.storage_profile.data_disks = data_disks
self.log("Update virtual machine with parameters:") self.log("Update virtual machine with parameters:")
self.create_or_update_vm(vm_resource) self.create_or_update_vm(vm_resource, False)
# Make sure we leave the machine in requested power state # Make sure we leave the machine in requested power state
if (powerstate_change == 'poweron' and if (powerstate_change == 'poweron' and
@ -1421,7 +1421,6 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
nic_dict = self.serialize_obj(nic, 'NetworkInterface') nic_dict = self.serialize_obj(nic, 'NetworkInterface')
interface_dict['name'] = int_dict['networkInterfaces'] interface_dict['name'] = int_dict['networkInterfaces']
interface_dict['properties'] = nic_dict['properties'] interface_dict['properties'] = nic_dict['properties']
# Expand public IPs to include config properties # Expand public IPs to include config properties
for interface in result['properties']['networkProfile']['networkInterfaces']: for interface in result['properties']['networkProfile']['networkInterfaces']:
for config in interface['properties']['ipConfigurations']: for config in interface['properties']['ipConfigurations']:
@ -1493,53 +1492,69 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
self.fail("Error generalizing virtual machine {0} - {1}".format(self.name, str(exc))) self.fail("Error generalizing virtual machine {0} - {1}".format(self.name, str(exc)))
return True return True
def remove_autocreated_resources(self, tags):
if tags:
sa_name = tags.get('_own_sa_')
nic_name = tags.get('_own_nic_')
pip_name = tags.get('_own_pip_')
nsg_name = tags.get('_own_nsg_')
if sa_name:
self.delete_storage_account(self.resource_group, sa_name)
if nic_name:
self.delete_nic(self.resource_group, nic_name)
if pip_name:
self.delete_pip(self.resource_group, pip_name)
if nsg_name:
self.delete_nsg(self.resource_group, nsg_name)
def delete_vm(self, vm): def delete_vm(self, vm):
vhd_uris = [] vhd_uris = []
managed_disk_ids = [] managed_disk_ids = []
nic_names = [] nic_names = []
pip_names = [] pip_names = []
if self.remove_on_absent.intersection(set(['all', 'virtual_storage'])): if 'all_autocreated' not in self.remove_on_absent:
# store the attached vhd info so we can nuke it after the VM is gone if self.remove_on_absent.intersection(set(['all', 'virtual_storage'])):
if(vm.storage_profile.os_disk.managed_disk): # store the attached vhd info so we can nuke it after the VM is gone
self.log('Storing managed disk ID for deletion') if(vm.storage_profile.os_disk.managed_disk):
managed_disk_ids.append(vm.storage_profile.os_disk.managed_disk.id) self.log('Storing managed disk ID for deletion')
elif(vm.storage_profile.os_disk.vhd): managed_disk_ids.append(vm.storage_profile.os_disk.managed_disk.id)
self.log('Storing VHD URI for deletion') elif(vm.storage_profile.os_disk.vhd):
vhd_uris.append(vm.storage_profile.os_disk.vhd.uri) self.log('Storing VHD URI for deletion')
vhd_uris.append(vm.storage_profile.os_disk.vhd.uri)
data_disks = vm.storage_profile.data_disks
for data_disk in data_disks: data_disks = vm.storage_profile.data_disks
if data_disk is not None: for data_disk in data_disks:
if(data_disk.vhd): if data_disk is not None:
vhd_uris.append(data_disk.vhd.uri) if(data_disk.vhd):
elif(data_disk.managed_disk): vhd_uris.append(data_disk.vhd.uri)
managed_disk_ids.append(data_disk.managed_disk.id) elif(data_disk.managed_disk):
managed_disk_ids.append(data_disk.managed_disk.id)
# FUTURE enable diff mode, move these there...
self.log("VHD URIs to delete: {0}".format(', '.join(vhd_uris))) # FUTURE enable diff mode, move these there...
self.results['deleted_vhd_uris'] = vhd_uris self.log("VHD URIs to delete: {0}".format(', '.join(vhd_uris)))
self.log("Managed disk IDs to delete: {0}".format(', '.join(managed_disk_ids))) self.results['deleted_vhd_uris'] = vhd_uris
self.results['deleted_managed_disk_ids'] = managed_disk_ids self.log("Managed disk IDs to delete: {0}".format(', '.join(managed_disk_ids)))
self.results['deleted_managed_disk_ids'] = managed_disk_ids
if self.remove_on_absent.intersection(set(['all', 'network_interfaces'])):
# store the attached nic info so we can nuke them after the VM is gone if self.remove_on_absent.intersection(set(['all', 'network_interfaces'])):
self.log('Storing NIC names for deletion.') # store the attached nic info so we can nuke them after the VM is gone
for interface in vm.network_profile.network_interfaces: self.log('Storing NIC names for deletion.')
id_dict = azure_id_to_dict(interface.id) for interface in vm.network_profile.network_interfaces:
nic_names.append(dict(name=id_dict['networkInterfaces'], resource_group=id_dict['resourceGroups'])) id_dict = azure_id_to_dict(interface.id)
self.log('NIC names to delete {0}'.format(str(nic_names))) nic_names.append(dict(name=id_dict['networkInterfaces'], resource_group=id_dict['resourceGroups']))
self.results['deleted_network_interfaces'] = nic_names self.log('NIC names to delete {0}'.format(str(nic_names)))
if self.remove_on_absent.intersection(set(['all', 'public_ips'])): self.results['deleted_network_interfaces'] = nic_names
# also store each nic's attached public IPs and delete after the NIC is gone if self.remove_on_absent.intersection(set(['all', 'public_ips'])):
for nic_dict in nic_names: # also store each nic's attached public IPs and delete after the NIC is gone
nic = self.get_network_interface(nic_dict['resource_group'], nic_dict['name']) for nic_dict in nic_names:
for ipc in nic.ip_configurations: nic = self.get_network_interface(nic_dict['resource_group'], nic_dict['name'])
if ipc.public_ip_address: for ipc in nic.ip_configurations:
pip_dict = azure_id_to_dict(ipc.public_ip_address.id) if ipc.public_ip_address:
pip_names.append(dict(name=pip_dict['publicIPAddresses'], resource_group=pip_dict['resourceGroups'])) pip_dict = azure_id_to_dict(ipc.public_ip_address.id)
self.log('Public IPs to delete are {0}'.format(str(pip_names))) pip_names.append(dict(name=pip_dict['publicIPAddresses'], resource_group=pip_dict['resourceGroups']))
self.results['deleted_public_ips'] = pip_names self.log('Public IPs to delete are {0}'.format(str(pip_names)))
self.results['deleted_public_ips'] = pip_names
self.log("Deleting virtual machine {0}".format(self.name)) self.log("Deleting virtual machine {0}".format(self.name))
self.results['actions'].append("Deleted virtual machine {0}".format(self.name)) self.results['actions'].append("Deleted virtual machine {0}".format(self.name))
@ -1550,23 +1565,27 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
except Exception as exc: except Exception as exc:
self.fail("Error deleting virtual machine {0} - {1}".format(self.name, str(exc))) self.fail("Error deleting virtual machine {0} - {1}".format(self.name, str(exc)))
# TODO: parallelize nic, vhd, and public ip deletions with begin_deleting if 'all_autocreated' in self.remove_on_absent:
# TODO: best-effort to keep deleting other linked resources if we encounter an error self.remove_autocreated_resources(vm.tags)
if self.remove_on_absent.intersection(set(['all', 'virtual_storage'])): else:
self.log('Deleting VHDs') # TODO: parallelize nic, vhd, and public ip deletions with begin_deleting
self.delete_vm_storage(vhd_uris) # TODO: best-effort to keep deleting other linked resources if we encounter an error
self.log('Deleting managed disks') if self.remove_on_absent.intersection(set(['all', 'virtual_storage'])):
self.delete_managed_disks(managed_disk_ids) self.log('Deleting VHDs')
self.delete_vm_storage(vhd_uris)
if self.remove_on_absent.intersection(set(['all', 'network_interfaces'])): self.log('Deleting managed disks')
self.log('Deleting network interfaces') self.delete_managed_disks(managed_disk_ids)
for nic_dict in nic_names:
self.delete_nic(nic_dict['resource_group'], nic_dict['name']) if self.remove_on_absent.intersection(set(['all', 'network_interfaces'])):
self.log('Deleting network interfaces')
if self.remove_on_absent.intersection(set(['all', 'public_ips'])): for nic_dict in nic_names:
self.log('Deleting public IPs') self.delete_nic(nic_dict['resource_group'], nic_dict['name'])
for pip_dict in pip_names:
self.delete_pip(pip_dict['resource_group'], pip_dict['name']) if self.remove_on_absent.intersection(set(['all', 'public_ips'])):
self.log('Deleting public IPs')
for pip_dict in pip_names:
self.delete_pip(pip_dict['resource_group'], pip_dict['name'])
return True return True
def get_network_interface(self, resource_group, name): def get_network_interface(self, resource_group, name):
@ -1575,6 +1594,7 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
return nic return nic
except Exception as exc: except Exception as exc:
self.fail("Error fetching network interface {0} - {1}".format(name, str(exc))) self.fail("Error fetching network interface {0} - {1}".format(name, str(exc)))
return True
def delete_nic(self, resource_group, name): def delete_nic(self, resource_group, name):
self.log("Deleting network interface {0}".format(name)) self.log("Deleting network interface {0}".format(name))
@ -1597,6 +1617,15 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
# Delete returns nada. If we get here, assume that all is well. # Delete returns nada. If we get here, assume that all is well.
return True return True
def delete_nsg(self, resource_group, name):
self.results['actions'].append("Deleted NSG {0}".format(name))
try:
poller = self.network_client.network_security_groups.delete(resource_group, name)
self.get_poller_result(poller)
except Exception as exc:
self.fail("Error deleting {0} - {1}".format(name, str(exc)))
return True
def delete_managed_disks(self, managed_disk_ids): def delete_managed_disks(self, managed_disk_ids):
for mdi in managed_disk_ids: for mdi in managed_disk_ids:
try: try:
@ -1604,6 +1633,16 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
self.get_poller_result(poller) self.get_poller_result(poller)
except Exception as exc: except Exception as exc:
self.fail("Error deleting managed disk {0} - {1}".format(mdi, str(exc))) self.fail("Error deleting managed disk {0} - {1}".format(mdi, str(exc)))
return True
def delete_storage_account(self, resource_group, name):
self.log("Delete storage account {0}".format(name))
self.results['actions'].append("Deleted storage account {0}".format(name))
try:
self.storage_client.storage_accounts.delete(self.resource_group, name)
except Exception as exc:
self.fail("Error deleting storage account {0} - {2}".format(name, str(exc)))
return True
def delete_vm_storage(self, vhd_uris): def delete_vm_storage(self, vhd_uris):
# FUTURE: figure out a cloud_env indepdendent way to delete these # FUTURE: figure out a cloud_env indepdendent way to delete these
@ -1625,6 +1664,7 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
blob_client.delete_blob(container_name, blob_name) blob_client.delete_blob(container_name, blob_name)
except Exception as exc: except Exception as exc:
self.fail("Error deleting blob {0}:{1} - {2}".format(container_name, blob_name, str(exc))) self.fail("Error deleting blob {0}:{1} - {2}".format(container_name, blob_name, str(exc)))
return True
def get_marketplace_image_version(self): def get_marketplace_image_version(self):
try: try:
@ -1681,11 +1721,13 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
except Exception as exc: except Exception as exc:
self.fail("Error fetching storage account {0} - {1}".format(name, str(exc))) self.fail("Error fetching storage account {0} - {1}".format(name, str(exc)))
def create_or_update_vm(self, params): def create_or_update_vm(self, params, remove_autocreated_on_failure):
try: try:
poller = self.compute_client.virtual_machines.create_or_update(self.resource_group, self.name, params) poller = self.compute_client.virtual_machines.create_or_update(self.resource_group, self.name, params)
self.get_poller_result(poller) self.get_poller_result(poller)
except Exception as exc: except Exception as exc:
if remove_autocreated_on_failure:
self.remove_autocreated_resources(params.tags)
self.fail("Error creating or updating virtual machine {0} - {1}".format(self.name, str(exc))) self.fail("Error creating or updating virtual machine {0} - {1}".format(self.name, str(exc)))
def vm_size_is_valid(self): def vm_size_is_valid(self):
@ -1712,6 +1754,8 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
''' '''
account = None account = None
valid_name = False valid_name = False
if self.tags is None:
self.tags = {}
# Attempt to find a valid storage account name # Attempt to find a valid storage account name
storage_account_name_base = re.sub('[^a-zA-Z0-9]', '', self.name[:20].lower()) storage_account_name_base = re.sub('[^a-zA-Z0-9]', '', self.name[:20].lower())
@ -1746,6 +1790,7 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
self.get_poller_result(poller) self.get_poller_result(poller)
except Exception as exc: except Exception as exc:
self.fail("Failed to create storage account: {0} - {1}".format(storage_account_name, str(exc))) self.fail("Failed to create storage account: {0} - {1}".format(storage_account_name, str(exc)))
self.tags['_own_sa_'] = storage_account_name
return self.get_storage_account(storage_account_name) return self.get_storage_account(storage_account_name)
def check_storage_account_name(self, name): def check_storage_account_name(self, name):
@ -1769,6 +1814,8 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
network_interface_name = self.name + '01' network_interface_name = self.name + '01'
nic = None nic = None
if self.tags is None:
self.tags = {}
self.log("Create default NIC {0}".format(network_interface_name)) self.log("Create default NIC {0}".format(network_interface_name))
self.log("Check to see if NIC {0} exists".format(network_interface_name)) self.log("Check to see if NIC {0} exists".format(network_interface_name))
@ -1849,10 +1896,12 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
sku = self.network_models.PublicIPAddressSku(name="Standard") if self.zones else None sku = self.network_models.PublicIPAddressSku(name="Standard") if self.zones else None
pip_info = self.create_default_pip(self.resource_group, self.location, self.name + '01', self.public_ip_allocation_method, sku=sku) pip_info = self.create_default_pip(self.resource_group, self.location, self.name + '01', self.public_ip_allocation_method, sku=sku)
pip = self.network_models.PublicIPAddress(id=pip_info.id, location=pip_info.location, resource_guid=pip_info.resource_guid, sku=sku) pip = self.network_models.PublicIPAddress(id=pip_info.id, location=pip_info.location, resource_guid=pip_info.resource_guid, sku=sku)
self.tags['_own_pip_'] = self.name + '01'
self.results['actions'].append('Created default security group {0}'.format(self.name + '01')) self.results['actions'].append('Created default security group {0}'.format(self.name + '01'))
group = self.create_default_securitygroup(self.resource_group, self.location, self.name + '01', self.os_type, group = self.create_default_securitygroup(self.resource_group, self.location, self.name + '01', self.os_type,
self.open_ports) self.open_ports)
self.tags['_own_nsg_'] = self.name + '01'
parameters = self.network_models.NetworkInterface( parameters = self.network_models.NetworkInterface(
location=self.location, location=self.location,
@ -1877,6 +1926,7 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
network_interface_name, network_interface_name,
parameters) parameters)
new_nic = self.get_poller_result(poller) new_nic = self.get_poller_result(poller)
self.tags['_own_nic_'] = network_interface_name
except Exception as exc: except Exception as exc:
self.fail("Error creating network interface {0} - {1}".format(network_interface_name, str(exc))) self.fail("Error creating network interface {0} - {1}".format(network_interface_name, str(exc)))
return new_nic return new_nic

@ -3,6 +3,7 @@
storage_account: "{{ resource_group | hash('md5') | truncate(24, True, '') }}" storage_account: "{{ resource_group | hash('md5') | truncate(24, True, '') }}"
vm_name1: "vm1{{ resource_group | hash('md5') | truncate(5, True, '') }}" vm_name1: "vm1{{ resource_group | hash('md5') | truncate(5, True, '') }}"
vm_name2: "vm2{{ resource_group | hash('md5') | truncate(5, True, '') }}" vm_name2: "vm2{{ resource_group | hash('md5') | truncate(5, True, '') }}"
vm_name3: "vm3{{ resource_group | hash('md5') | truncate(5, True, '') }}"
abs_name1: "avbs1{{ resource_group | hash('md5') | truncate(3, True, '') }}" abs_name1: "avbs1{{ resource_group | hash('md5') | truncate(3, True, '') }}"
abs_name2: "avbs2{{ resource_group | hash('md5') | truncate(3, True, '') }}" abs_name2: "avbs2{{ resource_group | hash('md5') | truncate(3, True, '') }}"
@ -500,3 +501,49 @@
#- assert: #- assert:
#that: not output.changed #that: not output.changed
- name: Create minimal VM with defaults
azure_rm_virtualmachine:
resource_group: "{{ resource_group }}"
name: "{{ vm_name3 }}"
admin_username: "testuser"
admin_password: "Pass123$$$abx!"
vm_size: Standard_B1ms
image:
offer: UbuntuServer
publisher: Canonical
sku: 16.04-LTS
version: latest
register: vm_output
- name: Delete VM
azure_rm_virtualmachine:
resource_group: "{{ resource_group }}"
name: "{{ vm_name3 }}"
remove_on_absent: all_autocreated
state: absent
- name: Query NIC
azure_rm_networkinterface_facts:
resource_group: "{{ resource_group }}"
name: "{{ vm_name3 }}01"
register: output_nic
- name: Query NSG
azure_rm_securitygroup_facts:
resource_group: "{{ resource_group }}"
name: "{{ vm_name3 }}01"
register: output_nsg
- name: Query PIP
azure_rm_publicipaddress_facts:
resource_group: "{{ resource_group }}"
name: "{{ vm_name3 }}01"
register: output_pip
- name: Assert that autocreated resources were deleted
assert:
that:
- output_nic.ansible_facts.azure_networkinterfaces | length == 0
- output_nsg.ansible_facts.azure_securitygroups | length == 0
- output_pip.ansible_facts.azure_publicipaddresses | length == 0

Loading…
Cancel
Save