When working with Ansible's setup module, you'll encounter network interface facts stored in two distinct formats:
"ansible_interfaces": ["lo", "eth0", "eth1"],
"ansible_eth0": {
"active": true,
"ipv4": {
"address": "192.168.10.2"
},
"macaddress": "52:54:00:5c:c1:36"
}
A common mistake is trying to directly interpolate interface names:
- name: Attempt with direct interpolation (WON'T WORK)
debug:
msg: "{{ ansible_{{ item }} }}"
with_items: "{{ ansible_interfaces }}"
This fails because Ansible's template engine processes variables before expanding loops.
Method 1: Using the vars Lookup Plugin
- name: Correct way to access interface facts
debug:
msg: "{{ lookup('vars', 'ansible_' + item) }}"
loop: "{{ ansible_interfaces }}"
Method 2: Using the combine Filter (Ansible 2.5+)
- name: Gather all interface facts into single dictionary
set_fact:
all_interface_facts: "{{
all_interface_facts | default({}) | combine({
item: hostvars[inventory_hostname]['ansible_' + item]
})
}}"
loop: "{{ ansible_interfaces }}"
- name: Display collected facts
debug:
var: all_interface_facts
Here's how to extract MAC addresses from all interfaces:
- name: Create list of MAC addresses
set_fact:
mac_addresses: "{{
mac_addresses | default([]) + [
lookup('vars', 'ansible_' + item).macaddress
]
}}"
loop: "{{ ansible_interfaces }}"
when: lookup('vars', 'ansible_' + item).macaddress is defined
- name: Display MAC addresses
debug:
var: mac_addresses
Consider these improvements for production use:
- name: Safe interface fact gathering with error handling
block:
- name: Get interface facts
set_fact:
iface_fact: "{{ lookup('vars', 'ansible_' + item) }}"
loop: "{{ ansible_interfaces }}"
loop_control:
loop_var: item
- name: Process interface data
debug:
msg: "Interface {{ item.key }}: MAC {{ item.value.macaddress | default('N/A') }}"
with_dict: "{{ dict(ansible_interfaces | zip(ansible_interfaces | map('extract', hostvars[inventory_hostname] | default({})))) }}"
when: item.value is mapping
For large inventories, consider these optimizations:
- name: Optimized interface processing
vars:
interface_facts: "{{
dict(ansible_interfaces | map('regex_replace', '^', 'ansible_') |
map('extract', hostvars[inventory_hostname])) }}"
tasks:
- name: Show optimized results
debug:
msg: "{{ item.value }}"
with_dict: "{{ interface_facts }}"
When working with Ansible facts, network interface information is organized in a specific pattern. The ansible_interfaces
fact provides a list of available interfaces, while each interface has its own dedicated fact variable named ansible_<interface>
containing detailed configuration.
# Example facts structure:
"ansible_interfaces": ["lo", "eth0", "eth1"],
"ansible_eth0": {
"active": true,
"device": "eth0",
"ipv4": {
"address": "192.168.10.2"
},
"macaddress": "52:54:00:5c:c1:36"
}
Since interface names vary across systems (physical NICs, bridges, VLANs, etc.), we need a way to dynamically access each interface's facts without hardcoding interface names. A naive approach like this doesn't work:
- name: Attempt to access interface facts (won't work)
debug:
msg: "{{ ansible_{{ item }} }}"
with_items: "{{ ansible_interfaces }}"
This fails because Ansible doesn't evaluate nested variable references in this way.
The proper way to dynamically access variable facts is through Ansible's vars
lookup plugin:
- name: Display all interface facts
debug:
msg: "{{ lookup('vars', 'ansible_' + item) }}"
with_items: "{{ ansible_interfaces }}"
This solution works because the vars
lookup dynamically constructs the variable name at runtime and retrieves its value.
Here's a complete example that collects MAC addresses from all interfaces and prepares them for storage in Elasticsearch:
- name: Gather and process interface information
hosts: all
tasks:
- name: Extract MAC addresses from all interfaces
set_fact:
interface_macs: >-
{% set result = [] %}
{% for iface in ansible_interfaces %}
{% set iface_fact = lookup('vars', 'ansible_' + iface) %}
{% if iface_fact is defined and iface_fact.macaddress is defined %}
{% set _ = result.append({
'interface': iface,
'macaddress': iface_fact.macaddress,
'host': inventory_hostname
}) %}
{% endif %}
{% endfor %}
{{ result }}
- name: Display collected MAC addresses
debug:
var: interface_macs
- name: Send to Elasticsearch
community.general.elastic:
host: "http://elastic.example.com:9200"
index: "network-inventory"
doc_type: "interface"
body: "{{ item }}"
with_items: "{{ interface_macs }}"
When working with dynamic interface facts, consider these scenarios:
# Example of handling missing or undefined facts
- name: Safely access interface facts
debug:
msg: >-
Interface {{ item }}:
{% set iface_var = 'ansible_' + item %}
{% if iface_var in vars %}
MAC: {{ vars[iface_var].macaddress | default('N/A') }}
{% else %}
(No facts available)
{% endif %}
with_items: "{{ ansible_interfaces }}"
For large inventories, the vars
lookup approach can be slightly slower than direct variable access. In performance-critical scenarios, consider:
# Alternative using hostvars (for playbooks running against multiple hosts)
- name: Access interface facts via hostvars
debug:
msg: "{{ hostvars[inventory_hostname]['ansible_' + item] }}"
with_items: "{{ ansible_interfaces }}"