Nested loops in ansible with subelements filter

Long story short : you can't do nested loops in ansible.  However, there is a simple work-around if you can live with a nested loop of 1 level deep.  Now first let's think of a scenario why one would want a nested loop.  

A common use of nested loops is generally something like this.  Let's take a real example.  (let's not debate whether this is the right approach, but let's just go with the example to cover nested loops)

I have several esxi-servers and I want to loop every datastore in every esxi.

esxi_list:
  - name: esxi1
    ip: 10.0.0.1
    datastores:
      - name: datastore1
        path: /datastore1
      - name: datastore2
        path: /datastore2
  - name: esxi2
    ip: 10.0.0.2
    datastores:
      - name: datastore3
        path: /datastore3
      - name: datastore4
        path: /datastore4    

The problem here is, that with a normal loop, I can either loop the esxi servers (esxi_list) or one of the datastorelists (esxi_list[0].datastores), but not both.  Now in this case the one list is part of the other and we can merge a parent and a child list with the filter "subelements".

"{{ esxi_list | subelements('datastores' }}"

The jinja example will create a cartesian product which we can use like this

---
- name: This is a filter example
  hosts: localhost
  vars:
    esxi_list:
      - name: esxi1
        ip: 10.0.0.1
        datastores:
          - name: datastore1
            path: /datastore1
          - name: datastore2
            path: /datastore2
      - name: esxi2
        ip: 10.0.0.2
        datastores:
          - name: datastore3
            path: /datastore3
          - name: datastore4
            path: /datastore4
  tasks:
    - name: "print"
      debug:
        msg: "Esxi has ip : {{ item.0.ip }} - datastore has path : {{ item.1.path }}"
      loop: "{{ esxi_list | subelements('datastores') }}"
      loop_control:
        label: "Looping datastore {{ item.1.name }} of esxi host {{ item.0.name }} "

the output :



TASK [print] ******************************************************************************************************************************
ok: [localhost] => (item=Looping datastore datastore1 of esxi host esxi1 ) => {
    "msg": "Esxi has ip : 10.0.0.1 - datastore has path : /datastore1"
}
ok: [localhost] => (item=Looping datastore datastore2 of esxi host esxi1 ) => {
    "msg": "Esxi has ip : 10.0.0.1 - datastore has path : /datastore2"
}
ok: [localhost] => (item=Looping datastore datastore3 of esxi host esxi2 ) => {
    "msg": "Esxi has ip : 10.0.0.2 - datastore has path : /datastore3"
}
ok: [localhost] => (item=Looping datastore datastore4 of esxi host esxi2 ) => {
    "msg": "Esxi has ip : 10.0.0.2 - datastore has path : /datastore4"
}

Now you can see that a single loop is resulting in double loop of all datastores AND all esxi hosts.  We can grab item.0 and item.1 to get either the esxi data or the datastore data.

Note that you can only use subelements once.

If you want to have a 3 or multilevel product of 3 or more lists, we can use a custom filter for that.  Which I will cover in my next post.


Post a Comment

0 Comments