mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Automatically removing all resources allocated by VM (#50652)
This commit is contained in:
parent
1a105a99dc
commit
be859a9f8e
2 changed files with 166 additions and 69 deletions
|
@ -276,9 +276,11 @@ options:
|
|||
- 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
|
||||
- "When removing a VM using state 'absent', also remove associated resources."
|
||||
- "It can be 'all' or 'all_autocreated' or a list with any of the following: ['network_interfaces', 'virtual_storage', 'public_ips']."
|
||||
- "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']
|
||||
plan:
|
||||
description:
|
||||
|
@ -497,15 +499,6 @@ EXAMPLES = '''
|
|||
name: testvm002
|
||||
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
|
||||
azure_rm_virtualmachine:
|
||||
resource_group: Testing
|
||||
|
@ -515,6 +508,13 @@ EXAMPLES = '''
|
|||
admin_password: password01
|
||||
image: customimage001
|
||||
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 = '''
|
||||
|
@ -1210,7 +1210,7 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
|
|||
"from the marketplace. - {2}").format(self.name, self.plan, str(exc)))
|
||||
|
||||
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:
|
||||
# Update the VM based on detected config differences
|
||||
|
@ -1342,7 +1342,7 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
|
|||
vm_resource.storage_profile.data_disks = data_disks
|
||||
|
||||
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
|
||||
if (powerstate_change == 'poweron' and
|
||||
|
@ -1421,7 +1421,6 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
|
|||
nic_dict = self.serialize_obj(nic, 'NetworkInterface')
|
||||
interface_dict['name'] = int_dict['networkInterfaces']
|
||||
interface_dict['properties'] = nic_dict['properties']
|
||||
|
||||
# Expand public IPs to include config properties
|
||||
for interface in result['properties']['networkProfile']['networkInterfaces']:
|
||||
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)))
|
||||
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):
|
||||
vhd_uris = []
|
||||
managed_disk_ids = []
|
||||
nic_names = []
|
||||
pip_names = []
|
||||
|
||||
if self.remove_on_absent.intersection(set(['all', 'virtual_storage'])):
|
||||
# store the attached vhd info so we can nuke it after the VM is gone
|
||||
if(vm.storage_profile.os_disk.managed_disk):
|
||||
self.log('Storing managed disk ID for deletion')
|
||||
managed_disk_ids.append(vm.storage_profile.os_disk.managed_disk.id)
|
||||
elif(vm.storage_profile.os_disk.vhd):
|
||||
self.log('Storing VHD URI for deletion')
|
||||
vhd_uris.append(vm.storage_profile.os_disk.vhd.uri)
|
||||
if 'all_autocreated' not in self.remove_on_absent:
|
||||
if self.remove_on_absent.intersection(set(['all', 'virtual_storage'])):
|
||||
# store the attached vhd info so we can nuke it after the VM is gone
|
||||
if(vm.storage_profile.os_disk.managed_disk):
|
||||
self.log('Storing managed disk ID for deletion')
|
||||
managed_disk_ids.append(vm.storage_profile.os_disk.managed_disk.id)
|
||||
elif(vm.storage_profile.os_disk.vhd):
|
||||
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:
|
||||
if data_disk is not None:
|
||||
if(data_disk.vhd):
|
||||
vhd_uris.append(data_disk.vhd.uri)
|
||||
elif(data_disk.managed_disk):
|
||||
managed_disk_ids.append(data_disk.managed_disk.id)
|
||||
data_disks = vm.storage_profile.data_disks
|
||||
for data_disk in data_disks:
|
||||
if data_disk is not None:
|
||||
if(data_disk.vhd):
|
||||
vhd_uris.append(data_disk.vhd.uri)
|
||||
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)))
|
||||
self.results['deleted_vhd_uris'] = vhd_uris
|
||||
self.log("Managed disk IDs to delete: {0}".format(', '.join(managed_disk_ids)))
|
||||
self.results['deleted_managed_disk_ids'] = managed_disk_ids
|
||||
# FUTURE enable diff mode, move these there...
|
||||
self.log("VHD URIs to delete: {0}".format(', '.join(vhd_uris)))
|
||||
self.results['deleted_vhd_uris'] = vhd_uris
|
||||
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
|
||||
self.log('Storing NIC names for deletion.')
|
||||
for interface in vm.network_profile.network_interfaces:
|
||||
id_dict = azure_id_to_dict(interface.id)
|
||||
nic_names.append(dict(name=id_dict['networkInterfaces'], resource_group=id_dict['resourceGroups']))
|
||||
self.log('NIC names to delete {0}'.format(str(nic_names)))
|
||||
self.results['deleted_network_interfaces'] = nic_names
|
||||
if self.remove_on_absent.intersection(set(['all', 'public_ips'])):
|
||||
# also store each nic's attached public IPs and delete after the NIC is gone
|
||||
for nic_dict in nic_names:
|
||||
nic = self.get_network_interface(nic_dict['resource_group'], nic_dict['name'])
|
||||
for ipc in nic.ip_configurations:
|
||||
if ipc.public_ip_address:
|
||||
pip_dict = azure_id_to_dict(ipc.public_ip_address.id)
|
||||
pip_names.append(dict(name=pip_dict['publicIPAddresses'], resource_group=pip_dict['resourceGroups']))
|
||||
self.log('Public IPs to delete are {0}'.format(str(pip_names)))
|
||||
self.results['deleted_public_ips'] = pip_names
|
||||
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
|
||||
self.log('Storing NIC names for deletion.')
|
||||
for interface in vm.network_profile.network_interfaces:
|
||||
id_dict = azure_id_to_dict(interface.id)
|
||||
nic_names.append(dict(name=id_dict['networkInterfaces'], resource_group=id_dict['resourceGroups']))
|
||||
self.log('NIC names to delete {0}'.format(str(nic_names)))
|
||||
self.results['deleted_network_interfaces'] = nic_names
|
||||
if self.remove_on_absent.intersection(set(['all', 'public_ips'])):
|
||||
# also store each nic's attached public IPs and delete after the NIC is gone
|
||||
for nic_dict in nic_names:
|
||||
nic = self.get_network_interface(nic_dict['resource_group'], nic_dict['name'])
|
||||
for ipc in nic.ip_configurations:
|
||||
if ipc.public_ip_address:
|
||||
pip_dict = azure_id_to_dict(ipc.public_ip_address.id)
|
||||
pip_names.append(dict(name=pip_dict['publicIPAddresses'], resource_group=pip_dict['resourceGroups']))
|
||||
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.results['actions'].append("Deleted virtual machine {0}".format(self.name))
|
||||
|
@ -1550,23 +1565,27 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
|
|||
except Exception as 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
|
||||
# TODO: best-effort to keep deleting other linked resources if we encounter an error
|
||||
if self.remove_on_absent.intersection(set(['all', 'virtual_storage'])):
|
||||
self.log('Deleting VHDs')
|
||||
self.delete_vm_storage(vhd_uris)
|
||||
self.log('Deleting managed disks')
|
||||
self.delete_managed_disks(managed_disk_ids)
|
||||
if 'all_autocreated' in self.remove_on_absent:
|
||||
self.remove_autocreated_resources(vm.tags)
|
||||
else:
|
||||
# TODO: parallelize nic, vhd, and public ip deletions with begin_deleting
|
||||
# TODO: best-effort to keep deleting other linked resources if we encounter an error
|
||||
if self.remove_on_absent.intersection(set(['all', 'virtual_storage'])):
|
||||
self.log('Deleting VHDs')
|
||||
self.delete_vm_storage(vhd_uris)
|
||||
self.log('Deleting managed disks')
|
||||
self.delete_managed_disks(managed_disk_ids)
|
||||
|
||||
if self.remove_on_absent.intersection(set(['all', 'network_interfaces'])):
|
||||
self.log('Deleting network interfaces')
|
||||
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')
|
||||
for nic_dict in nic_names:
|
||||
self.delete_nic(nic_dict['resource_group'], nic_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'])
|
||||
|
||||
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
|
||||
|
||||
def get_network_interface(self, resource_group, name):
|
||||
|
@ -1575,6 +1594,7 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
|
|||
return nic
|
||||
except Exception as exc:
|
||||
self.fail("Error fetching network interface {0} - {1}".format(name, str(exc)))
|
||||
return True
|
||||
|
||||
def delete_nic(self, resource_group, 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.
|
||||
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):
|
||||
for mdi in managed_disk_ids:
|
||||
try:
|
||||
|
@ -1604,6 +1633,16 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
|
|||
self.get_poller_result(poller)
|
||||
except Exception as 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):
|
||||
# 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)
|
||||
except Exception as exc:
|
||||
self.fail("Error deleting blob {0}:{1} - {2}".format(container_name, blob_name, str(exc)))
|
||||
return True
|
||||
|
||||
def get_marketplace_image_version(self):
|
||||
try:
|
||||
|
@ -1681,11 +1721,13 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
|
|||
except Exception as 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:
|
||||
poller = self.compute_client.virtual_machines.create_or_update(self.resource_group, self.name, params)
|
||||
self.get_poller_result(poller)
|
||||
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)))
|
||||
|
||||
def vm_size_is_valid(self):
|
||||
|
@ -1712,6 +1754,8 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
|
|||
'''
|
||||
account = None
|
||||
valid_name = False
|
||||
if self.tags is None:
|
||||
self.tags = {}
|
||||
|
||||
# Attempt to find a valid storage account name
|
||||
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)
|
||||
except Exception as 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)
|
||||
|
||||
def check_storage_account_name(self, name):
|
||||
|
@ -1769,6 +1814,8 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
|
|||
|
||||
network_interface_name = self.name + '01'
|
||||
nic = None
|
||||
if self.tags is None:
|
||||
self.tags = {}
|
||||
|
||||
self.log("Create default NIC {0}".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
|
||||
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)
|
||||
self.tags['_own_pip_'] = 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,
|
||||
self.open_ports)
|
||||
self.tags['_own_nsg_'] = self.name + '01'
|
||||
|
||||
parameters = self.network_models.NetworkInterface(
|
||||
location=self.location,
|
||||
|
@ -1877,6 +1926,7 @@ class AzureRMVirtualMachine(AzureRMModuleBase):
|
|||
network_interface_name,
|
||||
parameters)
|
||||
new_nic = self.get_poller_result(poller)
|
||||
self.tags['_own_nic_'] = network_interface_name
|
||||
except Exception as exc:
|
||||
self.fail("Error creating network interface {0} - {1}".format(network_interface_name, str(exc)))
|
||||
return new_nic
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
storage_account: "{{ resource_group | hash('md5') | truncate(24, True, '') }}"
|
||||
vm_name1: "vm1{{ 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_name2: "avbs2{{ resource_group | hash('md5') | truncate(3, True, '') }}"
|
||||
|
||||
|
@ -500,3 +501,49 @@
|
|||
|
||||
#- assert:
|
||||
#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…
Reference in a new issue