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 }}"