skip to Main Content

I’m trying to create a list of interface names along with their mac addresses from a Debian 11 server, initially, I was trying to get the mac addresses in order only but now I realize I need a list that looks like this:

eth0 <SOME_MAC>
eth1 <SOME_MAC>
...

I want to pass this list as a variable and then use it in the next task to create a 10-persistent-net.link file in the /etc/systemd/network directory.

The current task that I’m using is:

- name: Get mac addresses of all interfaces except local
  debug:
    msg: "{{ ansible_interfaces |
         map('regex_replace','^','ansible_') |
         map('extract',hostvars[inventory_hostname]) |
         selectattr('macaddress','defined') |
         map(attribute='macaddress') |
         list }}"

As you can see I’m using the debug module to test out my code and I have no idea how to create my desired list and pass it as a variable.

The above code gives the following result:

ok: [target1] => 
  msg:
 - 08:00:27:d6:08:1a
 - 08:00:27:3a:3e:ff
 - f6:ac:58:a9:35:33
 - 08:00:27:3f:82:c2
 - 08:00:27:64:6a:f8
ok: [target2] => 
  msg:
 - 08:00:27:34:70:60
 - 42:04:1a:ff:6c:46
 - 42:04:1a:ff:6c:46
 - 08:00:27:d6:08:1a
 - 08:00:27:9c:d7:af
 - f6:ac:58:a9:35:33

Any help on which module to use to pass the list as a variable and how to create the list in the first place is appreciated.

Kindly Note that I’m using Ansible v5.9.0 and each server may have any number of interfaces, some of them may have ethx interface name format while others may have enspx, brx etc interface format.

UPDATE: Per advice in a comment I must mention that I need one list for each target that will be used in a natural host loop task that will run against each target.

UPDATE 2: As I’m new to Ansible and per my coworker’s advice I was under the impression that a list of interface names along with their MAC addresses separated by space is what I need as a variable to be passed to the next task, however, throughout comments and answers I now realize that I was absolutely heading in the wrong direction. Please accept my apology and blame it on my lack of experience and knowledge of Ansible. In the end, it turned out that a dictionary of interface names and their MAC addresses is what is most suitable for this kind of action in Ansible.

2

Answers


  1. This is how I would do it.

    Note that my example uses the json_query filter which requires pip install jmespath on your ansible controller.

    ---
    - name: Create a formated list for all interfaces
      hosts: all
      
      vars:
        elligible_interfaces: "{{ ansible_interfaces | reject('==', 'lo') }}"
    
        interfaces_list_raw: >-
          {{
            hostvars[inventory_hostname]
            | dict2items
            | selectattr('value.device', 'defined')
            | selectattr('value.device', 'in', elligible_interfaces)
            | map(attribute='value')
          }}
    
        interface_query: >-
          [].[device, macaddress]
    
        interfaces_formated_list: >-
          {{ interfaces_list_raw | json_query(interface_query) | map('join', ' ') }}
    
      tasks:
        - name: Show our calculated var
          debug:
            var: interfaces_formated_list
    

    Which gives running against my localhost:

    $ ansible-playbook -i localhost, /tmp/test.yml 
    
    PLAY [Create a formated list for all interfaces] **************************************************************************************************
    
    TASK [Gathering Facts] ****************************************************************************************************************************
    ok: [localhost]
    
    TASK [Show our calculated var] ********************************************************************************************************************
    ok: [localhost] => {
        "interfaces_formated_list": [
            "docker0 02:42:98:b8:4e:75",
            "enp4s0 50:3e:aa:14:17:8f",
            "vboxnet0 0a:00:27:00:00:00",
            "veth7201fce 92:ab:61:7e:df:65"
        ]
    }
    
    PLAY RECAP ****************************************************************************************************************************************
    localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
    

    As you can see, this is showing several interfaces that you might want to filter out in your use case. You can inspect interfaces_list_raw and create additional filters to achieve your goal. But at least you get the idea.

    Login or Signup to reply.
  2. Get the list of the variables

    blacklist: ['lo']
    interfaces: "{{ ['ansible_']|
                    product(ansible_interfaces|
                            difference(blacklist))|
                    map('join')|list }}"
    

    Get the values of the variables and create a dictionary

    devices: "{{ interfaces|
                 map('extract', vars)|
                 items2dict(key_name='device',
                            value_name='macaddress') }}"
    

    Notes

    • A dictionary is more efficient compared with a list. The keys must be unique.
    • A dictionary in YAML aka mapping is ‘an unordered set of key/value node pairs, with the restriction that each of the keys is unique‘.
    • As of Python version 3.7, dictionaries are ordered.. As a result Ansible (YAML) dictionaries are also ordered when using Python 3.7 and later. For example,
      devices:
        docker0: 02:42:35:39:f7:f5
        eth0: 80:3f:5d:14:b1:d3
        eth1: e4:6f:13:f5:09:80
        wlan0: 64:5d:86:5d:16:b9
        xenbr0: 80:3f:5d:14:b1:d3
    
    • See Jinja on how to create various formats of output. For example,
        - debug:
            msg: |-
              {% for ifc, mac in devices.items() %}
              {{ ifc }} {{ mac }}
              {% endfor %}
    

    gives

      msg: |-
        wlan0 64:5d:86:5d:16:b9
        eth0 80:3f:5d:14:b1:d3
        eth1 e4:6f:13:f5:09:80
        xenbr0 80:3f:5d:14:b1:d3
        docker0 02:42:35:39:f7:f5
    

    You can see that the output of Jinja is not ordered. Actually, the order is not even persistent when you repeat the task. Use the filter sort if you want to order the lines. For example,

        - debug:
            msg: |-
              {% for ifc, mac in devices.items()|sort %}
              {{ ifc }} {{ mac }}
              {% endfor %}
    

    gives

      msg: |-
        docker0 02:42:35:39:f7:f5
        eth0 80:3f:5d:14:b1:d3
        eth1 e4:6f:13:f5:09:80
        wlan0 64:5d:86:5d:16:b9
        xenbr0 80:3f:5d:14:b1:d3
    

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search