Reputation: 39129
Whenever I try to make Ansible interpret a nested variable — so, a variable inside another variable — I cannot get the result I expect.
Given the variables:
key: bar
foo:
bar: baz
foo_bar: baz
I have tried those three approaches without much luck to dynamically access the key bar
of the dictionary foo
or the key foo_bar
, when constructed from the value of key
:
- ansible.builtin.debug:
msg: "{{ foo[{{ key }}] }}"
But, I get the error:
'template error while templating string: expected token '':'', got ''}''. String: {{ foo[{{ key }}] }}'
- ansible.builtin.debug:
msg: "{{ foo_{{ key }} }}"
But, I get a similar error
'template error while templating string: expected token ''end of print statement'', got ''{''. String: {{ foo_{{ key }} }}'
- ansible.builtin.debug:
msg: "{{ foo['{{ key }}'] }}"
And here, I get the error
The task includes an option with an undefined variable. The error was: 'dict object' has no attribute '{{ key }}'
I was expecting to get the value of foo.bar
or foo_bar
, so baz
.
What would be the correct approach to achieve this?
Upvotes: 4
Views: 11589
Reputation: 39129
As advised in the Frequently Asked Questions of Ansible, moustache do not stack.
Another rule is ‘moustaches don’t stack’. We often see this:
{{ somevar_{{other_var}} }}
The above DOES NOT WORK as you expect, if you need to use a dynamic variable use the following as appropriate:
{{ hostvars[inventory_hostname]['somevar_' ~ other_var] }}
For ‘non host vars’ you can use the vars lookup plugin:
{{ lookup('vars', 'somevar_' ~ other_var) }}
So, there are two cases where this will apply:
When trying to access a key of a dictionary from a variable, you would simply use the variable as is, remembering that, when you are inside the expression delimiters {{ ... }}
, a string will be interpreted as a variable, if not enclosed inside simple or double quotes.
- ansible.builtin.debug:
msg: "{{ foo[key] }}"
vars:
key: bar
foo:
bar: baz
When trying to construct the name of the variable or the key of a dictionary from a variable, you will have to use the concatenation operator, ~
:
- ansible.builtin.debug:
msg: "{{ foo['foo_' ~ key] }}"
vars:
key: bar
foo:
foo_bar: baz
You might also need to use the vars
lookup to access a dynamic variable:
- ansible.builtin.debug:
msg: "{{ lookup('vars', 'foo_' ~ key) }}"
vars:
key: bar
foo_bar: baz
Side notes:
Do use the vars
lookup — lookup('vars', 'somevar_' ~ other_var)
— and not the vars
dictionary — vars['somevar_' ~ other_var]
, as it was never intended to be an Ansible feature and will be removed in future version
Short history,
vars
is a leftover from previous code that used it to pass variables to template, it was never intended for external use and most of the time didn't template anything.Unrelated changes allowed it to template 'sometimes' but this was never on purpose, the only reason it was not removed is because some people relied on it, that had discovered by looking at the code and/or other people that had already been using it. Even though it has been our intention for a long time to deprecate and remove the
vars
construct, lack of a good way to trigger a runtime message has kept us from doing so.We created 2 alternatives via lookups
varnames
andvars
, which might not be as flexible as a dict but also would not chew up memory for unneeded access, since most users just want to match a small subset of existing variables.
Source: https://github.com/ansible/ansible/issues/74904#issuecomment-854137949
It is more advisable to use the right concatenation operator, ~
than the math operator +
as advised in the Ansible documentation for the reason raised in Jinja documentation:
Usually the objects are numbers, but if both are strings or lists, you can concatenate them this way. This, however, is not the preferred way to concatenate strings! For string concatenation, have a look-see at the
~
operator.
Source: https://jinja.palletsprojects.com/en/2.11.x/templates/#math
Upvotes: 7