Jinja+facts :: how to get active interface

I’m trying to get in a configuration file the active interface from facts … but i’m still wrong somewhere in the jinja code or in facts accessing …
My tryout looks like this:

# Network interface to monitor (default eth0)
{% set active_eth_list = [] %}
{% for iface in ansible_interfaces if iface != 'lo' %}
    {% set fact_eth_name = "ansible_" ~ iface  %}
    {% if ansible_facts[fact_eth_name]['active'] %}
        {{ active_eth_list.append(iface) }}
    {% endif %}
{% endfor %}

EOS_FST_NETWORK_INTERFACE="{{ active_eth_list[0] }}"

And the error message is

The full traceback is:
Traceback (most recent call last):
  File "/home/adrian/.local/lib/python3.9/site-packages/ansible/template/__init__.py", line 1015, in do_template
    res = myenv.concat(rf)
  File "/home/adrian/.local/lib/python3.9/site-packages/ansible/template/native_helpers.py", line 83, in ansible_concat
    return ''.join([to_text(v) for v in nodes])
  File "/home/adrian/.local/lib/python3.9/site-packages/ansible/template/native_helpers.py", line 83, in <listcomp>
    return ''.join([to_text(v) for v in nodes])
  File "<template>", line 33, in root
  File "/home/adrian/.local/lib/python3.9/site-packages/jinja2/runtime.py", line 857, in _fail_with_undefined_error
    raise self._undefined_exception(self._undefined_message)
jinja2.exceptions.UndefinedError: 'dict object' has no attribute 'ansible_eno1'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/adrian/.local/lib/python3.9/site-packages/ansible/plugins/action/template.py", line 152, in run
    resultant = templar.do_template(template_data, preserve_trailing_newlines=True, escape_backslashes=False, overrides=overrides)
  File "/home/adrian/.local/lib/python3.9/site-packages/ansible/template/__init__.py", line 1049, in do_template
    raise AnsibleUndefinedVariable(e, orig_exc=e)
ansible.errors.AnsibleUndefinedVariable: 'dict object' has no attribute 'ansible_eno1'. 'dict object' has no attribute 'ansible_eno1'
fatal: [fst16.spacescience.ro]: FAILED! => {
    "changed": false
}

MSG:

AnsibleUndefinedVariable: 'dict object' has no attribute 'ansible_eno1'. 'dict object' has no attribute 'ansible_eno1'

So, the name of the fact key is correctly constructed and i can confirm looking in the redis store:

redis-cli get fst16.spacescience.ro | jq | grep ansible_eno1
  "ansible_eno1": {

but somehow the ansible_facts does not have it …
I do have gather_facts: False as the facts are already cached in redis and my expectation is that the cache facts are available to playbook as if they were freshly gathered…

So, does anyone have an idea where i’m wrong?
Thanks a lot

When accessing ansible facts from ansible_facts dictionary, there is no ansible_ prefix in key names. In other words its:

ansible_facts['eno1']

instead of

ansible_facts['ansible_eno1']

actually i do have all ansible_ prefixes in the facts (i did a separate dump to be sure), but the thing is that ansible_facts dictionary seems to not exists, i can only use directly the keys name (instead of ansible_facts["ansible_default_ipv4"]["interface"] i can only use directly ansible_default_ipv4["interface"])
so, if the key is a composed one (name deduced from something else) i see no method to actually access it … but i got what i need with the above example so that’s nice.

This error message:

jinja2.exceptions.UndefinedError: 'dict object' has no attribute 'ansible_eno1'

suggests that ansible_facts variable is defined (it exists) but attribute/key ansible_eno1 does not exist in it which is expected. If ansible_facts did not exist, you would get some other type of error like “variable ansible_facts not found”.

How did you do a dump to check?

So, there are 2 things:

  1. checking:
    1.a Check the content of the redis key for host
    1.b do a dump with this task

  2. i succeeded to do what i needed by using directly the key (which, i think, should not be possible) with:
    EOS_FST_NETWORK_INTERFACE="{{ ansible_default_ipv4["interface"] }}"

moreover i have fact_caching_prefix = in ansible.cfg so it’s not a prefix added by me … ansible is 2.9 (the only one i can install on alma9 without a venv)

To get a better understanding of Ansible facts and ansible_facts dictionary, you should dump them like this:

---
- hosts: all

  tasks:
  - name: "Dump ansible_facts"
    debug:
      msg: "{{ ansible_facts }}"

This is not the same as dumping hostvars[inventory_hostname] presented in your example.

Oh, it’s pretty much possible and intentional. In some early days of Ansible, all Ansible facts were only accessible by using an ansible_ prefixed host variables. In other words by using, e.g:

{{ ansible_default_ipv4 }}

which is the same as:

{{ hostvars[inventory_hostname]['ansible_default_ipv4'] }}

Starting from Ansible 2.5, I believe, there was a decision to namespace all Ansible facts by putting them inside the ansible_facts variable of type dictionary. So the previously mentioned ansible_default_ipv4 variable could also be accessed by using:

{{ ansible_facts['default_ipv4'] }}

Note the missing ansible_ prefix for the dict key compared to the previous case.

To maintain backward compatibility, lots of community created role for example, both of these ways of accessing Ansible facts is supported and valid. On the other hand, there are some security concerns when using {{ ansible_default_ipv4 }} as compared to {{ ansible_facts['default_ipv4'] }} so there is a Ansible configuration option to disable usage of {{ ansible_default_ipv4 }} form:

https://docs.ansible.com/ansible/latest/reference_appendices/config.html#inject-facts-as-vars

The default is true so both ways are supported.

The security concerns are described here:

https://docs.ansible.com/ansible/devel/reference_appendices/faq.html#argsplat-unsafe

To summarize. There are two ways (three if you count hostvars) to get the value of some Ansible fact, old and new one. The old one uses ansible_ prefix for all of the facts. The new one uses ansible_facts dictionary with key names that are not prefixed with ansible_.

3 Likes

Thanks a lot for the comprehensive info!!!
As a small comment on ansible_facts : this should be what is cached in redis isn’t it? So, AFAIK i do not need to dump anything as i can just look into the host key in redis …

I’m not sure how fact caching works, particularly in combination with Redis. It’s possible that what you see in Redis is not 1:1 compared to ansible_facts dictionary variable. In other words it is a good idea to dump/inspect the content of ansible_facts variable to get a better understanding of what you can find inside and how the hierarchy looks like instead of looking inside Redis.

so, sorry for the late answer … using the following tasks:

  tasks:
  - name: "Dump ansible_facts"
    debug:
      msg: "{{ ansible_facts | to_nice_json }}"

  - name: "Dump ansible_facts"
    delegate_to: localhost
    run_once: true
    copy:
        content: "{{ ansible_facts | to_nice_json }}"
        dest: "/home/adrian/ansible/{{ inventory_hostname }}-dump.json"

i could confirm that the output is identical to the redis content.
Thanks a lot ! :slight_smile:

L.E. actually i was too fast to write … i still have ansible_ prefixes in redis output

1 Like