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

  • Given an ip address, you want to find the name of the interface to which it was assigned. To do that, we need to build a data structure that maps addresses to interface names. I like to use filters for this sort of thing, since that allows us to implement the logic in Python.

    If we drop the following into filter_plugins/filters.py:

    def build_address_map(interface_ips):
        address_map = {}
        for iface, config in interface_ips.items():
            for addr in config["ipv4"]:
                address_map[addr] = iface
            for addr in config["ipv6"]:
                address_map[addr] = iface
    
        return address_map
    
    
    class FilterModule:
        def filters(self):
            return {
                "build_address_map": build_address_map,
            }
    

    Then we can write something like this:

    - hosts: localhost
      gather_facts: false
      vars:
        collected_ips:
        - 10.0.0.10
        - 192.168.10.100
        - 10.100.0.10
    
        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"
            }
          }
      tasks:
        - set_fact:
            address_map: "{{ interface_ips | build_address_map }}"
    
        - debug:
            msg: "address {{ item }} is assigned to {{ address_map[item]|default('unknown')}}"
          loop: "{{ collected_ips }}"
    

    Which produces as output:

    TASK [debug] *********************************************************************************************************************************
    ok: [localhost] => (item=10.0.0.10) => {
        "msg": "address 10.0.0.10 is assigned to br0"
    }
    ok: [localhost] => (item=192.168.10.100) => {
        "msg": "address 192.168.10.100 is assigned to unknown"
    }
    ok: [localhost] => (item=10.100.0.10) => {
        "msg": "address 10.100.0.10 is assigned to lxcbr0"
    }