How to Extract Matching Network Interface Names Using Regex in Ansible Playbooks


10 views

Modern Linux distributions have moved away from the traditional eth0 naming convention to predictable network interface names like ens33, enp0s3, or eno1. While this provides consistency, it creates challenges for automation when you need to:

  • Identify physical interfaces dynamically
  • Configure bridge interfaces properly
  • Handle varying interface names across hosts

Using {{ ansible_default_ipv4.interface }} seems logical initially, but it presents problems:

# Problem scenario:
# After bridge creation, this will return br0 instead of the physical interface
bridge_ports: "{{ ansible_default_ipv4.interface }}"  # Returns br0 after bridge creation

Here's how to properly extract physical interface names using Ansible's regex capabilities:

Solution 1: Using select and match filters

- name: Extract physical interfaces
  set_fact:
    physical_interfaces: "{{ ansible_interfaces | select('match','^en') | list }}"
    
- name: Get first physical interface
  set_fact:
    primary_interface: "{{ physical_interfaces[0] }}"

Solution 2: Jinja2 regex with list filtering

- name: Find matching interfaces
  debug:
    msg: "{{ item }}"
  loop: "{{ ansible_interfaces }}"
  when: item is regex('^en[a-z0-9]*$')
  register: interface_results

- name: Extract successful matches
  set_fact:
    matched_interfaces: "{{ interface_results.results | selectattr('skipped', 'equalto', false) | map(attribute='item') | list }}"

Solution 3: Combining with network facts

- name: Gather specific interface facts
  ansible.builtin.setup:
    filter: "ansible_*"
    gather_subset: "!all"

- name: Get interfaces with MAC addresses (physical interfaces)
  set_fact:
    physical_ifaces: "{{ ansible_facts | dict2items | selectattr('key', 'match', '^ansible_en') | map(attribute='key') | list | map('regex_replace', '^ansible_', '') }}"

Here's how to apply this in your /etc/network/interfaces template:

auto br0
iface br0 inet dhcp
    bridge_ports {{ primary_interface | default('eth0') }}
    bridge_stp off
    bridge_fd 0

Consider these additional scenarios:

- name: Handle multiple matching interfaces
  set_fact:
    bridge_ports: "{{ physical_interfaces | join(' ') }}"
  when: physical_interfaces | length > 1

- name: Fallback to eth0 if no matches found
  set_fact:
    primary_interface: "eth0"
  when: physical_interfaces | length == 0

Here's a complete example to test interface detection:

- hosts: all
  gather_facts: yes
  tasks:
    - name: Get physical interfaces
      set_fact:
        phys_ifaces: "{{ ansible_interfaces | select('match','^en') | list }}"
    
    - name: Debug physical interfaces
      debug:
        var: phys_ifaces
    
    - name: Configure bridge if physical interface exists
      template:
        src: interfaces.j2
        dest: /etc/network/interfaces
      when: phys_ifaces | length > 0

When automating bridge interface configuration in Ansible, one common challenge is reliably identifying the physical network interface that should be bridged. The traditional approach using ansible_default_ipv4.interface fails after bridge creation because it then returns the bridge interface itself.

Ansible provides ansible_interfaces, which contains a list of all network interfaces. For modern Linux systems with predictable network interface names, these typically follow patterns like ens*, enp*s*, or eth*.

ansible_interfaces: ['lo', 'ens33', 'br0']

The key to solving this is combining Jinja2 filters with proper regex matching. Here's the most robust solution:

- name: Find first matching physical interface
  set_fact:
    physical_interface: "{{ ansible_interfaces | select('match','^en') | first }}"
    # '^en' matches ens*, enp*, eno*, etc.

Here's a full playbook example that handles the bridge configuration:

- hosts: all
  tasks:
    - name: Get first physical interface
      set_fact:
        bridge_port: "{{ ansible_interfaces | select('match','^en') | first }}"

    - name: Configure bridge interface
      template:
        src: interfaces.j2
        dest: /etc/network/interfaces
        owner: root
        group: root
        mode: 0644

With the corresponding template (interfaces.j2):

auto br0
iface br0 inet dhcp
    bridge_ports {{ bridge_port }}

For more complex environments, you might need additional filtering:

- name: Get physical interface excluding specific patterns
  set_fact:
    bridge_port: >-
      {{ 
        ansible_interfaces 
        | select('match','^en')
        | reject('match','^enx')  # exclude USB Ethernet
        | reject('match','^enp.*f')  # exclude virtual functions
        | first 
      }}

Another method leverages the ansible_facts dictionary for more detailed interface information:

- name: Get physical interface with link status
  set_fact:
    bridge_port: >-
      {{
        ansible_facts
        | dict2items
        | selectattr('key','match','^en')
        | selectattr('value.active','eq',true)
        | map(attribute='key')
        | first
      }}