Skip to content

Use namespace as global variable in Ansible Jinja templates

A simple task, or so I thought: in a Jinja template keep track of the number of items in a loop. And then use that count afterwards.

Disclaimer: the number of items is not equal the number of times the loop runs, so I can't use the loop variables.

Turns out that Jinja has other opinions, and variables inside a loop are all local. When a variable is changed inside the loop, the scoop of the variable stays local, and once the loop ends the original value of the variable is restored. That's even true for variables which are created outside the loop.


To demonstrate the problem, here is a simple Playbook, just a small "template":

- hosts: all
  gather_facts: True
  any_errors_fatal: True
  force_handlers: True

    - name: Template
        module: template
        src: "/tmp/"
        dest: "/tmp/template.out"

The Playbook is executed:

ansible-playbook -i, /tmp/template.yml


The template is setting a variable, running through a loop and increasing the variable:

{% set counter = 1 %}

Counter before loop: {{ counter }}

{% for i in range(6) %}
{% set counter = counter + 1 %}
Counter in loop ({{ i }}): {{ counter }}

{% endfor %}

Counter after loop: {{ counter }}

One would expect that the variable starts with "1", the loop runs 5 times (the end value of "6" for the range is never reached), and will increase the counter to "6". Instead this happens:

Counter before loop: 1

Counter in loop (1): 2

Counter in loop (2): 2

Counter in loop (3): 2

Counter in loop (4): 2

Counter in loop (5): 2

Counter after loop: 1

Every run of the loop gets a "fresh" copy of the variable value, and the total is never increased beyond "2".


Now one can argue that not too much work should be done in templates, but at some point the work has to be done. A counter is rarely pre-calculated. To solve this kind of problems, Jinja introduced Namespaces in version 2.10. By using a namespace, the variable in the loop is never localized, but instead the variable in the namespace is used. The same simple loop from above with a namespace:

{% set ns = namespace() %}
{% set ns.counter = 1 %}

Counter before loop: {{ ns.counter }}

{% for i in range(1, 6) %}
{% set ns.counter = ns.counter + 1 %}
Counter in loop ({{ i }}): {{ ns.counter }}

{% endfor %}

Counter after loop: {{ ns.counter }}

Results in the following output:

Counter before loop: 1

Counter in loop (1): 2

Counter in loop (2): 3

Counter in loop (3): 4

Counter in loop (4): 5

Counter in loop (5): 6

Counter after loop: 6

Exactly what I'm looking for!


No Trackbacks


Display comments as Linear | Threaded

No comments

Add Comment

Enclosing asterisks marks text as bold (*word*), underscore are made via _word_.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications.
To leave a comment you must approve it via e-mail, which will be sent to your address after submission.
Form options