Repository: ansible-collections/community.vmware

Issue:
VMware Inventory Plugin does not select correct IP address on hosts with multiple NICs
<!--- 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
My goal is to have the VMware inventory plugin set "ansible_host" as a specific IP address that matches a RegEx expression. Our servers have multiple NICs on different subnets and only one of those subnets has connectivity with our Ansible Tower cluster. The default network interfaces do not connect with Tower, hence the need for this custom Jinja expression in the plugin. I have tried dozens of different expressions and none of them work, either in Tower or with CLI Ansible.

##### ISSUE TYPE
- Bug Report

##### COMPONENT NAME
VMware inventory plugin for Ansible

##### ANSIBLE VERSION

```
Ansible Tower:
Ansible 2.9.24   Ansible Tower 3.7.4

Ansible CLI:
ansible [core 2.11.2]
  config file = /export/home/<redacted>/vmware/ansible.cfg
  configured module search path = ['/export/home/<redacted>/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /export/home/<redacted>/vmware/.venv/lib64/python3.6/site-packages/ansible
  ansible collection location = /export/home/<redacted>/.ansible/collections:/usr/share/ansible/collections
  executable location = /export/home/<redacted>/vmware/.venv/bin/ansible
  python version = 3.6.8 (default, Mar 18 2021, 08:58:41) [GCC 8.4.1 20200928 (Red Hat 8.4.1-1)]
  jinja version = 3.0.1
  libyaml = True
```

##### COLLECTION VERSION
```
Bundled with Ansible Tower 3.7.4.
On CLI Ansible:
Collection                    Version
----------------------------- -------
community.vmware              1.10.0
```

##### CONFIGURATION
<!--- Paste verbatim output from "ansible-config dump --only-changed" between quotes -->
```
INVENTORY_ENABLED(/export/home/<redacted>/vmware/ansible.cfg) = ['vmware_vm_inventory']
```

##### OS / ENVIRONMENT
<!--- Provide all relevant information below, e.g. target OS versions, network device firmware, etc. -->
RHEL 8 control nodes. Targeted nodes are a blend of many different OSes in many different networks.

##### STEPS TO REPRODUCE
<!--- Describe exactly how to reproduce the problem, using a minimal test-case -->
First I tried what was described here: https://access.redhat.com/solutions/3701361
Then I started testing lots of other expressions, such as:
<!--- Paste example playbooks or commands between quotes below -->
```
---

plugin: vmware_vm_inventory
strict: False
hostname: <redacted>
username: <redacted>
password: <redacted>
validate_certs: False
with_tags: True
compose:
  ansible_host: "{{ guest.net[0]|select('ipAddress', 'contains', '10.177.') }}"
max_object_level: 5
filters:
- runtime.powerState == 'poweredOn'
- config.name is match('<redacted>')
properties:
- 'config.name'
- 'config.guestId'
- 'guest.ipAddress'
- 'summary.runtime.powerState'
- 'guest.net'
- 'config.uuid'
```
```
# Other ansible_host expressions tried include but are not limited to:
  ansible_host: "{{ (guest.net|select_chain_match('ipAddress', '^10.177.*|^10.183.*'))[0] }}"
  ansible_host: "{{ guest.net[0]|selectattr('ipAddress', 'contains', '10.177.') }}"
  ansible_host: "{{ (guest.net|selectattr('ipAddress', 'contains', '10.177.')|first) }}"
  ansible_host |
  "{% for item in guest.net %}
    {% if '10.177.' in item.ipAddress[0] or if '10.183.' in item.ipAddress[0] %}
      {{ item.ipAddress[0] }}
    {% endif %}
  {% endfor %}"
 # The following expression does return an IP address but it's not always the correct one. 
 ansible_host: "{{ guest.net[2].ipAddress[0] }}"
```

<!--- HINT: You can paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
"ansible_host" should be set to an IP address starting with either 10.177 or 10.183.


##### ACTUAL RESULTS
<!--- Describe what actually happened. If possible run with extra verbosity (-vvvv) -->
None of the dozens of expressions I've tried have returned an IP address value for "ansible_host", that matches what I need. 
This one:
```
ansible_host: "{{ guest.net[2].ipAddress[0] }}"
````
does return an IP address but it isn't selecting from the list of IP addresses the one that I need. Most other expressions return nothing for "ansible_host", indicating invalid Jinja or a bug.



Discussion:
Files identified in the description:
* [`lib/ansible/plugins/inventory`](https://github.com/['ansible-collections/amazon.aws', 'ansible-collections/community.aws', 'ansible-collections/community.vmware']/blob/main/lib/ansible/plugins/inventory)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
Hi @cscal!

Would this works for you?

```
- hosts: localhost
  vars:
    guest:
      net:
        - ipAddress: ['192.168.1.2']
        - ipAddress: ['10.177.45.3']
  tasks:
    - set_fact: 
        my_ansible_host: "{{ guest.net | selectattr('ipAddress')|map(attribute='ipAddress')|flatten|select('match', '^10.177.*|^10.183.*')|list|first }}"
    - debug: var=my_ansible_host
```
Yes, this worked, thank you! I feel like this must be a somewhat common use case in large datacenters, and should be added in the documentation somewhere.
@cscal would you like to open a PR to improve the documentation of the inventory plugin?
@cscal how you set compose?
Got it working by using @goneri suggestion, here is the part of my inventory configuration file that made it work : 
```
plugin: community.vmware.vmware_vm_inventory
compose:
  ansible_host: >-
    guest.net
    | selectattr('ipAddress')
    | map(attribute='ipAddress')
    | flatten
    | select('match', '^10.177.*|^10.183.*')
    | list
    | first
properties:
  - guest.net
```
@matletix Would you be interested in updating documentation? Let us know. 
@Akasurde ok I'll submit a PR
> @Akasurde ok I'll submit a PR

Thanks a lot. Let me know if you need any help.

Resolving patch:
diff --git a/plugins/inventory/vmware_vm_inventory.py b/plugins/inventory/vmware_vm_inventory.py
--- a/plugins/inventory/vmware_vm_inventory.py
+++ b/plugins/inventory/vmware_vm_inventory.py
@@ -340,6 +340,26 @@
       - 'guest.ipAddress'
       - 'guest.guestFamily'
       - 'guest.ipStack'
+
+# Select a specific IP address for use by ansible when multiple NICs are present on the VM
+    plugin: community.vmware.vmware_vm_inventory
+    strict: False
+    hostname: 10.65.223.31
+    username: administrator@vsphere.local
+    password: Esxi@123$%
+    validate_certs: False
+    compose:
+      # Set the IP address used by ansible to one that starts by 10.42. or 10.43.
+      ansible_host: >-
+        guest.net
+        | selectattr('ipAddress')
+        | map(attribute='ipAddress')
+        | flatten
+        | select('match', '^10.42.*|^10.43.*')
+        | list
+        | first
+    properties:
+      - guest.net
 '''
 
 from ansible.errors import AnsibleError, AnsibleParserError


Test patch: