Repository: ansible-collections/community.vmware

Issue:
vmware_guest_custom_attributes module crashes when trying to set a VirtualMachine attribute with the same name as an existing HostSystem attribute
<!--- 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
When running a task with `vmware_guest_custom_attributes`, if the name of any attribute already exists as a HostSystem attribute, the module will crash with an unhandled exception.

```
pyVmomi.VmomiSupport.InvalidArgument: (vmodl.fault.InvalidArgument) {
   dynamicType = <unset>,
   dynamicProperty = (vmodl.DynamicProperty) [],
   msg = 'A specified parameter was not correct: entity',
   faultCause = <unset>,
   faultMessage = (vmodl.LocalizableMessage) [],
   invalidProperty = u'entity'
}
```

The crash is due to the module finding the HostSystem attribute and trying to do a `self.content.customFieldsManager.SetField(entity=vm, key=field_key.key, value=field_value)` with the key of the wrong type of attribute.

The issue happens because in the line https://github.com/ansible-collections/community.vmware/blob/a92ccb0a07cc833e22b13cb838d0696b16ebf64d/plugins/modules/vmware_guest_custom_attributes.py#L202 there is no explicit filtering for VirtualMachine custom attributes. If the cycle's first match is a HostSystem attribute, the function will return the wrong type.

This would work if the `check_exists` function were something like:

```
    def check_exists(self, field):
        for x in self.custom_field_mgr:
            if x.name == field and x.managedObjectType == vim.VirtualMachine:
                return x
        return False
```

##### ISSUE TYPE
- Bug Report

##### COMPONENT NAME
vmware_guest_custom_attributes

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes -->
```
ansible 2.9.13
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/user1/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /home/user1/virtualenvs/ansible-2.9/lib/python2.7/site-packages/ansible
  executable location = /home/user1/virtualenvs/ansible-2.9/bin/ansible
  python version = 2.7.5 (default, Aug  7 2019, 00:51:29) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)]

```

##### CONFIGURATION
<!--- Paste verbatim output from "ansible-config dump --only-changed" between quotes -->
```paste below
ANSIBLE_SSH_ARGS(/home/user1/ansible.aso/ansible.cfg) =
ANSIBLE_SSH_RETRIES(/home/user1/ansible.aso/ansible.cfg) = 2
CACHE_PLUGIN(/home/user1/ansible.aso/ansible.cfg) = jsonfile
CACHE_PLUGIN_CONNECTION(/home/user1/ansible.aso/ansible.cfg) = inventory/.cache
CACHE_PLUGIN_TIMEOUT(/home/user1/ansible.aso/ansible.cfg) = 86400
DEFAULT_GATHERING(/home/user1/ansible.aso/ansible.cfg) = smart
DEFAULT_LOG_PATH(/home/user1/ansible.aso/ansible.cfg) = /home/user1/ansible.aso/ansible.log
DEFAULT_MANAGED_STR(/home/user1/ansible.aso/ansible.cfg) = Managed by Ansible - DO NOT MODIFY
DEFAULT_TIMEOUT(/home/user1/ansible.aso/ansible.cfg) = 30
HOST_KEY_CHECKING(/home/user1/ansible.aso/ansible.cfg) = False
INVENTORY_CACHE_ENABLED(/home/user1/ansible.aso/ansible.cfg) = True
INVENTORY_CACHE_PLUGIN(/home/user1/ansible.aso/ansible.cfg) = jsonfile
INVENTORY_CACHE_PLUGIN_CONNECTION(/home/user1/ansible.aso/ansible.cfg) = inventory/.cache
```

##### OS / ENVIRONMENT
<!--- Provide all relevant information below, e.g. target OS versions, network device firmware, etc. -->
`CentOS Linux release 7.6.1810 (Core)`

##### STEPS TO REPRODUCE
<!--- Describe exactly how to reproduce the problem, using a minimal test-case -->

With the following playbook:

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: "Write VM custom attributes"
  hosts: all
  gather_facts: false

  tasks:
  - name: Add virtual machine custom attributes
    vmware_guest_custom_attributes:
      hostname: "{{ vm_vcenter_host | default(lookup('env', 'VMWARE_HOST')) }}"
      username: "{{ vm_vcenter_user | default(lookup('env', 'VMWARE_USER')) }}"
      password: "{{ vm_vcenter_pass | default(lookup('env', 'VMWARE_PASSWORD')) }}"
      name: "{{ inventory_hostname }}"
      validate_certs: no
      state: present
      attributes:
        - name: "Department"
          value: "{{ custom_attribute_department | default('undefined')  }}"
    delegate_to: localhost
    register: attributes

```
vcenter has the following Custom Attributes:

```
(vim.CustomFieldsManager.FieldDef) {
   dynamicType = <unset>,
   dynamicProperty = (vmodl.DynamicProperty) [],
   key = 630,
   name = 'Department',
   type = str,
   managedObjectType = vim.HostSystem,
   fieldDefPrivileges = <unset>,
   fieldInstancePrivileges = <unset>
}

(vim.CustomFieldsManager.FieldDef) {
   dynamicType = <unset>,
   dynamicProperty = (vmodl.DynamicProperty) [],
   key = 1044,
   name = 'Department',
   type = str,
   managedObjectType = vim.VirtualMachine,
   fieldDefPrivileges = <unset>,
   fieldInstancePrivileges = <unset>
}
```

and run as:

`ansible-playbook -i inventory/vm_inventory_testvm.ini playbook_vcenter_custom_annotations.yml -l testvm02  -D --flush-cache -vvv`

<!--- HINT: You can paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- Describe what you expected to happen when running the steps above -->

Should create / update the VM custom attribute


##### ACTUAL RESULTS
<!--- Describe what actually happened. If possible run with extra verbosity (-vvvv) -->

Crashes with exception:

<!--- Paste verbatim command output between quotes -->
```paste below
pyVmomi.VmomiSupport.InvalidArgument: (vmodl.fault.InvalidArgument) {
   dynamicType = <unset>,
   dynamicProperty = (vmodl.DynamicProperty) [],
   msg = 'A specified parameter was not correct: entity',
   faultCause = <unset>,
   faultMessage = (vmodl.LocalizableMessage) [],
   invalidProperty = u'entity'
}
```



Discussion:
Now _that's_ a great issue. Thank you very much for giving us so many details! This should make it much easier for us to fix this. Thanks :-)

Resolving patch:
diff --git a/plugins/modules/vmware_guest_custom_attributes.py b/plugins/modules/vmware_guest_custom_attributes.py
--- a/plugins/modules/vmware_guest_custom_attributes.py
+++ b/plugins/modules/vmware_guest_custom_attributes.py
@@ -199,7 +199,8 @@ def set_custom_field(self, vm, user_fields):
 
     def check_exists(self, field):
         for x in self.custom_field_mgr:
-            if x.name == field:
+            # The custom attribute should be either global (managedObjectType == None) or VM specific
+            if x.managedObjectType in (None, vim.VirtualMachine) and x.name == field:
                 return x
         return False
 


Test patch: