Repository: ansible-collections/community.vmware

Issue:
Folders search collision in vcenter_folder module
<!--- Verify first that your issue is not already reported on GitHub -->
<!--- Also test if the latest release and devel branch are affected too -->
<!--- Complete *all* sections as described, this form is processed automatically -->

##### SUMMARY
Hi, thanks for great collection.

I have a collision problem when i try to delete folder via `vcenter_folder` module. The collision causes deletion of wrong folder.


I think the problem in recoursive behavior in get_folder function:
https://github.com/ansible-collections/community.vmware/blob/401f9e1e634dc55f48c0f72436b684d3bd21d65e/plugins/modules/vcenter_folder.py#L276

##### ISSUE TYPE
- Bug Report

##### COMPONENT NAME
vcenter_folder module

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes -->
```paste below
4.5.0
```

##### COLLECTION VERSION
<!--- Paste verbatim output from "ansible-galaxy collection list <namespace>.<collection>"  between the quotes
for example: ansible-galaxy collection list community.general
-->
```paste below
1.15.0
```
latest version is still affected.

##### OS / ENVIRONMENT
Ubuntu 20.04


##### STEPS TO REPRODUCE
I have folders structure:
`foo/bar/collision_folder/`

and params for `vcenter_folder` module:
```yaml
folder_name: "collision_folder"
parent_folder: "foo"
folder_type: "vm"
```

##### EXPECTED RESULTS
The execution returns `ok` and 3rd level `collision_folder` is still present


##### ACTUAL RESULTS
The execution returns `changed` and deletes 3rd level `collision_folder`,




Discussion:

