Repository: ansible/ansible-modules-extras

Issue:
Can't delete a VM with vmware_guest module
##### ISSUE TYPE
- Bug Report
##### COMPONENT NAME

vmware_guest
##### ANSIBLE VERSION

```
(pip)c7:ansible-demos$ ansible --version
ansible 2.2.0.0
  config file = /home/vagrant/src/ansible-demos/ansible.cfg
  configured module search path = Default w/o overrides

(pip)c7:ansible-demos$ rpm -q ansible
ansible-2.2.0.0-100.git201610182035.bce9bfc.HEAD.el7.centos.noarch
```
##### CONFIGURATION
##### OS / ENVIRONMENT

(pip)c7:ansible-demos$ cat /etc/centos-release
CentOS Linux release 7.1.1503 (Core) 
##### SUMMARY

Trying to delete a VM by setting 'state: absent' fails with vmware_guest module. First it fails if 'template' switch is not set, second it fails by trying to clone when 'template' is set. Also, if just `state: absent` is set before trying to power off, then nothing happens at all.
##### STEPS TO REPRODUCE

```
      - name: Shutdown VM first if delete required
        vmware_guest:
          validate_certs: False
          hostname: "{{ vcenter_hostname }}"
          username: "{{ vcenter_user }}"
          password: "{{ vcenter_pass }}"
          name: "{{ name }}"
          state: "poweredoff"
          template: ""
      when: state == "absent"

    - name: Set VM to desired state
      vmware_guest:
        validate_certs: False
        hostname: "{{ vcenter_hostname }}"
        username: "{{ vcenter_user }}"
        password: "{{ vcenter_pass }}"
        name: "{{ name }}"
        state: "{{ state }}"
        template: ""
```
##### EXPECTED RESULTS

VM powered off and deleted.
##### ACTUAL RESULTS

Nothing if just state: absent, failure if try to power off...

```
TASK [Shutdown VM first if delete required] ************************************
fatal: [linux]: FAILED! => {"changed": true, "failed": true, "msg": "The name 'ssp.box' already exists."}
```

Template missing...

```
TASK [Shutdown VM first if delete required] ************************************
fatal: [linux]: FAILED! => {"changed": false, "failed": true, "module_stderr": "/home/vagrant/pip/lib/python2.7/site-packages/requests/packages/urllib3/connectionpool.py:789: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.org/en/latest/security.html\n  InsecureRequestWarning)\nTraceback (most recent call last):\n  File \"/tmp/ansible_5ZzRF6/ansible_module_vmware_guest.py\", line 959, in <module>\n    main()\n  File \"/tmp/ansible_5ZzRF6/ansible_module_vmware_guest.py\", line 937, in main\n    wait_for_ip=module.params['wait_for_ip_address']\n  File \"/tmp/ansible_5ZzRF6/ansible_module_vmware_guest.py\", line 494, in deploy_template\n    template = get_obj(self.content, [vim.VirtualMachine], self.params['template'])\nKeyError: 'template'\n", "module_stdout": "", "msg": "MODULE FAILURE"}
```



Discussion:
<!-- @phips: Thanks for filing this bug report! -->

@jctanner ping, this issue is waiting for your response.
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)

<!--- boilerplate: issue_bug_new --->


Resolving patch:
diff --git a/cloud/vmware/vmware_guest.py b/cloud/vmware/vmware_guest.py
--- a/cloud/vmware/vmware_guest.py
+++ b/cloud/vmware/vmware_guest.py
@@ -281,6 +281,22 @@ def getfolders(self):
         self.folder_map = self._build_folder_map(self.folders)
         return (self.folders, self.folder_map)
 
