Wildcards in ansible_facts

Hello,

I’m attempting to loop over numerous network interfaces and send them to our NetBox instance. Unfortunately, due to the disparate types of servers and network interfaces that are provided, I would like to be able to use wildcards to loop over all interfaces of certain types. So far I have attempted the following:

    - name: Write interfaces and addresses
      include_tasks:
        file: interfaces.yml
      loop:
        - "{{ ansible_facts['ansible_eth*'] }}"
        - "{{ ansible_facts['ansible_'+eno*] }}"
        - "{{ ansible_facts['ansible_'+enp24s0f*] }}"

Unsurprisingly, ansible isn’t happy when I tried to do so:

TASK [Write interfaces and addresses] ********************************************************************************************
fatal: [<hostname>]: FAILED! => {"msg": "'dict object' has no attribute 'ansible_eth*'. 'dict object' has no attribute 'ansible_eth*'"}

I’m looking for guidance on how to best iterate over the interfaces. Any advice is greatly appreciated. If it’s relevant, ansible version info is below:

ansible [core 2.16.6]
  config file = /home/user/project-hpc-netbox-migration/ansible.cfg
  configured module search path = ['/home/user/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /opt/rci/miniconda3/envs/ansible/lib/python3.12/site-packages/ansible
  ansible collection location = /home/user/.ansible/collections:/usr/share/ansible/collections
  executable location = /opt/rci/miniconda3/envs/ansible/bin/ansible
  python version = 3.12.3 | packaged by Anaconda, Inc. | (main, May  6 2024, 19:46:43) [GCC 11.2.0] (/opt/rci/miniconda3/envs/ansible/bin/python)
  jinja version = 3.1.4
  libyaml = True

Hii

| sam_karateman
September 4 |

  • | - |

Hello,

I’m attempting to loop over numerous network interfaces and send them to our NetBox instance. Unfortunately, due to the disparate types of servers and network interfaces that are provided, I would like to be able to use wildcards to loop over all interfaces of certain types. So far I have attempted the following:

    - name: Write interfaces and addresses
      include_tasks:
        file: interfaces.yml
      loop:
        - "{{ ansible_facts['ansible_eth*'] }}"
        - "{{ ansible_facts['ansible_'+eno*] }}"
        - "{{ ansible_facts['ansible_'+enp24s0f*] }}"

Unsurprisingly, ansible isn’t happy when I tried to do so:

TASK [Write interfaces and addresses] ********************************************************************************************
fatal: [<hostname>]: FAILED! => {"msg": "'dict object' has no attribute 'ansible_eth*'. 'dict object' has no attribute 'ansible_eth*'"}

I’m looking for guidance on how to best iterate over the interfaces. Any advice is greatly appreciated.

Try looping over this:

loop: “{{ ansible_facts.interfaces | select(‘search’, ‘(eth|eno|enp24s0f)\d+’) }}”

That is a regex there, you may have to tweak it a bit further for your situation.

Dick

3 Likes

As a best practice you first filter facts using set_fact module using filters and iterate over the that variable or list of facts. loop matching the exact attribute name from facts.

ansible.builtin.set_fact:
my_loop_facts: “{{ ansible_facts[‘interfaces’] | select(‘search’, ‘^eth|^eno|^enp24s0f’) | list }}”

and then iterate over my_loop_facts variable.
loop:
my_loop_facts

-Mayur

I was able to get it working for the lookup based on the replies, but I’m having trouble referencing the variable from within ansible_facts. I would like to do something like this:

  when:
    - ansible_facts[\"{{ item }}\"][active] is true

but I’m not sure of what the correct syntax would be for referencing a variable from within the ansible_facts variable. Would anybody have a recommendation?

when: expressions are already in a Jinja context, so you never need (or want) mustaches in them.

With zero testing, assuming you’re on the right track, the expression you show above would be spelled like this:

when:
  - ansible_facts[item]['active'] | default(false) | bool

And if that doesn’t work, we can make up something else. :)

I didn’t want to leave this hanging, but this is what ended up working for me:

    - name: Set interface names
      ansible.builtin.set_fact:
        interface_list: "{{ ansible_facts.interfaces | select('search', '\\A(eth|eno|enp)') | list }}"

And in another playbook for the writing of the interfaces:

  - name: Write interfaces
    local_action:
      module: netbox.netbox.netbox_device_interface
      netbox_url: "{{ lookup('env', 'NETBOX_API') }}"
      netbox_token: "{{ lookup('env', 'NETBOX_TOKEN') }}"
      data:
        name: "{{ ansible_facts[item]['device'] }}"
        label: "{{ ansible_facts[item]['device'] }}"
        device: "{{ ansible_facts['hostname'] }}"
        type: "{{ 'SFP+ (10GE)' if ansible_facts[item]['speed'] == 10000 else '1000BASE-T (1GE)' }}"
        enabled: "{{ true if ansible_facts[item]['active'] is true else false }}"
        mac_address: "{{ ansible_facts[item]['perm_macaddress'] if ansible_facts[item]['perm_macaddress'] is defined else ansible_facts[item]['macaddress'] }}"
        lag: "{{ ansible_facts['bond0'] if item in ansible_facts['bond0']['slaves'] else omit}}"

Hopefully this will be useful for anybody else who comes along with a similar issue.

Some of those expressions could be spelled in a more canonical way:

        type: "{{ ansible_facts[item]['speed'] == 10000 | ansible.builtin.ternary('SFP+ (10GE)', '1000BASE-T (1GE)') }}"
        enabled: "{{ ansible_facts[item]['active'] }}"
        mac_address: "{{ ansible_facts[item]['perm_macaddress'] | default(ansible_facts[item]['macaddress']) }}"
        lag: "{{ item in ansible_facts['bond0']['slaves'] | ansible.builtin.ternary(ansible_facts['bond0'], omit}}"
1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.