Search code examples
listloopsansiblenested-loops

Ansible - Nested Loop with list of dictionaries when dictionary values are lists


I have a list of dictionaries.

  • Dictionary keys are Cisco Switch names
  • Dictionary values are vlan IDs that are configured on the corresponding switch
"switch_vlan_ids": [
    {
        "switch01": ["1", "10", "30", "50"]
    },
    {
        "switch02": ["1", "20", "40", "60"]
    }
]

I'm attempting to loop through each switch and, on each switch, loop through each ID so that I can pass this ID to a command, eg show vlan {{id}}

The end goal, connect to a bunch of switches, verify that for each switch that the switch ports are configured for each vlan ID and, report an error/fail if not.

I tried a nested loop but, the inner loop complains that the outer loop loop_var isn't defined

 - name: Outer loop - Iterate through TOR Switches
   ansible.builtin.debug:
     msg: "TOR Name: {{ tor_item.key }}"
   loop: "{{ switch_vlan_ids | map('dict2items') | flatten }}"
   loop_control:
     loop_var: tor_item

 - name: Inner loop - Iterate through VLAN IDs
   ansible.builtin.debug:
     msg: "VLAN ID: {{ id_item.value }}"
   loop: "{{ tor_item.value }}"
   loop_control:
     loop_var: id_item

I've also attempted to just loop through the ID values but, this doesn't give the switch name so, if there's an error, I can't report the name of the switch with the error

- name: Set fact to loop through the tor_vlan_is_list values
  ansible.builtin.set_fact:
    switch_vlan_id_values: "{{ switch_vlan_ids | json_query('[].values(@)') | flatten }}"
- debug: var=switch_vlan_id_list_values

the above returns

"switch_vlan_id_values": ["1", "10", "30", "50", "1", "20", "40", "60"]

I've even tried a Jinja filter like below but abandoned it as I have no idea how I'd pass the values to an API call

- name: blah
  set_fact: | 
    {% for switch in switch_vlan_ids %}
    {% for name,ids in switch.items() %}
    {% for id in ids %}

Solution

  • Q: "Loop through each switch and, on each switch, loop through each ID."

    A: Adding subelements('value') to the pipe does the job

    - debug:
        msg: "{{ item.0.key }} {{ item.1 }}"
      loop: "{{ switch_vlan_ids|map('dict2items')|flatten|subelements('value') }}"
    

    gives (abridged)

      msg: switch01 1
      msg: switch01 10
      msg: switch01 30
      msg: switch01 50
      msg: switch02 1
      msg: switch02 20
      msg: switch02 40
      msg: switch02 60
    

    Example of a complete playbook for testing

    - hosts: all
    
      vars:
    
        switch_vlan_ids:
          - switch01: ["1", "10", "30", "50"]
          - switch02: ["1", "20", "40", "60"]
    
      tasks:
    
        - debug:
            msg: "{{ item.0.key }} {{ item.1 }}"
          loop: "{{ switch_vlan_ids|map('dict2items')|flatten|subelements('value') }}"