I almost missed that Ansible 2.24 will be defaulting INJECT_FACTS_AS_VARS to False in future, meaning that facts like {{ ansible_os_distribution }} will stop working.
Instead, we’ll have to write {{ ansible_facts['distribution'] }}.
As somebody on the Fediverse amusingly wrote:
Happy (careful) search & replace day through all your roles and playbooks to those who celebrate.
The original behavior can be brought back (until that is deprecated) with an ansible.cfg setting:
The good thing is that with ansible-core 2.20+, it will tell you when you run the role/playbook/task file which places use the old way to access facts. So it’s not that hard to find these places. Or at least most of them…
The bad thing is that you have to pay careful attention if you’re using plugins such as community.sops.load_vars, since you should better not change “facts” they created since they will change to real variables once ansible-core finally allows it (hopefully in 2.21). It would have been nicer if the deprecation would have only be done after allowing plugins to set variables (and not just facts)…
Maybe the idea is to make it easier to distinguish between facts and variables. Right now their relationship is quite confusing. (And ansible.builtlin.set_fact’s name implying that it “sets facts” while it usually sets variables is not helping…)
Is there an ansible.builtin.set_var in our future? If I’m going to have to examine all my existing set_facts and make syntax changes, would much rather change to a module that actually does what I want rather than uglyfy the syntax to reference “facts” that aren’t actually facts.
In all my years of Ansible use (and accumulated code), I don’t believe I have a single instance of using set_fact to set a fact rather than a variable.
If I understand the discussion above, I’d strongly encourage the core team to reconsider the timing / ordering of these changes. We need a set_var before the current behavior of set_fact wrt variables is taken away.
ansible.builtin.set_fact is already setting variables, you need to provide cacheable: true to make it also set facts. It does make sense to split up these two things though IMO.
This only deprecates the default value of ‘injection’, so we can turn it off by default.
There is a plan on eventually deprecating the injection itself, but that will come later, after the revamp of register to add projections (probably in next version) and adding a way for actions to cleanly and purposefully set variables.
Long story about set_fact: set_fact was initially named set_var but this was misleading and set_host_scoped_var was … too long. So after many discussions set_fact was the name of compromise, misleading one as it did not ‘set a fact’, but a higher priority hope scoped variable with higher priority than facts. Eventually cacheable was added to allow for it also to be stored in the cache … but what really happens under the hood is that 2 variables get created, the aforementioned host scoped variable with higher precedence AND an actual fact, both set to the same value. The fact, gets cached and has ‘fact level precedence’ once retrieved from the cache, in subsequent runs, as in the ‘current’ run the host scoped variable will still exist and have the higher precedence.
I know it was a typo, but I really like the idea of a “hope scoped variable”. Having seen it, I wonder how the field ever advanced this far without the concept.
@felixfontein There is also secondary problem with this change, because it is executing on all variables in play.
This means that if someone defines their extra var with deprecated name, it will trigger only when this variable is consumed in roles, creating confusion that the role is causing it, not actual user defined variable.
Ansible-lint is also not catching this warning, which is not helping because if we could identify it in vars/facts for our roles, we could at least prevent it from our side, leaving issue only coming from user defined extra vars.
@felixfontein This is only topic about INJECT_FACTS_AS_VARS and 2.20 so I added it here.
Issue I described is specific to 2.20+ and deprecation warning, that is not accurate and it is causing confusion because of variable expansion during runtime.
Issue:
Playbook with 10 different roles, each using input variable __my_domain
Playbook executed with __my_domain: "{{ ansible_domain }}"
Result: 100+ warnings for each task that uses __my_domain
This causes lot of confusion because it appears that roles are problem, not input variable. ansible-lint is also not showing this warning either.
I think this is severely overstating how much potential there is for confusion.
Here’s setting a variable referenced in a role from the command line:
$ ansible-playbook test.yml -e 'foo={{ ansible_fqdn }}'
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [localhost] *************************************************************************************************************
TASK [Gathering Facts] *******************************************************************************************************
ok: [localhost]
TASK [foo : debug] ***********************************************************************************************************
[WARNING]: Deprecation warnings can be disabled by setting `deprecation_warnings=False` in ansible.cfg.
[DEPRECATION WARNING]: INJECT_FACTS_AS_VARS default to `True` is deprecated, top-level facts will not be auto injected after the change. This feature will be removed from ansible-core version 2.24.
Origin: <CLI option '-e'>
{{ ansible_fqdn }}
Use `ansible_facts["fact_name"]` (no `ansible_` prefix) instead.
ok: [localhost] =>
msg: ip-10
Vs. setting the variable in the playbook:
[DEPRECATION WARNING]: INJECT_FACTS_AS_VARS default to `True` is deprecated, top-level facts will not be auto injected after the change. This feature will be removed from ansible-core version 2.24.
Origin: /home/testing/test.yml:4:10
2 gather_facts: true
3 vars:
4 foo: "{{ ansible_fqdn }}"
^ column 10
Use `ansible_facts["fact_name"]` (no `ansible_` prefix) instead.
Vs. getting it from a host variable:
[DEPRECATION WARNING]: INJECT_FACTS_AS_VARS default to `True` is deprecated, top-level facts will not be auto injected after the change. This feature will be removed from ansible-core version 2.24.
Origin: /home/testing/group_vars/all.yml:1:6
1 foo: "{{ ansible_fqdn }}"
^ column 6
Use `ansible_facts["fact_name"]` (no `ansible_` prefix) instead.
In each case I tested the warning told me exactly where the issue originated rather than pointing at the role that merely used it, even though I did my best to trick it by referencing the variable indirectly in the role:
- debug:
msg: "{{ bar }}"
vars:
bar: "{{ foo[:5] }}"
Unfortunately it does not tell you which fact was being accessed as a variable, it only gives you a pointer to the line which caused the access to occur. In my case I have some where the line in question does not reference any facts-as-variables directly, but probably does so via a chain of template expressions, and figuring out where the variable reference exists is not trivial.
It would be very helpful if the deprecation warning message indicated which specific fact was being accessed as a variable.
From my experience the error message always points to the deepest layer, so there shouldn’t be a chain of template expressions - at least from my experience so far. Do you have a specific example where this is the case?
I think the reason for this change is to improve the flexibility and control that users have over their Ansible playbooks. By defaulting INJECT_FACTS_AS_VARS to False, Ansible is shifting the responsibility of accessing facts to the user, rather than relying on the automatic injection of variables.
My suggestion is to update your playbooks to use the new syntax, as it is more explicit and consistent with Ansible’s philosophy of variable handling. If you need to preserve the original behavior, you can indeed use the ansible.cfg setting to override the default value.
I do, but it’s not publicly available so I’ll have to track it down and then try to make a minimal reproduction example.
I’m not sure what prompted this suggestion - I’m fine with updating my playbooks and roles, I’ve been doing it as I get time, but I need the hints from Ansible to know what needs to be changed as I can’t just scan every single ansible_<foo> reference in my entire stack (~2,500 files) to change them all.
OK, I figured it out… and I suppose it means the deprecation message could use a bit of improvement.
In my case I have a role which sets a cached fact named source_python_interpreter, and a few playbooks were evaluating that fact as a plain variable. Changing those references to ansible_facts['source_python_interpreter'] eliminates the deprecation warning, but the warning message includes a reference to the ansible_ prefix when the fact being evaluated may not have had an ansible_ prefix
I was able to replicate this by commenting out self._variable_manager.set_nonpersistent_facts(target_host, result_item['ansible_facts'].copy()) in plugins/strategy/__init__.py (so I don’t have to go through an actual cache ).
You’re right — I misunderstood the core issue at first.
The challenge isn’t migrating to the new syntax, but identifying exactly which fact is being accessed indirectly when Ansible raises the warning.
I agree that having the specific fact name in the deprecation message would make large codebases much easier to update.