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?
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) }}
Source: https://docs.ansible.com/ansible/latest/reference_appendices/faq.html#when-should-i-use-also-how-to-interpolate-variables-or-dynamic-variable-names
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,
varsis 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
varsconstruct, lack of a good way to trigger a runtime message has kept us from doing so.We created 2 alternatives via lookups
varnamesandvars, 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With