loopsansiblenestednested-listsansible-2.x

Loop through subelements of subelements


I am using the following data structure in Ansible:

datacenters:
  - name: Datacenter1
    clusters:
      - name: ClusterA
        hosts:
          - 192.168.0.1
          - 192.168.0.2
      - name: ClusterB
        hosts:
          - 192.168.1.1
          - 192.168.1.2
  - name: Datacenter2
    clusters:
      - name: ClusterC
        hosts:
          - 192.168.2.1
          - 192.168.2.2

In a task, I want to iterate over each host while having access to the data of all the parent layers. If there is only one nesting level, it can easily be done with the subelements filter:

loop: '{{ datacenters | subelements(''clusters'') }}'

This will give me access to the data like this:

'Datacenter: {{ item.0.name }}, Cluster: {{ item.1.name }}'

I was hoping to be able to extend this concept like this:

loop: '{{ datacenters | subelements(''clusters'') | subelements(''hosts'') }}'

And being able to access the data like this:

'Datacenter: {{ item.0.name }}, Cluster: {{ item.1.name }}, Host: {{ item.2 }}'

But that does not work and I get the following error message instead:

Unexpected templating type error occurred on ({{ datacenters | subelements('clusters') | subelements('hosts') }}): the key hosts should point to a dictionary, got ...(the result of the first layer)

I found this question, which solves a similar problem, but relies on having distinct dict keys on all nesting levels, which I don't, because datacenters and clusters have the same name key.

So, how can I iterate over subelements of subelements in Ansible?


Solution

  • A bit far-fetched but the following playbook will achieve your goal:

     ---
     - hosts: localhost
       gather_facts: false
     
       vars:
         datacenters:
         - name: Datacenter1
           clusters:
             - name: ClusterA
               hosts:
                 - 192.168.0.1
                 - 192.168.0.2
             - name: ClusterB
               hosts:
                 - 192.168.1.1
                 - 192.168.1.2
         - name: Datacenter2
           clusters:
             - name: ClusterC
               hosts:
                 - 192.168.2.1
                 - 192.168.2.2
     
         # Get the list of datacenters
         _dcs: "{{ datacenters | map(attribute='name') }}"
         # Get the corresponding list of clusters with subelements on hosts
         _clusters: "{{ datacenters | map(attribute='clusters') | map('subelements', 'hosts') }}"
         # Recreate a list with the sublisted hosts per clusters and create subelements on that final result
         _overall: "{{ dict(_dcs | zip(_clusters)) | dict2items(key_name='datacenter', value_name='clusters') | subelements('clusters') }}"
     
       tasks:
         - name: Loop on the result
           debug:
             msg:
               - "DC: {{ item.0.datacenter }}"
               - "Cluster: {{ item.1.0.name }}"
               - "Host: {{ item.1.1 }}"
           loop: "{{ _overall }}"
           loop_control:
             label: "{{ item.0.datacenter }} - {{ item.1.0.name }}"
    

    This gives:

    PLAY [localhost] **************************************************************************************************************************************************
    
    TASK [Loop on the result] *****************************************************************************************************************************************
    ok: [localhost] => (item=Datacenter1 - ClusterA) => {
        "msg": [
            "DC: Datacenter1",
            "Cluster: ClusterA",
            "Host: 192.168.0.1"
        ]
    }
    ok: [localhost] => (item=Datacenter1 - ClusterA) => {
        "msg": [
            "DC: Datacenter1",
            "Cluster: ClusterA",
            "Host: 192.168.0.2"
        ]
    }
    ok: [localhost] => (item=Datacenter1 - ClusterB) => {
        "msg": [
            "DC: Datacenter1",
            "Cluster: ClusterB",
            "Host: 192.168.1.1"
        ]
    }
    ok: [localhost] => (item=Datacenter1 - ClusterB) => {
        "msg": [
            "DC: Datacenter1",
            "Cluster: ClusterB",
            "Host: 192.168.1.2"
        ]
    }
    ok: [localhost] => (item=Datacenter2 - ClusterC) => {
        "msg": [
            "DC: Datacenter2",
            "Cluster: ClusterC",
            "Host: 192.168.2.1"
        ]
    }
    ok: [localhost] => (item=Datacenter2 - ClusterC) => {
        "msg": [
            "DC: Datacenter2",
            "Cluster: ClusterC",
            "Host: 192.168.2.2"
        ]
    }
    
    PLAY RECAP ********************************************************************************************************************************************************
    localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0