Resolving patch:
diff --git a/plugins/modules/vcenter_folder.py b/plugins/modules/vcenter_folder.py
--- a/plugins/modules/vcenter_folder.py
+++ b/plugins/modules/vcenter_folder.py
@@ -167,7 +167,6 @@ def ensure(self):
         Manage internal state management
         """
         state = self.module.params.get('state')
-        datacenter_name = self.module.params.get('datacenter')
         folder_type = self.module.params.get('folder_type')
         folder_name = self.module.params.get('folder_name')
         parent_folder = self.module.params.get('parent_folder', None)
@@ -180,15 +179,13 @@ def ensure(self):
                     parent_folder_parts = parent_folder.strip('/').split('/')
                     p_folder_obj = None
                     for part in parent_folder_parts:
-                        part_folder_obj = self.get_folder(datacenter_name=datacenter_name,
-                                                          folder_name=part,
+                        part_folder_obj = self.get_folder(folder_name=part,
                                                           folder_type=folder_type,
                                                           parent_folder=p_folder_obj)
                         if not part_folder_obj:
                             self.module.fail_json(msg="Could not find folder %s" % part)
                         p_folder_obj = part_folder_obj
-                    child_folder_obj = self.get_folder(datacenter_name=datacenter_name,
-                                                       folder_name=folder_name,
+                    child_folder_obj = self.get_folder(folder_name=folder_name,
                                                        folder_type=folder_type,
                                                        parent_folder=p_folder_obj)
                     if child_folder_obj:
@@ -196,16 +193,14 @@ def ensure(self):
                                             " parent folder %s" % (folder_name, parent_folder)
                         self.module.exit_json(**results)
                 else:
-                    p_folder_obj = self.get_folder(datacenter_name=datacenter_name,
-                                                   folder_name=parent_folder,
+                    p_folder_obj = self.get_folder(folder_name=parent_folder,
                                                    folder_type=folder_type)
 
                     if not p_folder_obj:
                         self.module.fail_json(msg="Parent folder %s does not exist" % parent_folder)
 
                     # Check if folder exists under parent folder
-                    child_folder_obj = self.get_folder(datacenter_name=datacenter_name,
-                                                       folder_name=folder_name,
+                    child_folder_obj = self.get_folder(folder_name=folder_name,
                                                        folder_type=folder_type,
                                                        parent_folder=p_folder_obj)
                     if child_folder_obj:
@@ -214,9 +209,9 @@ def ensure(self):
                                             " parent folder %s" % (folder_name, parent_folder)
                         self.module.exit_json(**results)
             else:
-                folder_obj = self.get_folder(datacenter_name=datacenter_name,
-                                             folder_name=folder_name,
-                                             folder_type=folder_type)
+                folder_obj = self.get_folder(folder_name=folder_name,
+                                             folder_type=folder_type,
+                                             recurse=True)
 
                 if folder_obj:
                     results['result']['path'] = self.get_folder_path(folder_obj)
@@ -266,34 +261,30 @@ def ensure(self):
                     parent_folder_parts = parent_folder.strip('/').split('/')
                     p_folder_obj = None
                     for part in parent_folder_parts:
-                        part_folder_obj = self.get_folder(datacenter_name=datacenter_name,
-                                                          folder_name=part,
+                        part_folder_obj = self.get_folder(folder_name=part,
                                                           folder_type=folder_type,
                                                           parent_folder=p_folder_obj)
                         if not part_folder_obj:
                             self.module.fail_json(msg="Could not find folder %s" % part)
                         p_folder_obj = part_folder_obj
-                    folder_obj = self.get_folder(datacenter_name=datacenter_name,
-                                                 folder_name=folder_name,
+                    folder_obj = self.get_folder(folder_name=folder_name,
                                                  folder_type=folder_type,
                                                  parent_folder=p_folder_obj)
                 else:
-                    p_folder_obj = self.get_folder(datacenter_name=datacenter_name,
-                                                   folder_name=parent_folder,
+                    p_folder_obj = self.get_folder(folder_name=parent_folder,
                                                    folder_type=folder_type)
 
                     if not p_folder_obj:
                         self.module.fail_json(msg="Parent folder %s does not exist" % parent_folder)
 
                     # Check if folder exists under parent folder
-                    folder_obj = self.get_folder(datacenter_name=datacenter_name,
-                                                 folder_name=folder_name,
+                    folder_obj = self.get_folder(folder_name=folder_name,
                                                  folder_type=folder_type,
                                                  parent_folder=p_folder_obj)
             else:
-                folder_obj = self.get_folder(datacenter_name=datacenter_name,
-                                             folder_name=folder_name,
-                                             folder_type=folder_type)
+                folder_obj = self.get_folder(folder_name=folder_name,
+                                             folder_type=folder_type,
+                                             recurse=True)
             if folder_obj:
                 try:
                     if parent_folder:
@@ -328,23 +319,18 @@ def ensure(self):
                                               " exception %s " % to_native(gen_exec))
             self.module.exit_json(**results)
 
-    def get_folder(self, datacenter_name, folder_name, folder_type, parent_folder=None):
+    def get_folder(self, folder_name, folder_type, parent_folder=None, recurse=False):
         """
         Get managed object of folder by name
         Returns: Managed object of folder by name
 
         """
-        folder_objs = get_all_objs(self.content, [vim.Folder], parent_folder)
+        parent_folder = parent_folder or self.datacenter_folder_type[folder_type]
+
+        folder_objs = get_all_objs(self.content, [vim.Folder], parent_folder, recurse=recurse)
         for folder in folder_objs:
-            if parent_folder:
-                if folder.name == folder_name and \
-                   self.datacenter_folder_type[folder_type].childType == folder.childType:
-                    return folder
-            else:
-                if folder.name == folder_name and \
-                   self.datacenter_folder_type[folder_type].childType == folder.childType and \
-                   folder.parent.parent.name == datacenter_name:    # e.g. folder.parent.parent.name == /DC01/host/folder
-                    return folder
+            if folder.name == folder_name:
+                return folder
 
         return None
 


Test patch:
diff --git a/tests/integration/targets/vcenter_folder/tasks/main.yml b/tests/integration/targets/vcenter_folder/tasks/main.yml
--- a/tests/integration/targets/vcenter_folder/tasks/main.yml
+++ b/tests/integration/targets/vcenter_folder/tasks/main.yml
@@ -214,3 +214,6 @@
     assert:
       that:
           - not all_folder_results.changed
+
+  - block:
+    - include_tasks: regression_folder_collision.yml
diff --git a/tests/integration/targets/vcenter_folder/tasks/regression_folder_collision.yml b/tests/integration/targets/vcenter_folder/tasks/regression_folder_collision.yml
new file mode 100644
--- /dev/null
+++ b/tests/integration/targets/vcenter_folder/tasks/regression_folder_collision.yml
@@ -0,0 +1,135 @@
+# Test code for the vcenter_folder module.
+# Copyright: (c) 2021, Victor Dvornikov <spacefuryphonk@gmail.com>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Create first lvl vm folder
+  vcenter_folder:
+    hostname: "{{ vcenter_hostname }}"
+    username: "{{ vcenter_username }}"
+    password: "{{ vcenter_password }}"
+    validate_certs: false
+    datacenter: "{{ dc1 }}"
+    folder_name: "foo"
+    folder_type: "vm"
+    state: present
+  register: creation_result
+
+- debug:
+    msg: "{{ creation_result }}"
+
+- name: Create second lvl vm folder
+  vcenter_folder:
+    hostname: "{{ vcenter_hostname }}"
+    username: "{{ vcenter_username }}"
+    password: "{{ vcenter_password }}"
+    validate_certs: false
+    datacenter: "{{ dc1 }}"
+    folder_name: "bar"
+    parent_folder: "foo"
+    folder_type: "vm"
+    state: present
+  register: creation_result
+
+- debug:
+    msg: "{{ creation_result }}"
+
+- name: Create collision third lvl vm folder
+  vcenter_folder:
+    hostname: "{{ vcenter_hostname }}"
+    username: "{{ vcenter_username }}"
+    password: "{{ vcenter_password }}"
+    validate_certs: false
+    datacenter: "{{ dc1 }}"
+    folder_name: "collision_folder"
+    parent_folder: "foo/bar"
+    folder_type: "vm"
+    state: present
+  register: creation_result
+
+- debug:
+    msg: "{{ creation_result }}"
+
+- name: Delete missed second lvl vm folder using name collision
+  vcenter_folder:
+    hostname: "{{ vcenter_hostname }}"
+    username: "{{ vcenter_username }}"
+    password: "{{ vcenter_password }}"
+    validate_certs: false
+    datacenter: "{{ dc1 }}"
+    folder_name: "collision_folder"
+    parent_folder: "foo"
+    folder_type: "vm"
+    state: absent
+  register: deletion_result
+
+- debug:
+    msg: "{{ deletion_result }}"
+
+- name: Assert collision folder wasn't deleted
+  assert:
+    that:
+        - not deletion_result.changed
+
+- name: Check present collision folder
+  vcenter_folder:
+    hostname: "{{ vcenter_hostname }}"
+    username: "{{ vcenter_username }}"
+    password: "{{ vcenter_password }}"
+    validate_certs: false
+    datacenter: "{{ dc1 }}"
+    folder_name: "collision_folder"
+    parent_folder: "foo/bar"
+    folder_type: "vm"
+    state: present
+  register: check_present_result
+  check_mode: true
+
+- debug:
+    msg: "{{ check_present_result }}"
+
+- name: Assert collision folder is still present
+  assert:
+    that:
+        - not deletion_result.changed
+
+- name: Delete third lvl folder first time
+  vcenter_folder:
+    hostname: "{{ vcenter_hostname }}"
+    username: "{{ vcenter_username }}"
+    password: "{{ vcenter_password }}"
+    validate_certs: false
+    datacenter: "{{ dc1 }}"
+    folder_name: "collision_folder"
+    parent_folder: "foo/bar"
+    folder_type: "vm"
+    state: absent
+  register: deletion_result
+
+- debug:
+    msg: "{{ deletion_result }}"
+
+- name: Assert third lvl folder has been successfully deleted
+  assert:
+    that:
+        - deletion_result.changed
+
+- name: Delete third lvl folder again
+  vcenter_folder:
+    hostname: "{{ vcenter_hostname }}"
+    username: "{{ vcenter_username }}"
+    password: "{{ vcenter_password }}"
+    validate_certs: false
+    datacenter: "{{ dc1 }}"
+    folder_name: "collision_folder"
+    parent_folder: "foo/bar"
+    folder_type: "vm"
+    state: absent
+  register: deletion_result
+
+- debug:
+    msg: "{{ deletion_result }}"
+
+- name: Assert third lvl folder has already been deleted earlier
+  assert:
+    that:
+        - not deletion_result.changed