3

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.

Sinux
  • 94
  • 7
  • 1
    Please [edit] your question and clarify if you want one global list with all interfaces from all target servers to run one single task for all hosts at once or one list for each target that you will use in a natural host loop taks that will run against each target. – Zeitounator Aug 09 '22 at 16:21
  • Thanks for your advice, I sure will. – Sinux Aug 09 '22 at 16:42

2 Answers2

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
Vladimir Botka
  • 58,131
  • 4
  • 32
  • 63
  • Hello, I will surely test this. Thank you so much, will report back if it satisfies my requirements. – Sinux Aug 10 '22 at 09:05
  • This answer is actually so much better than the previous one since it gives a dictionary and the interfaces are in order, meaning it starts from e.g. `eth0` and ends with the last interface. Thank you very much. – Sinux Aug 10 '22 at 12:20
  • Since this method gives the results in the correct order it makes things far more easier, what change in the code must be made to only get the MAC addresses in the correct order, the reason I originally wanted a list of interfaces names with their MAC Address was that I didn't know you can generate a list of MAC addresses in the correct order. – Sinux Aug 10 '22 at 12:25
  • 1
    If you are looking for a dict as a result, you should say so in your question. You asked for a list of strings composed of names and ports separated by a space. – Zeitounator Aug 10 '22 at 12:25
  • 1
    That's true, but it's because I am so new to Ansible and I was misguided by my co-worker that a list does what I want to do, as a matter of fact, this is the first time I'm seeing a dictionary being generated in Ansible. I deeply apologize for this @Zeitounator – Sinux Aug 10 '22 at 12:31
  • Thank you so much for updating your answer and teaching me how to fish instead of giving me the fish @vladmir-botka – Sinux Aug 10 '22 at 12:44
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.

Zeitounator
  • 38,476
  • 7
  • 53
  • 66