# Buggy code
def delete_vm(self, vm):
    vhd_uris = []
    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
        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:
            vhd_uris.append(data_disk.vhd.uri)

        self.log("VHD URIs to delete: {0}".format(", ".join(vhd_uris)))
        self.results["deleted_vhd_uris"] = vhd_uris

    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(id_dict["networkInterfaces"])
        self.log("NIC names to delete {0}".format(", ".join(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 name in nic_names:
                nic = self.get_network_interface(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(pip_dict["publicIPAddresses"])
            self.log("Public IPs to  delete are {0}".format(", ".join(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))
    try:
        poller = self.compute_client.virtual_machines.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 {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 virtual storage")
        self.delete_vm_storage(vhd_uris)

    if self.remove_on_absent.intersection(set(["all", "network_interfaces"])):
        self.log("Deleting network interfaces")
        for name in nic_names:
            self.delete_nic(name)

    if self.remove_on_absent.intersection(set(["all", "public_ips"])):
        self.log("Deleting public IPs")
        for name in pip_names:
            self.delete_pip(name)
    return True


# Traceback (AttributeError)
TASK [Clean up VM] **************************************************************************************************************************
task path: /home/elaurijssens/voyager-image-factory/playbooks/azure/vm_delete.yml:19
Using module file /usr/lib/python2.7/site-packages/ansible-2.4.0-py2.7.egg/ansible/modules/cloud/azure/azure_rm_virtualmachine.py
<localhost> ESTABLISH LOCAL CONNECTION FOR USER: root
<localhost> EXEC /bin/sh -c 'echo ~ &amp;&amp; sleep 0'
<localhost> EXEC /bin/sh -c '( umask 77 &amp;&amp; mkdir -p "` echo /root/.ansible/tmp/ansible-tmp-1505157460.96-218311556974775 `" &amp;&amp; echo ansible-tmp-1505157460.96-218311556974775="` echo /root/.ansible/tmp/ansible-tmp-1505157460.96-218311556974775 `" ) &amp;&amp; sleep 0'
<localhost> PUT /tmp/tmpb9uhEf TO /root/.ansible/tmp/ansible-tmp-1505157460.96-218311556974775/azure_rm_virtualmachine.py
<localhost> EXEC /bin/sh -c 'chmod u+x /root/.ansible/tmp/ansible-tmp-1505157460.96-218311556974775/ /root/.ansible/tmp/ansible-tmp-1505157460.96-218311556974775/azure_rm_virtualmachine.py &amp;&amp; sleep 0'
<localhost> EXEC /bin/sh -c '/usr/bin/python /root/.ansible/tmp/ansible-tmp-1505157460.96-218311556974775/azure_rm_virtualmachine.py; rm -rf "/root/.ansible/tmp/ansible-tmp-1505157460.96-218311556974775/" > /dev/null 2>&amp;1 &amp;&amp; sleep 0'
The full traceback is:
Traceback (most recent call last):
File "/tmp/ansible_QSCsG6/ansible_module_azure_rm_virtualmachine.py", line 1524, in <module>
main()
File "/tmp/ansible_QSCsG6/ansible_module_azure_rm_virtualmachine.py", line 1521, in main
AzureRMVirtualMachine()
File "/tmp/ansible_QSCsG6/ansible_module_azure_rm_virtualmachine.py", line 652, in __init__
supports_check_mode=True)
File "/tmp/ansible_QSCsG6/ansible_modlib.zip/ansible/module_utils/azure_rm_common.py", line 255, in __init__
File "/tmp/ansible_QSCsG6/ansible_module_azure_rm_virtualmachine.py", line 1074, in exec_module
self.delete_vm(vm)
File "/tmp/ansible_QSCsG6/ansible_module_azure_rm_virtualmachine.py", line 1191, in delete_vm
vhd_uris.append(vm.storage_profile.os_disk.vhd.uri)
AttributeError: 'NoneType' object has no attribute 'uri'

fatal: [localhost]: FAILED! => {
"changed": false,
"failed": true,
"module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_QSCsG6/ansible_module_azure_rm_virtualmachine.py\", line 1524, in <module>\n    main()\n  File \"/tmp/ansible_QSCsG6/ansible_module_azure_rm_virtualmachine.py\", line 1521, in main\n    AzureRMVirtualMachine()\n  File \"/tmp/ansible_QSCsG6/ansible_module_azure_rm_virtualmachine.py\", line 652, in __init__\n    supports_check_mode=True)\n  File \"/tmp/ansible_QSCsG6/ansible_modlib.zip/ansible/module_utils/azure_rm_common.py\", line 255, in __init__\n  File \"/tmp/ansible_QSCsG6/ansible_module_azure_rm_virtualmachine.py\", line 1074, in exec_module\n    self.delete_vm(vm)\n  File \"/tmp/ansible_QSCsG6/ansible_module_azure_rm_virtualmachine.py\", line 1191, in delete_vm\n    vhd_uris.append(vm.storage_profile.os_disk.vhd.uri)\nAttributeError: 'NoneType' object has no attribute 'uri'\n",
"module_stdout": "",
"msg": "MODULE FAILURE",
"rc": 0
}

# Fixed code
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)

        data_disks = vm.storage_profile.data_disks
        for data_disk in data_disks:
            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

    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(id_dict["networkInterfaces"])
        self.log("NIC names to delete {0}".format(", ".join(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 name in nic_names:
                nic = self.get_network_interface(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(pip_dict["publicIPAddresses"])
            self.log("Public IPs to  delete are {0}".format(", ".join(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))
    try:
        poller = self.compute_client.virtual_machines.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 {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 self.remove_on_absent.intersection(set(["all", "network_interfaces"])):
        self.log("Deleting network interfaces")
        for name in nic_names:
            self.delete_nic(name)

    if self.remove_on_absent.intersection(set(["all", "public_ips"])):
        self.log("Deleting public IPs")
        for name in pip_names:
            self.delete_pip(name)
    return True