10

I'd like to get an ordered list of the network interfaces on a machine using Ansible. Modern Linux systems don't use eth0, eth1, etc. So the names are unpredictable. On our network we connect the lowest numbered interface to the LAN and the highest to the WAN, so I can use the position of the interface in an ordered list to determine its function.

I am looking for the canonical way to do this in Ansible. So that I can use something like {{ansible_eth0.ipv4.address}}. (Where eth0 is some other name).

Even if I manually set a variable with the name of the interface there seems no way to get the IP of that interface (using the contents of the variable).

I'd like to process the Ansible facts to get what I want rather than running a shell script on the remote system.

Neik
  • 374
  • 2
  • 3
  • 10
  • Actually, now the names are completely predictable. They were _unpredictable_ before, when they were all eth*. – Michael Hampton Apr 06 '16 at 09:23
  • 1
    If I get a new machine with one interface I don't know what it's interface name will be until I turn it on. That's what I mean by unpredictable. – Neik Apr 06 '16 at 21:10
  • Right, but you also don't know which interface is eth0 and which is eth1 until you turn it on...and you don't know if they will switch places on the next reboot (which is why these are no longer used). And, at least some of the time, you _can_ know the new interface names before you first power on and install the system. – Michael Hampton Apr 06 '16 at 21:16
  • 1
    I agree with Neik: if you have one of those millions of (virtual) servers that have only one NIC, the interface name used to be predicable: 'eth0'. Now, since the invention of 'predictable nic names', the single server nic name has become unpredictable as far as I can see. Instead of using 'eth0', we now have to enumerate interface names to get the default nic name. What is predictable about that? – anneb Jun 27 '16 at 14:40
  • I've found that `ansible_default_ipv4.address` will give the default IPv4 address, and `ansible_default_ipv4.interface` the default IPv4 interface. This doesn't answer your question, but it helped me as a workaround, in a situation where the default interface was named `eth0` on certain systems and `eth1` on others. – StockB Jul 11 '17 at 17:13

5 Answers5

9

The ansible_interfaces fact lists all of the existing network interfaces.

Michael Hampton
  • 237,123
  • 42
  • 477
  • 940
6

Some hints to get an interface when we know some information:

    var:
      allNetworkInterfaces: "{{ ansible_facts | dict2items | selectattr('value.ipv4', 'defined') | map(attribute='value') | list }}"
      allNetworkInterfaces_variant2: "{{ ansible_facts.interfaces | map('extract', ansible_facts ) | list }}"
      interfaceWithKnownIp: "{{ ansible_facts | dict2items | selectattr('value.ipv4', 'defined') | selectattr('value.ipv4.address', 'equalto', myKnowIpV4) | first }}"
      interfaceWithKnownIp_fromVar: "{{ allNetworkInterfaces | selectattr('ipv4.address', 'equalto', myKnowIpV4) | first }}"
      interfacesWithPartialKnowMac: "{{ allNetworkInterfaces | selectattr('macaddress', 'match', knownMacPrefix~'.*') | list }}"
      interfacesWitKnowType: "{{ allNetworkInterfaces | selectattr('type', 'equalto', knownType) | sort(attribute='device') | list }}"
      # extended on 2020-10-28
      queryForKnownIpv6: "[*].{device: device, ipv4: ipv4, ipv6: ipv6[? address == 'fe80::a00:27ff:fe38:ad36']}[?ipv6])" # string must be in ' # sorry, only partial interface info, did not find out how to return all info directly
      interfacesWithKnownIpv6: '{{ allNetworkInterfaces | json_query(queryForKnownIpv6) | first }}'
      queryForKnownIpv4_linux: "[?ipv4.address == '{{ myKnownIpV4 }}']}[?ipv4])" # string must be in '
      interfacesWithKnownIp_variantJsonQuery: '{{ allNetworkInterfaces | json_query(queryForKnownIpv4_linux) | first }}'

some short explications:

simohe
  • 216
  • 2
  • 5
  • On FreeBSD the interfaces `ipv4` structure is not an object, but an array. So the `interfaceWithKnownIp` doesn't work. Is it possible to make this work for both platforms? – Casey Oct 27 '20 at 14:20
  • It is not the same in all ansible? Bummer! Is ipv4 on FreeBSD an array of dict? Similar to ipv6 for ansible_eth0 in https://docs.ansible.com/ansible/latest/user_guide/playbooks_vars_facts.html#ansible-facts ? – simohe Oct 28 '20 at 08:31
  • yes exactly, ipv4 is an array of dicts. i opened another question here https://serverfault.com/questions/1040246/how-to-select-network-interface-given-ip-address-in-ansible-across-debian-and-fr/1040273#1040273 – Casey Oct 28 '20 at 12:34
  • In this case, for FreeBSD you can adapt the query for ipv6 address. Merging the two possibilities is a bit trickier, which is discussed in your new question. (And the different implementations [are reported as bugs](https://github.com/ansible/ansible/issues?q=is%3Aissue+is%3Aopen+ipv4.secondaries)) – simohe Oct 28 '20 at 14:52
4

I understand where Neik is coming from so after a bit of experimenting, I think I found a task that will do the trick.

As Michael Hamilton mentioned above the ansible_interfaces fact contains a list of all the network interfaces and they appear to be in order (i.e., the first ethernet interface would have been called eth0, the second eth1, etc). So a little set_fact magic and a lot of experimenting later:

- name: define traditional ethernet facts
  set_fact:
    ansible_eth: "{% set ansible_eth = ansible_eth|default([]) + [hostvars[inventory_hostname]['ansible_' + item]] %}{{ ansible_eth|list }}"
  when: hostvars[inventory_hostname]['ansible_' + item]['type'] == 'ether'
  with_items:
    - "{{ hostvars[inventory_hostname]['ansible_interfaces'] }}"

This loops over all of the ansible_interfaces entries for the current machine and builds a list of the hostvars[inventory_hostname]['ansible_' + item] entries that have a type equal to "ether".

Thus now ansible_eth.0 and ansible_eth.1 should be roughly equivalent to the old ansible_eth0 and ansible_eth1 respectively.

I haven't thoroughly tested this to ensure the order always works out like expected but it seems to do the trick.

Many thanks to this StackOverflow answer for showing me how to build the list using with_items.

PaulR
  • 321
  • 2
  • 8
  • 1
    `with_items:` `- "{{ hostvars[inventory_hostname]['ansible_interfaces'] }}"` Can be replaced with: `with_items: "{{ansible_interfaces}}"` – Rwky Apr 17 '20 at 18:05
2

Been a while since i touched ansible, but without more details i would expect something like:

ip link show | grep mode | sed 's/://g' | awk '{print $1,$2}'

to work...

1 lo
2 eth0
Daniel Widrick
  • 3,418
  • 2
  • 12
  • 26
  • 1
    I should have made it clear that I am looking for the canonical way to do this in Ansible. So that I can use things like {{ansible_eth0.ipv4.address}}. I don't want unnecessary change events from scripts messing up my output. – Neik Apr 06 '16 at 08:41
0

Very similar to Daniel Widrick's answer but avoiding the older with_items in favor of loop. Also I only wanted to find the wifi interface, which is still wlan0 for the raspberry. It would also find many other adapters, as most start with wl (but not all)

- name: Get wifi adapter
  set_fact: 
    wifi_adapter: '{{ item }}'
  loop: '{{ ansible_facts.interfaces }}'
  when: 'item.startswith("wl")'enter code here
oneindelijk
  • 159
  • 8