Search code examples
ansiblejinja2nested-loops

Ansible Dictionary Nested For Loop


I've tried using a dictionary structure like this before but have only ever gotten it to work in a template. I need to loop through all subnets/subnet_cidrs of first-level keys in servers that don't match inventory_hostname.

---
- name: Test Playbook
  hosts: localhost
  gather_facts: no

  vars:
    servers: 
      alpha.lan:
        eth0:
          subnet: 192.168.0.0
          subnet_mask: 255.255.255.0
          subnet_cidr: 24
        eth1:
          subnet: 192.168.1.0
          subnet_mask: 255.255.255.0
          subnet_cidr: 24
      bravo.lan:
        eth0:
          subnet: 172.16.0.0
          subnet_mask: 255.255.252.0
          subnet_cidr: 22
        eth1:
          subnet: 172.16.4.0
          subnet_mask: 255.255.252.0
          subnet_cidr: 22

  tasks:
    - debug:
        msg: "{{something['subnet']}}/{{something['subnet_cidr']}}"
      loop: "{{servers...}}"

So if this playbook was run on alpha.lan I would get

"msg": "172.16.0.0/22"
"msg": "172.16.4.0/22"

This is how I got it to work in a template, able to use values from both item and item2 in the final output:

{% for key,item in servers.items() if key != inventory_hostname %}
{% for key2,item2 in item.items() %}
{{item2['subnet']}}/{{item2['subnet_cidr']}}
{% endfor %}
{% endfor %}

I'd like to be able to use or make tests off of each third-level key (subnet,subnet_mask,subnet_cidr) independently in firewall commands. Thanks in advance for any help.

Some of the information sources I've tried using:
https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html
https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html
https://jinja.palletsprojects.com/en/2.11.x/templates/#list-of-builtin-filters


Solution

  • Given the dictionary servers the playbook

    - hosts: alpha.lan,bravo.lan
      tasks:
        - debug:
            msg: "{{ item.value.subnet }}/{{ item.value.subnet_cidr }}"
          loop: "{{ servers[host]|dict2items }}"
          loop_control:
            label: "{{ item.key }}"
          vars:
            host: "{{ servers|difference([inventory_hostname])|first }}"
    

    gives (abridged)

    TASK [debug] ****
    ok: [alpha.lan] => (item=eth0) => 
      msg: 172.16.0.0/22
    ok: [alpha.lan] => (item=eth1) => 
      msg: 172.16.4.0/22
    ok: [bravo.lan] => (item=eth0) => 
      msg: 192.168.0.0/24
    ok: [bravo.lan] => (item=eth1) => 
      msg: 192.168.1.0/24
    

    Q: "List of first-level keys, ... iterate through servers ... while iterating through the second-level keys for each of those as well"

    Given the dictionary

    servers: 
      alpha.lan:
        eth0:
          subnet: 192.168.0.0
          subnet_cidr: 24
        eth1:
          subnet: 192.168.1.0
          subnet_cidr: 24
      bravo.lan:
        eth0:
          subnet: 172.16.0.0
          subnet_cidr: 22
        eth1:
          subnet: 172.16.4.0
          subnet_cidr: 22
      charlie.lan:
        eth0:
          subnet: 172.17.0.0
          subnet_cidr: 22
        eth1:
          subnet: 172.17.4.0
          subnet_cidr: 22
    

    and the task

    shell> cat loop-net.yml
    - debug:
        msg: "inventory: {{ inventory_hostname }}
              server: {{ outer_item }}
              net: {{ item.value.subnet }}/{{ item.value.subnet_cidr }}"
      loop: "{{ servers[outer_item]|dict2items }}"
      loop_control:
        label: "{{ item.key }}"
    

    The playbook below

    shell> cat pb.yml
    - hosts: alpha.lan,bravo.lan,charlie.lan
      tasks:
        - include_tasks: loop-net.yml
          loop: "{{ servers.keys()|difference([inventory_hostname]) }}"
          loop_control:
            loop_var: outer_item
    

    gives

    shell> ansible-playbook pb.yml | grep msg | sort
      msg: 'inventory: alpha.lan server: bravo.lan net: 172.16.0.0/22'
      msg: 'inventory: alpha.lan server: bravo.lan net: 172.16.4.0/22'
      msg: 'inventory: alpha.lan server: charlie.lan net: 172.17.0.0/22'
      msg: 'inventory: alpha.lan server: charlie.lan net: 172.17.4.0/22'
      msg: 'inventory: bravo.lan server: alpha.lan net: 192.168.0.0/24'
      msg: 'inventory: bravo.lan server: alpha.lan net: 192.168.1.0/24'
      msg: 'inventory: bravo.lan server: charlie.lan net: 172.17.0.0/22'
      msg: 'inventory: bravo.lan server: charlie.lan net: 172.17.4.0/22'
      msg: 'inventory: charlie.lan server: alpha.lan net: 192.168.0.0/24'
      msg: 'inventory: charlie.lan server: alpha.lan net: 192.168.1.0/24'
      msg: 'inventory: charlie.lan server: bravo.lan net: 172.16.0.0/22'
      msg: 'inventory: charlie.lan server: bravo.lan net: 172.16.4.0/22'
    

    The task below gives the same results

        - debug:
            msg: "inventory: {{ inventory_hostname }}
                  server: {{ item.0.key }}
                  net: {{ item.1.net }}"
          with_subelements:
            - "{{ my_servers|from_yaml|dict2items }}"
            - value
          vars:
            my_servers: |
              {% for key,item in servers.items() if key != inventory_hostname %}
                {{ key }}:
              {% for key2,item2 in item.items() %}
                  - {ifc: {{ key2 }}, net: {{ item2.subnet }}/{{ item2.subnet_cidr }}}
              {% endfor %}
              {% endfor %}