How to retrieve fact with a dynamic fact name?

I’m trying to call out to a task list with include_tasks where I want the task list to set a particular named fact, passed as a variable to the task list. I can set the fact, but it’s not appearing in ansible_facts as I would expect.

Here’s a playbook with five tasks:

  1. Sets the fact according to a fact name passed as a variable
  2. Retrieves the fact, proving it’s set
  3. Gets a fact from ansible_facts, showing how I expect that to work
  4. Gets the fact by name, which doesn’t work as I expect
  5. Gets the fact according to a fact name passed as a variable, what I’m trying to achieve

Can someone explain why step 4 doesn’t work, and how it should be done?

---
- hosts: all
  tasks:
  - name: Set first fact with dynamic fact name
    ansible.builtin.set_fact: {"{{ fact_name }}":"hello world"}
    vars:
      fact_name: "fact1"
  - name: Get first fact as variable - WORKS
    ansible.builtin.debug:
      var: fact1
  - name: Get processor cores from ansible_facts - WORKS
    ansible.builtin.debug:
      var: ansible_facts["processor_cores"]
  - name: Get first fact from ansible_facts - DOESN'T WORK
    ansible.builtin.debug:
      var: ansible_facts["fact1"]
  - name: Get first fact from ansible_facts with dynamic fact name - DOESN'T WORK
    ansible.builtin.debug:
      var: ansible_facts[fact_name]
    vars:
      fact_name: "fact1"

The reason is that ansible.builtin.set_fact does not create facts, but variables (more precisely: host-level variables) with a fixed value. The same happens if you don’t use a dynamic fact name, but a static fact name. You can fix that by making it a cacheable fact by setting cacheable=true, which will create a host-level variable and a “real” Ansible fact:

  - name: Set first fact with dynamic fact name
    ansible.builtin.set_fact:
      "{{ fact_name }}": hello world
      cacheable: true
    vars:
      fact_name: "fact1"

See ansible.builtin.set_fact module – Set host variable(s) and fact(s). — Ansible Community Documentation for more infos.

4 Likes

Thanks! I saw cacheable: true and could see that it was something important, but I couldn’t make head or tail of the explanation. 7 levels of precedence sounds like acute demonology. I used your example and now I made it all go the way I wanted.

The reason is that ansible.builtin.set_fact does not create facts, but variables

Perhaps it should be called set_variable, and have an option set_fact: true? Or, if that can’t work for demonology-related reasons, perhaps a slightly simpler warning in the docs?

1 Like

Well, to be honest, I wasn’t really aware of this behavior before myself, since I always used set_fact as a sort of “set variable to a constant value” (as opposed to vars, which sets them to something that’s evaluated on every usage). I agree that set_variable would be a better name, but I guess the naming comes from the early history of Ansible and hasn’t been changed since it’s used everywhere.

1 Like

There is an irc log somewhere with a 3 week flame war about naming set_fact (originally set_var IIRC)

2 Likes

Perhaps it should be called set_variable , and have an option set_fact: true ?

Please don’t give them any ideas :frowning:

Seagulls are called seagulls despite them not being exclusively tied to the sea, humanity moves on and doesn’t need to rewrite every piece of literature in existence for correctness sake. I say this with peace and love, of course!

1 Like

my proposal looong ago was:

set_var: value=x scope=host|play|fact
3 Likes

In my particular case, I wanted a way to set a “thing” (fact, variable, don’t mind) with a name string and retrieve it with the same name string. Having an ansible_vars array similar to ansible_facts would have worked just as well. But I couldn’t find anything like that. Seems like there’s a surprising asymmetry there.