How to select network interface given ip address in ansible across Debian and FreeBSD?

There is a difference in data collected by setup on Debian and FreeBSD.


In Ubuntu (Debian derivative) the attribute ipv4 is a dictionary. Secondary addresses are stored in the list ipv4_secondaries. As a first step create a list of devices and ipv4 addresses. For example

    - debug:
        var: ansible_facts.distribution
    - set_fact:
        ifc_list: "{{ ansible_facts|
                      dict2items|
                      json_query(query)|
                      selectattr('ipv4')|list }}"
      vars:
        query: "[?value.type == 'ether'].{device: value.device,
                                          ipv4: value.ipv4.address}"
    - debug:
        var: ifc_list

give (abridged)

  ansible_facts.distribution: Ubuntu

  ifc_list:
  - device: eth0
    ipv4: 10.1.0.27

Then "select network interface for given ip address"

    - set_fact:
        iface_for_ip: "{{ ifc_list|
                          selectattr('ipv4', 'eq', ip_address)|
                          map(attribute='device')|list }}"
      vars:
        ip_address: "10.1.0.27"
    - debug:
        var: iface_for_ip

give (abridged)

  iface_for_ip:
  - eth0

In FreeBSD the attribute ipv4 is a list. Create a list of devices and ipv4

    - debug:
        var: ansible_facts.distribution
    - set_fact:
        ifc_list: "{{ ansible_facts|
                      dict2items|
                      json_query(query)|
                      selectattr('ipv4')|list }}"
      vars:
        query: "[?value.type == 'ether'].{device: value.device,
                                          ipv4: value.ipv4[].address}"
    - debug:
        var: ifc_list

give (abridged)

  ansible_facts.distribution: FreeBSD

  ifc_list:
  - device: wlan0
    ipv4:
    - 10.1.0.51

Then "select network interface for given ip address"

    - set_fact:
        iface_for_ip: "{{ iface_for_ip|default([]) + [item.device] }}"
      loop: "{{ ifc_list }}"
      when: ip_address in item.ipv4
      vars:
        ip_address: "10.1.0.51"
    - debug:
        var: iface_for_ip

give (abridged)

  iface_for_ip:
  - wlan0

Q: "How to remove the final set_fact + loop so it can be defined purely in a vars file?"

A: The attribute ipv4 is a list. To use selectattr, instead of a loop, you'll need a test contains(seq, value). There is no such test available. Only in(value, seq) test with reversed order of parameters is available. You'll have to write your own test. For example

shell> cat test_plugins/contains.py 

def contains(l, value):
    return value in l


class TestModule:
    """Main test class from Ansible."""

    def tests(self):
        """Add these tests to the list of tests available to Ansible."""
        return {
            'contains': contains
        }

Then the set_fact gives the same result. The expression can be also used in vars

    - set_fact:
        iface_for_ip: "{{ ifc_list|
                          selectattr('ipv4', 'contains', ip_address)|
                          map(attribute='device')|list }}"