how to append to a list in jinja2 for ansible

74,544

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.

Share:
74,544

Related videos on Youtube

learning fun
Author by

learning fun

Updated on July 09, 2022

Comments

  • learning fun
    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
      Yu Jiaao about 6 years
      remove do from ` {% do server_ip.append({{ ip }}:{{ port }}) %}` ?
    • learning fun
      learning fun about 6 years
      Even i tried that but values are not getting appended.
  • learning fun
    learning fun about 6 years
    Thank you for the reply. But i was looking to assign the output to a variable called "devices".
  • D_K
    D_K almost 5 years
    if the answer's code is part of an html served as part of flask app, {{ server_ip.append( ip+":"+port ) }} is going to be producing None as many times as there are steps in the outer for loop. Is there a way around it?
  • maugch
    maugch almost 5 years
    to avoid printing None, I 'd do {{ server_ip.append(...)|default("", True) }}
  • GG_Python
    GG_Python over 4 years
    another way to avoid printing None: {{ server_ip.append( ip+":"+port ) or ""}}
  • Melendowski
    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
    Higgs over 3 years
    this 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 remove Nones
  • Jean Monet
    Jean Monet almost 3 years
    Nice find, however this can be misleading. Remember that the append method works in place and does not return anything (None).
  • Seldaek
    Seldaek over 2 years
    The cleaner solution to avoid printing anything is to use {% do server_ip.append(ip+":"+port) %}
  • Hanan Shteingart
    Hanan Shteingart about 2 years
    I 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
    Jean Monet about 2 years
    AFAIK 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
    Jean Monet about 2 years
    In Flask you can use the g variable/namespace, where you can store stuff and it remains available outside the template.
  • litao3rd
    litao3rd almost 2 years
    I tried but failed. It seems that regex_replace is not a jinja2 filter but located in ansible?
  • anemyte
    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-filter‌​s .
  • litao3rd
    litao3rd almost 2 years
    got it. I found it by google. And I solved my problems by implementing my own filter python function. Thanks very much.