Execution
Date 22 Jul 2025 13:10:43 +0100
Duration 00:00:05.81
Controller ssh-gw-4.layershift.com
User root
Versions
Ansible 2.16.11
ara 1.7.2 / 1.7.2
Python 3.10.10
Summary
2 Hosts
16 Tasks
16 Results
1 Plays
1 Files
0 Records

File: /home/ssh-gateway/ansible/kuly/get_mac_addresses.yaml

---
- name: Gather physical interface MACs and write to single local file using lineinfile
  hosts: all
  become: true
  gather_facts: false

  vars:
    output_file: "all_macs.txt"
    ignored_prefixes:
      - "lo"
      - "docker"
      - "veth"
      - "virbr"
      - "wl"
      - "wlan"
      - "tun"
      - "tap"

  tasks:
    - name: Get default route interface
      command: ip route show default
      register: default_route
      changed_when: false
      ignore_errors: yes

    - name: Extract main interface from default route
      set_fact:
        main_interface: >-
          {%- if 'dev' in default_route.stdout -%}
          {{ default_route.stdout.split('dev')[1].split()[0] }}
          {%- else -%}
          __no_default__
          {%- endif -%}

    - name: Fail if no default interface found
      fail:
        msg: "No default route found."
      when: main_interface == "__no_default__"

    # Check if interface is a bridge using nmcli
    - name: Get active nmcli connections (TYPE:DEVICE)
      command: nmcli -t -f TYPE,DEVICE connection show --active
      register: nmcli_connections
      changed_when: false
      ignore_errors: yes
      when: main_interface != "__no_default__"

    - name: Determine if main_interface is a bridge
      set_fact:
        is_bridge: "{{ 'true' if (nmcli_connections.stdout_lines | select('match', '^bridge:' + main_interface + '$') | list | length > 0) else 'false' }}"
      when: nmcli_connections.rc == 0

    - name: Set is_bridge to false if nmcli failed
      set_fact:
        is_bridge: 'false'
      when: nmcli_connections.rc != 0

    # Get slave interfaces if it's a bridge
    - name: Get nmcli slave connections (NAME:DEVICE:MASTER)
      command: nmcli -t -f NAME,DEVICE,MASTER connection show --active
      register: nmcli_slaves
      changed_when: false
      ignore_errors: yes
      when: is_bridge == 'true'

    - name: Find first physical slave interface
      set_fact:
        physical_interface: >-
          {%- set lines = nmcli_slaves.stdout_lines | default([]) -%}
          {%- for line in lines -%}
          {%- set parts = line.split(':') -%}
          {%- if parts[2] == main_interface -%}
          {{ parts[1] }}
          {%- break -%}
          {%- endif -%}
          {%- endfor -%}
      when: is_bridge == 'true'

    - name: Use main interface as physical if not a bridge
      set_fact:
        physical_interface: "{{ main_interface }}"
      when: is_bridge != 'true'

    - name: Fail if no physical interface found from bridge
      fail:
        msg: "Could not find physical interface enslaved to bridge {{ main_interface }}"
      when: is_bridge == 'true' and (physical_interface is undefined or physical_interface == '')

    # Ignore check
    - name: Check if physical interface is ignored
      set_fact:
        is_ignored: "{{ ignored_prefixes | select('regex', '^' ~ physical_interface) | list | length > 0 }}"

    - name: Fail if interface is ignored
      fail:
        msg: "Interface {{ physical_interface }} is ignored."
      when: is_ignored | bool

    - name: Validate interface exists
      stat:
        path: "/sys/class/net/{{ physical_interface }}"
      register: phys_stat
      failed_when: not phys_stat.stat.exists

    - name: Get MAC address
      command: "cat /sys/class/net/{{ physical_interface }}/address"
      register: mac_result
      changed_when: false

    # -----------------------------------------------------
    # ✅ Use lineinfile to append to a single local file
    # Runs once per host, delegated to localhost
    # -----------------------------------------------------

    - name: Write header to file (only once, from first host)
      delegate_to: localhost
      lineinfile:
        path: "{{ output_file }}"
        line: "# hostname - interface - mac_address"
        create: true
        insertbefore: BOF
      run_once: true

    - name: Append host entry to local file
      delegate_to: localhost
      lineinfile:
        path: "{{ output_file }}"
        line: "{{ inventory_hostname }} - {{ physical_interface }} - {{ mac_result.stdout }}"
        create: true