+    def compile_folder_path_for_object(self, vobj):
+        ''' make a /vm/foo/bar/baz like folder path for an object '''
+        paths = []
+        if type(vobj) == vim.Folder:
+            paths.append(vobj.name)
+
+        thisobj = vobj
+        while hasattr(thisobj, 'parent'):
+            thisobj = thisobj.parent
+            if type(thisobj) == vim.Folder:
+                paths.append(thisobj.name)
+        paths.reverse()
+        if paths[0] == 'Datacenters':
+            paths.remove('Datacenters')
+        return '/' + '/'.join(paths)
+
     def get_datacenter(self):
         self.datacenter = get_obj(self.content, [vim.Datacenter], 
                                    self.params['datacenter'])
@@ -292,6 +308,7 @@ def getvm(self, name=None, uuid=None, folder=None, name_match=None):
 
         vm = None
         folder_path = None
+        searchpath = None
 
         if uuid:
             vm = self.content.searchIndex.FindByUuid(uuid=uuid, vmSearch=True)
@@ -302,7 +319,6 @@ def getvm(self, name=None, uuid=None, folder=None, name_match=None):
                 self.params['folder'] = self.params['folder'][0:-1]
 
             # Build the absolute folder path to pass into the search method
-            searchpath = None
             if self.params['folder'].startswith('/vm'):
                 searchpath = '%s' % self.params['datacenter']
                 searchpath += self.params['folder']
@@ -333,9 +349,24 @@ def getvm(self, name=None, uuid=None, folder=None, name_match=None):
                             vm = cObj
                             break
 
-        else:
+        if not vm:
+
             # FIXME - this is unused if folder has a default value
             vmList = get_all_objs(self.content, [vim.VirtualMachine])
+
+            # narrow down by folder
+            if folder:
+                if not self.folders:
+                    self.getfolders()
+
+                # compare the folder path of each VM against the search path
+                for item in vmList.items():
+                    vobj = item[0]
+                    if not type(vobj.parent) == vim.Folder:
+                        continue
+                    if self.compile_folder_path_for_object(vobj) == searchpath:
+                        return vobj
+
             if name_match:
                 if name_match == 'first':
                     vm = get_obj(self.content, [vim.VirtualMachine], name)
@@ -519,9 +550,18 @@ def deploy_template(self, poweron=False, wait_for_ip=False):
         # grab the folder vim object
         destfolder = folders[0][1]
 
-        # FIXME: cluster or hostsystem ... ?
-        #cluster = get_obj(self.content, [vim.ClusterComputeResource], self.params['esxi']['hostname'])
-        hostsystem = get_obj(self.content, [vim.HostSystem], self.params['esxi_hostname'])
+        # if the user wants a cluster, get the list of hosts for the cluster and use the first one
+        if self.params['cluster']:
+            cluster = get_obj(self.content, [vim.ClusterComputeResource], self.params['cluster'])
+            if not cluster:
+                self.module.fail_json(msg="Failed to find a cluster named %s" % self.params['cluster'])
+            #resource_pool = cluster.resourcePool
+            hostsystems = [x for x in cluster.host]
+            hostsystem = hostsystems[0]
+        else:
+            hostsystem = get_obj(self.content, [vim.HostSystem], self.params['esxi_hostname'])
+            if not hostsystem:
+                self.module.fail_json(msg="Failed to find a host named %s" % self.params['esxi_hostname'])
 
         # set the destination datastore in the relocation spec
         datastore_name = None
@@ -548,6 +588,10 @@ def deploy_template(self, poweron=False, wait_for_ip=False):
         resource_pool = None
         resource_pools = get_all_objs(self.content, [vim.ResourcePool])
         for rp in resource_pools.items():
+            if not rp[0]:
+                continue
+            if not hasattr(rp[0], 'parent'):
+                continue
             if rp[0].parent == hostsystem.parent:
                 resource_pool = rp[0]    
                 break
@@ -887,6 +931,7 @@ def main():
             force=dict(required=False, type='bool', default=False),
             datacenter=dict(required=False, type='str', default=None),
             esxi_hostname=dict(required=False, type='str', default=None),
+            cluster=dict(required=False, type='str', default=None),
             wait_for_ip_address=dict(required=False, type='bool', default=True)
         ),
         supports_check_mode=True,


Test patch: