Ansible subelements() filter where a subelement is an empty list

Viewed 810

Trying to use subelement() filter where sometimes the list will be empty. I would still like to get a list where item.0 is populated but maybe item.1 is Undefined or None. Not sure this is possible.

Sample Playbook

- hosts: localhost
  connection: local

  vars:
    result:
      virtual_machine:
        interfaces:
          - name: interface 1
            ip_addresses:
              - address: 192.168.1.22
                enabled: yes
          - name: interface 2
            ip_addresses: []

  tasks:
    - name: starting var
      debug: var=result

    - name: filter data
      set_fact:
        vm_interfaces: >
          {{ result.virtual_machine.interfaces
            | default({})
            | subelements('ip_addresses', skip_missing=True)
            | list }}
        
    - name: new filtered var
      debug: var=vm_interfaces

    - name: loop through new var
      debug:
        msg: '{{ item.0.name }} {{ item.1.address }}'
      loop: '{{ vm_interfaces }}'
      loop_control:
        label: '{{ item.0.name }}'

Sample Output

TASK [starting var] *************************************************************************************************
ok: [localhost] => {
    "result": {
        "virtual_machine": {
            "interfaces": [
                {
                    "ip_addresses": [
                        {
                            "address": "192.168.1.22",
                            "enabled": true
                        }
                    ],
                    "name": "interface 1"
                },
                {
                    "ip_addresses": [],
                    "name": "interface 2"
                }
            ]
        }
    }
}

TASK [filter data] **************************************************************************************************
ok: [localhost]

TASK [new filtered var] *********************************************************************************************
ok: [localhost] => {
    "vm_interfaces": [
        [
            {
                "ip_addresses": [
                    {
                        "address": "192.168.1.22",
                        "enabled": true
                    }
                ],
                "name": "interface 1"
            },
            {
                "address": "192.168.1.22",
                "enabled": true
            }
        ]
    ]
}

TASK [loop through new var] *****************************************************************************************
ok: [localhost] => (item=interface 1) => {
    "msg": "interface 1 192.168.1.22"

Current less Ansible-y Solution The way I am currently solving this problem now is to use nested Include_tasks essentially in (2) loops. This works, but my understanding is one should strive to manipulate the data to the point you can use loop.

1 Answers

Here's a possible way to do it. I'm simply replacing the empty ip list with a list containing an empty object. Adapt to your own needs.

- hosts: localhost
  gather_facts: false

  vars:
    result:
      virtual_machine:
        interfaces:
          - name: interface 1
            ip_addresses:
              - address: 192.168.1.22
                enabled: yes
          - name: interface 2
            ip_addresses: []

    replace_empty_interface:
      ip_addresses: [{}]

    vm_interfaces_empty: >
      {{
        result.virtual_machine.interfaces
        | default({})
        | selectattr('ip_addresses', 'defined')
        | selectattr('ip_addresses', '==', [])
        | map('combine', replace_empty_interface)
        | list
      }}

    vm_interfaces_filled: >
      {{
        result.virtual_machine.interfaces
        | default({})
        | selectattr('ip_addresses', 'defined')
        | rejectattr('ip_addresses', '==', [])
        | list
      }}

    vm_interfaces: >
      {{
        (vm_interfaces_empty + vm_interfaces_filled)
        | subelements('ip_addresses')
        | list
      }}

  tasks:

    - debug:
        msg: "{{ item.0 }} | {{ item.1 }}"
      loop: "{{ vm_interfaces }}"

Result:


TASK [debug] ***********************************************************************************************************************************************************************************
Sunday 09 May 2021  10:14:13 +0200 (0:00:00.088)       0:00:00.088 ************ 
ok: [localhost] => (item=[{'name': 'interface 2', 'ip_addresses': [{}]}, {}]) => {
    "msg": "{'name': 'interface 2', 'ip_addresses': [{}]} | {}"
}
ok: [localhost] => (item=[{'name': 'interface 1', 'ip_addresses': [{'address': '192.168.1.22', 'enabled': True}]}, {'address': '192.168.1.22', 'enabled': True}]) => {
    "msg": "{'name': 'interface 1', 'ip_addresses': [{'address': '192.168.1.22', 'enabled': True}]} | {'address': '192.168.1.22', 'enabled': True}"
}

PLAY RECAP *************************************************************************************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 
Related