One thing that frequently causes quite some pain in Ansible are automatic conversion of templated strings to types (here’s yet another occurence which triggered me finally writing this post, after seeing many similar “bug reports” or user questions in many different collection repos, on IRC, matrix, mailing list, …, and having thought about this multiple times already).
The main problem here is that if Ansible encounters a string that uses Jinja2 templating, it does not know whether a) it’s a expression or b) a template. Before I continue, let me make clear what I mean with that:
- An expression is something like
{{ expression }}
where the user wantsexpression
to be evaluated and hopes to obtain the value returned by this expression without further modifications. - A template is something that can also look like
{{ expression }}
, for example{{ lookup("ansible.builtin.file", "/path/to/file.json") }}
, but can also contain more text around Jinja2 constructs (also conditionals etc.). Users generally expect this to be a string.
Now since Ansible cannot really distinguish between the two, it does a lot of magic to try to get this right. For example, if {{ expression }}
evaluates to something that looks like a Python dict (JSON dicts often do that), it will parse it. This makes it really hard to handle strings containing JSON dicts in Ansible, like in {{ lookup("ansible.builtin.file", "/path/to/file.json") }}
, since the unexpected conversions (especially the conversion back to strings if passed to a string parameter) usually results in something that isn’t JSON.
Sometimes these conversions are also used, for example by combining JSON with a template that’s full of Jinja2 constructs like {% for %}
etc., which Ansible in some situations happily parses without further instructions. This causes more confusion since that parsing only happens in some cases, not in others. (I remember a forum thread recently with such a problem.)
So it would be great if there would be an easy way to tell Ansible whether something is an expression or a template. How about using YAML tags for that? Similar to !unsafe
, which tells Ansible a value is “unsafe” and shouldn’t be templated; for example, !unsafe "{{ 'foo' }}"
will always be the string {{ 'foo' }}
and will never get evaluated. So I’m proposing something like:
- community.general.foo:
dict_parameter: !expression lookup("ansible.builtin.file", "/path/to/file.json")
string_parameter: !template "{{ some_dictionary | to_json }}"
Then Ansible safely knows that the result of lookup("ansible.builtin.file", "/path/to/file.json")
is whatever the file lookup returned and won’t automatically try to parse it before passing it on, and it knows that the result of "{{ some_dictionary | to_json }}"
is a string that it shouldn’t try to parse.
If these tags are used consequently, I think all problems would go away, assuming I didn’t overlooked something
So, my questions:
- Am I the first with this idea? (I would be a bit surprised if yes.) (Or does it even already exist in Ansible and I managed to not notice?)
- Are there some special cases I forgot, where this breaks down even if you use these tags every time you’re templating something?
- Bonus question: can you think of nicer tag names? For expressions I think
!expr
is shorter to type (maybe even!e
?), and for templates maybe!templ
or even!t
.