Anthony Pineda
Anthony Pineda

Reputation: 41

Ansible loop over nested dictionary

I have the following data being returned from an API call

"napalm_interfaces_ip": {
    "Vlan5": {
        "ipv4": {
            "10.45.230.250": {
                 "prefix_length": 24
            }
        }
     }
 }

How do I debug print the Vlan info, the IP and the prefix length?

This is what I have

- debug:
     msg: "Interface: {{ item.key }}, IP: {{ item.value.ipv4 }}"
  with_dict:
     - "{{ napalm_interfaces_ip }}"

It produces the below output which shows the IP pointing to another dictionary

TASK [validate_device_ips : debug]


ok: [] => (item={'value': {u'ipv4': {u'10.45.230.250': {u'prefix_length': 24}}}, 'key': u'Vlan5'}) => { "msg": "Interface: Vlan5, IP: {u'10.45.230.250': {u'prefix_length': 24}}" }

Upvotes: 4

Views: 16719

Answers (1)

Vladimir Botka
Vladimir Botka

Reputation: 68254

udpate

Given the below dictionary for testing

  interfaces:
    Vlan5:
      ipv4:
        10.45.230.250:
          prefix_length: 24
        10.45.230.251:
          prefix_length: 24
    Vlan6:
      ipv4:
        10.46.230.250:
          prefix_length: 24
        10.46.230.251:
          prefix_length: 24

Q: "Print the Vlan, IP, and the prefix length."

A: Get the list of Vlan

    vlan: "{{ interfaces|json_query('keys(@)') }}"

gives

    vlan: ['Vlan5', 'Vlan6']

Get the lists of IPs and lengths

    ipv4: "{{ interfaces|json_query('*.[ipv4.keys(@),
                                        ipv4.*.prefix_length]') }}"

gives

    ipv4:
      - - - 10.45.230.250
          - 10.45.230.251
        - - 24
          - 24
      - - - 10.46.230.250
          - 10.46.230.251
        - - 24
          - 24

zip the IPs and lengths

    addr: |
      [{% for i in ipv4 %}
      {{ i.0|zip(i.1) }},
      {% endfor %}]

gives

    addr:
      - - - 10.45.230.250
          - 24
        - - 10.45.230.251
          - 24
      - - - 10.46.230.250
          - 24
        - - 10.46.230.251
          - 24

zip the list of Vlan and the lists of addresses, and create a dictionary

    intf: "{{ dict(vlan|zip(addr)) }}"

gives

    intf:
      Vlan5:
      - - 10.45.230.250
        - 24
      - - 10.45.230.251
        - 24
      Vlan6:
      - - 10.46.230.250
        - 24
      - - 10.46.230.251
        - 24

Now, it is possible to iterate the dictionary with subelements

    - debug:
        msg: "{{ item.0.key }} {{ item.1.0 }} {{ item.1.1 }}"
      loop: "{{ intf|dict2items|subelements('value') }}"
      loop_control:
        label: "{{ item.0.key }}"

gives (abridged)

  msg: Vlan5 10.45.230.250 24
  msg: Vlan5 10.45.230.251 24
  msg: Vlan6 10.46.230.250 24
  msg: Vlan6 10.46.230.251 24

Example of a complete playbook for testing

- hosts: localhost

  vars:

    interfaces:
      Vlan5:
        ipv4:
          10.45.230.250:
            prefix_length: 24
          10.45.230.251:
            prefix_length: 24
      Vlan6:
        ipv4:
          10.46.230.250:
            prefix_length: 24
          10.46.230.251:
            prefix_length: 24

    vlan: "{{ interfaces|json_query('keys(@)') }}"
    #ip4: "{{ interfaces|json_query('*.ipv4.keys(@)') }}"
    #len: "{{ interfaces|json_query('*.ipv4.*.prefix_length') }}"
    ipv4: "{{ interfaces|json_query('*.[ipv4.keys(@),
                                        ipv4.*.prefix_length]') }}"
    addr: |
      [{% for i in ipv4 %}
      {{ i.0|zip(i.1) }},
      {% endfor %}]
    intf: "{{ dict(vlan|zip(addr)) }}"
      
  tasks:

    - debug:
        msg: |
          vlan: {{ vlan }}
          ipv4:
            {{ ipv4|to_nice_yaml(indent=2)|indent(2) }}
          addr:
            {{ addr|to_nice_yaml(indent=2)|indent(2) }}
          intf:
            {{ intf|to_nice_yaml(indent=2)|indent(2) }}

    - debug:
        msg: "{{ item.0.key }} {{ item.1.0 }} {{ item.1.1 }}"
      loop: "{{ intf|dict2items|subelements('value') }}"
      loop_control:
        label: "{{ item.0.key }}"

origin

dict2items should help:

    - debug:
        msg: "IP: {{ item.key }} prefix_length: {{ item.value.prefix_length }}"
      loop: "{{ napalm_interfaces_ip.Vlan5.ipv4 | dict2items }}"

with_dict works too

    - debug:
        msg: "IP: {{ item.key }} prefix_length: {{ item.value.prefix_length }}"
      with_dict: "{{ napalm_interfaces_ip.Vlan5.ipv4 }}"

An example of how to loop the interfaces is below

    - hosts: localhost
      gather_facts: false
      vars:
        interfaces:
          - Vlan0:
              ipv4:
                10.45.230.250:
                  prefix_length: 24
          - Vlan1:
              ipv4:
                10.45.230.251:
                  prefix_length: 24
          - Vlan2:
              ipv4:
                10.45.230.252:
                  prefix_length: 24
      tasks:
        - name: List selected variables
          vars:
            msg: |
              vlan {{ item|dict2items|json_query('[].key') }}
              ip {{ item|dict2items|json_query('[].value.ipv4|[0]')|dict2items|json_query('[].key') }}
              prefix {{ item|dict2items|json_query('[].value.ipv4|[0]')|dict2items|json_query('[].value.prefix_length') }}
          debug:
            msg: "{{ msg.split('\n') }}"
          loop: "{{ interfaces }}"

It is possible to simplify the structure of the data

    - hosts: localhost
      vars:
        interfaces:
          - Vlan0:
              ipv4: "10.45.230.250"
              prefix_length: "24"
          - Vlan1:
              ipv4: "10.45.230.251"
              prefix_length: "24"
          - Vlan2:
              ipv4: "10.45.230.252"
              prefix_length: "24"
      tasks:
        - name: List selected variables
          vars:
            msg: |
              vlan {{ item|dict2items|json_query('[].key') }}
              ip {{ item|dict2items|json_query('[].value.ipv4') }}
              prefix {{ item|dict2items|json_query('[].value.prefix_length') }}
          debug:
            msg: "{{ msg.split('\n') }}"
          loop: "{{ interfaces }}"

Upvotes: 3

Related Questions