how to append to a list in jinja2 for ansible
Solution 1
Try below code:
{% set port = '1234' %}
{% set server_ip = [] %}
{% for ip in host_ip %}
{{ server_ip.append( ip+":"+port ) }}
{% endfor %}
{{ server_ip|join(',') }}
You ll get:
192.168.56.14:1234,192.168.56.13:1234,192.168.56.10:1234
Solution 2
That worked for me:
- set_fact:
devices: >-
{% for ip in host_ip %}{{ ip }}:1234{% if not loop.last %},{% endif %}{% endfor %}
If you still want to use do
then add
jinja2_extensions = jinja2.ext.do
to your ansible config file and change
{% do server_ip.append({{ ip }}:{{ port }}) %}` to `{% do server_ip.append({ip:port}) %}`
Solution 3
I didn't like any of the answers, they feel too hacky (having to worry about outputting None
, or spurious whitespace using other techniques), but I think I've found a solution that works well. I took inspiration from this answer on a related question and realized that you can call set
multiple times for the same variable and seemingly not incur any penalty.
It's still a tad hacky, because I don't think it's intended to work like this (then again, several design decisions in Jinja make me scratch my head, so who knows).
{% set server_ip = server_ip.append({{ ip }}:{{ port }}) %}
Interestingly, while the value is indeed appended to server_ip
, the return value of that append (which we now know very well is None
) isn't assigned back to server_ip
on the LHS. Which led me to discover that the LHS side of the statement seems to be a no-op.
So you can also do this and the append works:
{% set tmp = server_ip.append({{ ip }}:{{ port }}) %}
Yet, if you print tmp
, nothing shows up. Go figure.
Solution 4
One-line solution with map()
and regex
:
{{ ["1.1.1.1","2.2.2.2"]|map('regex_replace', '(.+)', "\\1:1234")|join(', ') }}
map('regex_replace', '(.+)', "\\1:1234")
adds :1234
to any non-empty string (.+)
in the passed array ["1.1.1.1","2.2.2.2"]
. Result:
1.1.1.1:1234, 2.2.2.2:1234
Solution 5
In order to avoid having None
printed all over using {{ server_ip.append( ip+":"+port ) }}
(just spent 20 min debugging this) and if you don't want to use the misleading {% set _ = server_ip.append( ip+":"+port ) %}
, you can go back to Python basics and do the following:
# Remember that [1, 2] + [3] = [1, 2, 3]
{% set server_ip = server_ip + [ip+":"+port] %}
In 99.9% situations this will do the job. However in special cases where you work with very large lists there may be a small performance downside here in terms of memory usage: in the above example, [1, 2] + [3] = [1, 2, 3]
, both [1, 2]
and [1, 2, 3]
(initial and modified list) will coexist in memory for a brief moment, contrary to the append
method which doesn't create additional objects in memory.
Related videos on Youtube
learning fun
Updated on July 09, 2022Comments
-
learning fun almost 2 years
Below is the jinja2 template that i wrote to use in ansible.
{% set port = 1234 %} {% set server_ip = [] %} {% for ip in host_ip %} {% do server_ip.append({{ ip }}:{{ port }}) %} {% endfor %} {% server_ip|join(', ') %}
Below is the my desired output:
devices = 192.168.56.14:1234,192.168.56.13:1234,192.168.56.10:1234
But when i am running the ansible playbook, it is throwing the error as below:
"AnsibleError: teme templating string: Encountered unknown tag 'do'. Jinja was looking for th: 'endfor' or 'else'
Any help would be appreciated..
-
Yu Jiaao about 6 yearsremove
do
from ` {% do server_ip.append({{ ip }}:{{ port }}) %}` ? -
learning fun about 6 yearsEven i tried that but values are not getting appended.
-
-
learning fun about 6 yearsThank you for the reply. But i was looking to assign the output to a variable called "devices".
-
D_K almost 5 yearsif the answer's code is part of an html served as part of flask app,
{{ server_ip.append( ip+":"+port ) }}
is going to be producingNone
as many times as there are steps in the outer for loop. Is there a way around it? -
maugch almost 5 yearsto avoid printing None, I 'd do {{ server_ip.append(...)|default("", True) }}
-
GG_Python over 4 yearsanother way to avoid printing None:
{{ server_ip.append( ip+":"+port ) or ""}}
-
Melendowski over 3 years@GG_Python while your method removes the
None
its still causing a huge amount of whitespace to be printed. Is there really no way to perform these list appends as a{% %}
statement rather than{{ }}
expression? -
Higgs over 3 yearsthis solution appears to be cleaner in jinja, just add commas when it's not the last loop. It doesn't require the use of
default
to removeNone
s -
Jean Monet almost 3 yearsNice find, however this can be misleading. Remember that the
append
method works in place and does not return anything (None
). -
Seldaek over 2 yearsThe cleaner solution to avoid printing anything is to use
{% do server_ip.append(ip+":"+port) %}
-
Hanan Shteingart about 2 yearsI tried this solution but everytime I visit the for loop the variable get resets to it's original value. Do I need to use somekind of namespace to preserve variables in netsed loops
-
Jean Monet about 2 yearsAFAIK the variable you set inside the template will NOT survive outside the template (after it is rendered), if that's what you mean? Unless you pass it as an argument from within that template to a function that stores it outside, and then from there feed it back inside the template
-
Jean Monet about 2 yearsIn Flask you can use the
g
variable/namespace, where you can store stuff and it remains available outside the template. -
litao3rd almost 2 yearsI tried but failed. It seems that
regex_replace
is not a jinja2 filter but located in ansible? -
anemyte almost 2 years@litao3rd indeed, this filter is added by Ansible. Here's the list of Jinja built-in filters: jinja.palletsprojects.com/en/3.1.x/templates/#builtin-filters .
-
litao3rd almost 2 yearsgot it. I found it by google. And I solved my problems by implementing my own filter python function. Thanks very much.