Jinja is a powerful template engine for Python. Inside Ansible it is often used to dynamically create files from templates and fill in placeholders with data.
The Jinja language (now in version 2) also offers features like loops and data manipulation. Building files from loops and data-structures is more complex, especially when considering whitespace and line breaks. It can get tricky very fast.
The general approach (and I think many will agree with me on this one) is to just write e.g. a Jinja loop, see what the rendered outcome is, adjust and tweak it a little bit and make it work. This could be described as an trial-and-error approach.
The whole topic of whitespace control in Jinja within Ansible seems to be manageable with about 50% know-how. The last 50% can be completed by fiddling around.
I have done this myself that way for years. Though you are never an expert, you will get the job done and reach your goal. This article is trying to fill in the missing 50% and enable you (and me) to handle white-spaces and line-breaks effortless.
Each segment has a section TL;DR
which gives you a brief summary of what’s happening - in case you return to this
article and you just quickly need the information.
We will go through a couple of scenarios in detail to understand what actually is going on. In order to give some examples, let’s agree first on common terms:
Syntax Element | Code Example | Term |
---|---|---|
for/while/if |
{% for ... %} |
Tag (within a block). |
{% foobar %}{% endfoobar %} |
{% for i in x %}...{% endfor %} |
Block. |
{{ foobar }} |
{{ i }} |
Variable (replacement). |
All examples will use the following Ansible playbook and template file as starting point:
Code example...
$ tree /tmp/ansible/ /tmp/ansible/ ├── playbook.yml └── template.js $ cat /tmp/ansible/playbook.yml --- - hosts: all gather_facts: false become: false connection: local vars: i: foobar tasks: - name: Deploy a testfile. ansible.builtin.template: src: /tmp/ansible/template.j2 dest: /tmp/ansible/outputfile $ cat /tmp/ansible/template.j2 #jinja2: trim_blocks: True, lstrip_blocks: False ---- {% if true %} {{ i }} {% endif %} ----
The Ansible playbook (playbook.yml
) and Jinja template file will, when run with
Ansible, create a file /tmp/ansible/outputfile
with rendered content.
Code example...
$ cd /tmp/ansible
$ ansible-playbook -i localhost, playbook.yml
Executing playbook playbook.yml
- all on hosts: all -
Deploy a testfile...
wednesday 28 june 2023 22:24:22 +0200 (0:00:00.046) 0:00:00.046 ********
wednesday 28 june 2023 22:24:22 +0200 (0:00:00.044) 0:00:00.044 ********
localhost done
- Play recap -
localhost : ok=1 changed=1 unreachable=0 failed=0 rescued=0 ignored=0
Playbook run took 0 days, 0 hours, 0 minutes, 2 seconds
wednesday 28 june 2023 22:24:24 +0200 (0:00:02.078) 0:00:02.125 ********
===============================================================================
Deploy a testfile ---------------------------------------------------------2.08s
wednesday 28 june 2023 22:24:24 +0200 (0:00:02.078) 0:00:02.123 ********
===============================================================================
ansible.builtin.template ------------------------------------------------ 2.08s
total ------------------------------------------------------------------- 2.08s
With this out of the way, we can have a look at how we can influence white-spaces and line-breaks in Jinja templating within Ansible.
lstrip_blocks and trim_blocks
TL;DR
lstrip_blocks: False
: IfTrue
, remove leading tabs/white-spaces from start of line until the next block.trim_blocks: True
: Remove trailing newlines in lines with only block elements.
Details
There are two main switches in Jinja templating in Ansible, that influence the overall template rendering.
lstrip_blocks
trim_blocks
Those are parameters from Jinja whitespace control.
By default Ansible templates have set trim_blocks: True
and lstrip_blocks: False
.
This differs from the default Jinja2 configuration in Python/Jinja. The example template file
/tmp/ansible/template.j2
has set the same parameters in the top of the file (line 2). The line beginning with
#jinja2
could be technically omitted, when left unchanged. I included it for clarification. We will also play around with it later on.
The syntax rules allow two formats of writing this line. Both are interchangeable. You can even mix them if you like:
#jinja2: trim_blocks: True, lstrip_blocks: False
#jinja2: trim_blocks: "true", lstrip_blocks: "false"
#jinja2: trim_blocks: "true", lstrip_blocks: False
#jinja2: trim_blocks: True, lstrip_blocks: "false"
These two parameters are the only ones included in the templating rendering engine from Jinja within Ansible. Here is what they mean:
Parameter | Description |
---|---|
lstrip_blocks |
If set to True/"true" , tabs and spaces will be removed from the beginning of the line to the start of the next block as long as no other elements are in-between. |
trim_blocks |
If set to True/"true" , the first newline after a template tag is automatically removed, if there is nothing between the newline and the template tag. This is being enabled per default within Ansible templates. |
As mentioned: trim_blocks
is set to True/"true"
in Ansible. That is why the template file renders like this:
# cat /tmp/ansible/outputfile
----
foobar
----
and not to
----
foobar
----
The lines with the if condition are removed from the output. The line only contains the if block - which renders no
output) and a following newline. Since trim_blocks: True
applies, the whole line is not rendered.
For Ansible this makes practical sense. Jinja blocks usually do not contain much besides loop, conditions, expressions and other functionality, that - when rendered - usually does not not result in any content.
The lstrip_blocks
setting has no effect in this example. First of all this parameter is disabled by default (and by the
line being present in the template file). Second, there is no
block with leading whitespace in the template file present. The line containing the if-condition does not start with a whitespace or
line-break. So lstrip_blocks
would not even apply if activated.
This example will make it clear:
Code example...
Adding white-spaces before the if-block and keeping the Ansible default setting, will render the template differently.$ cat /tmp/ansible/template.j2
#jinja2: trim_blocks: True, lstrip_blocks: False
----
{% if true %}
{{ i }}
{% endif %}
----
----
foobar
----
----
foobar
----
{%-
and -%}
TL;DR
Remove all white-spaces and new-lines leading or appending until the next encountered block element.
Details
The hyphen switches only address lines within the template. Adding a hyphen into a Jinja block or variable will remove any heading or pending white-spaces and new-lines before or after that element until the next block element. The exact behaviour depends on where the hyphen is being added.
This is a long sentence, so let’s break it down in an example: Adding a hyphen into a variable will remove heading white-spaces and new-lines.
The following example just adds a hyphen and explores what happens.
Code example...
We add a hyphen in front of the variable element:#jinja2: trim_blocks: True, lstrip_blocks: False
----
{% if true %}
{{- i }}
{% endif %}
----
----
foobar
----
#jinja2: trim_blocks: True, lstrip_blocks: False
----
{% if true %}XXXX\n
XX{{- i }}
{% endif %}
----
#jinja2: trim_blocks: True, lstrip_blocks: False
----
{% if true %}
{{ i -}}
{% endif %}
----
----
foobar----
#jinja2: trim_blocks: True, lstrip_blocks: False
----
{% if true %}
{{ i }}
{%- endif %}
----
----
foobar----
{%+
and +%}
TL;DR
{%+
: Disablelstrip_blocks: True
for a specific line.+%}
: Disabletrim_blocks: True
for a specific line.
Details
The plus sign +
has two different effects, depending on where is is used.
Adding a plus character to the beginning of the first Jinja block in a line will disable
lstrip_blocks: True
for that particular line. This works only for the first
block in a line. Any following block on that line will be ignored.
Code example...
The example below contains two white-spaces before the closing if-condition block. Also: `lstrip_bocks: True` has been enabled!#jinja2: trim_blocks: True, lstrip_blocks: True
----
{% if true %}
{{ i }}
{% endif %}
----
----
foobar
----
#jinja2: trim_blocks: True, lstrip_blocks: True
----
{% if true %}
{{ i }}
{%+ endif %}
----
----
foobar
----
----
foobar
XX
----
Adding the +
-character to the closing braces of the first block in a line disables the option
trim_blocks
if set to True/"true"
.
Code example...
This example will preserve the line-breaks that are introduced with the if-condition block. Notice thattrim_blocks: True
has been set and any leading
whitespace and line-breaks should be removed.
#jinja2: trim_blocks: True, lstrip_blocks: True
----
{% if true +%}
{{ i }}
{% endif +%}
----
----
foobar
----
#jinja2: trim_blocks: True, lstrip_blocks: True
----
{% if true %} {{ i }}{% endif +%}
----
or
#jinja2: trim_blocks: True, lstrip_blocks: True
----
{% if true +%} {{ i }}
{% endif +%}-----
----
foobar
----
Summary
The time it took to write this article was well spent. The tinkering with the parameters and settings, trying to find any unexpected behaviour; all that increased my comfort level with the Jinja whitespace control.
The previous approach with just roughly writing the correct template and then hacking the result still works, of course. But after spending time going through the examples in detail and trying to understand what is happening, I find myself spending less time on try-and-error and more often render my templates correct on the first try.
One thing though I have to keep in mind:
Each parameter on its own is easy to handle. Confusion arises in combination with the default
settings for trim_blocks
and lstrip_blocks
which differ from standard Jinja/Python in Ansible. Since the default
settings and the line-operated switches can influence similar behaviour, unexpected things might happen.
Secondly, while the hyphen addresses white-spaces and new-lines, the plus character affects the general template rendering and the effect differs depending on if it is placed in the opening or closing braces.
This general overview served me well so far:
Tweak | Description |
---|---|
{‰- |
Remove all heading white-spaces and new-lines before a block. |
-%} |
Remove all trailing white-spaces and new-lines after the current block. |
{%+ |
Disable lstrip_blocks for the current line. |
+%} |
Disable trim_blocks for the current line. |
#jinja2: trim_bloks: True |
Remove line-breaks from block-lines. |
#jinja2: lstrip_blocks: True |
Remove leading white-spaces before elements. |
I hope this article was able to shed some light on how whitespace control works in Ansible in combination with Jinja.