Search code examples
pythonansiblejinja2netbox

How to retrieve dictionary key when knowing value on one of its attributes?


Recently I was working on my Netbox automation using Ansible. I prepared task, which collects information about network interfaces that looks like below:

"interface_ips": {
  "br0": {
    "ipv4": [                
      "10.0.0.10",
      "10.0.0.11"
    ],
    "ipv6": [],
    "mac": "aa:bb:cc:dd:ee:ff",
    "mtu": "1500",
    "type": "bridge"
  },
  "lxcbr0": {
    "ipv4": [
      "10.100.0.10"
    ],
    "ipv6": [],
    "mac": "ff:ee:dd:cc:bb:aa",
    "mtu": "1500",
    "type": "bridge"
  }
}

I wanted to iterate over collected IPs (IPv4 + IPv6) and return interface name for corresponding IP.

The output I want is the following:

  • 10.0.0.10 - br0 - host
  • 10.0.0.11 - br0 - host
  • 10.100.0.10 - lxcbr0 - host

Some interfaces might have multiple IPs assigned, so it's very important to iterate over every IP.

I managed to do following task, but it doesn't iterate over every address, only first ipv4 address.

- name: Extract IPv4 addresses and interface names
  set_fact:
    ip_interface_map: "{{ ip_interface_map | default({}) | combine({item.value.ipv4[0]: item.key}) }}"
    loop: "{{ interface_ips | dict2items }}"

Can this be done easily somehow?


Solution

  • Convert the dictionary to a list
      interface_ips | dict2items:
        - key: br0
          value:
            ipv4: [10.0.0.10, 10.0.0.11]
            ipv6: []
            mac: aa:bb:cc:dd:ee:ff
            mtu: '1500'
            type: bridge
        - key: lxcbr0
          value:
            ipv4: [10.100.0.10]
            ipv6: []
            mac: ff:ee:dd:cc:bb:aa
            mtu: '1500'
            type: bridge
    

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

    gives (abridged)

      msg: 10.0.0.10 - br0
      msg: 10.0.0.11 - br0
      msg: 10.100.0.10 - lxcbr0
    

    The same loop with subelements('value.ipv6') will be skipped because all ipv6 lists are empty

      - debug:
          msg: "{{ item.1 }} - {{ item.0.key }}"
        loop: "{{ interface_ips | dict2items | subelements('value.ipv6') }}"
    
    • Optionally, create a dictionary first
      ip_interface_map: "{{ dict(interface_ips | dict2items | 
                                 json_query('[].[key, 
                                                [value.ipv4, value.ipv6]|[]]')) }}"
    

    gives

      ip_interface_map:
        br0:
        - 10.0.0.10
        - 10.0.0.11
        lxcbr0:
        - 10.100.0.10
    
    • Iterate subelements
      - debug:
          msg: "{{ item.1 }} - {{ item.0.key }}"
        loop: "{{ ip_interface_map | dict2items | subelements('value') }}"
    

    gives (abridged) the same result

      msg: 10.0.0.10 - br0
      msg: 10.0.0.11 - br0
      msg: 10.100.0.10 - lxcbr0
    

    Example of a complete playbook for testing

    - hosts: all
    
      vars:
    
        interface_ips:
          br0:
            ipv4: [10.0.0.10, 10.0.0.11]
            ipv6: []
            mac: aa:bb:cc:dd:ee:ff
            mtu: '1500'
            type: bridge
          lxcbr0:
            ipv4: [10.100.0.10]
            ipv6: []
            mac: ff:ee:dd:cc:bb:aa
            mtu: '1500'
            type: bridge
    
        ip_interface_map: "{{ dict(interface_ips | dict2items |
                                   json_query('[].[key,
                                                  [value.ipv4, value.ipv6]|[]]')) }}"
    
      tasks:
    
        - debug:
            msg: "{{ item.1 }} - {{ item.0.key }}"
          loop: "{{ interface_ips | dict2items | subelements('value.ipv4') }}"
        - debug:
            msg: "{{ item.1 }} - {{ item.0.key }}"
          loop: "{{ interface_ips | dict2items | subelements('value.ipv6') }}"
    
        - debug:
            var: ip_interface_map
    
        - debug:
            msg: "{{ item.1 }} - {{ item.0.key }}"
          loop: "{{ ip_interface_map | dict2items | subelements('value') }}"