assignment of undefined variables in Jinja2 - ansible

I got this piece of YAML and i want jinja2 to assign and create item.menu, if it is not previously defined.
data:
- name: bar
menu: blah
- name: foo
This is my code, the error-output ist "template error while templating string: expected token 'end of statement block'"
{% for item in data %}
{% if item.menu is not defined %}
{% set item.menu=item.name %}
{% endif %}
{% endfor %}
Any Help about what I did wrong would be much apretiated :)
Greetings, Hendrik

You're question is not clear but here is my answer. I'll make everything explicit :
{% for item in data %}
{% if item.menu is not defined %}
{{ item.nameĀ }}
{% else %}
{{ item.menu }}
{% endif %}
{% endfor %}

Related

Using Twig to generate Markdown, how to indent blocks in twig?

I have very few hours of use with Twig so I probably missed an important tip; please forgive me if this is a trivial question.
I'm using Twig with PHP for the generation of markdown files.
My twig file contains one or more {% for %}...{% endfor %} block and inside a for-loop, a few {% if %}...{% endif %} and ... I can't make any indentation in my Twig otherwise the spaces are also present in my output.
A very stupid example: https://twigfiddle.com/fb6nzq (use the Show raw result to make sure to see the spaces before the word true).
If I don't indent my {% if %}...{% endif %}, I got the correct result (https://twigfiddle.com/fb6nzq/2) but I don't have anymore indentation of blocks in my template.
In my real world twig file, I can have multiple {% endif %} like below and it becomes unreadable.
{% for (variable) %}
{% if (condition) %}
{% if (condition) %}
{% if (condition) %}
{% endif %}
{% endif %}
{% endif %}
{% endfor%}
So ... do you know if there is a wonderful trick to keep an indentation in your code but without having an impact on the output?
Desired twig template:
{% for (variable) %}
{% if (condition) %}
{% if (condition) %}
{% if (condition) %}
{% endif %}
{% endif %}
{% endif %}
{% endfor%}
You can use a dash - on any opening or closing twig expression where:
a dash on the closing expression would do a trim on the left.
{% if true -%}
a dash on the opening expression would do a trim on the right
{%- if true %}
a dash on the both the opening and closing expression would do a trim on the both sides
{%- if true -%}
Mind that: this is acting as a PHP trim, so that means that it will also trim your line feeds!
Here is an example:
{% for i in 1..5 %}
{% if true %}
{% if true %}
{%- if true %}
foo
{%- endif %}
{%- endif %}
{% endif %}
{% endfor %}
That renders:
foo
foo
foo
foo
foo
This is testable here: https://twigfiddle.com/1awhzk
Also note: that there is a spaceless tag to achieve those kind of things.

How to trim last character when using ansible jinja loop

My template like as blow
{% if hostvars[inventory_hostname].local_zk_server_id is defined %}
zookeeperServers={% for host in {{ groups[{{ target_hosts }}] %}}
"{{ hostvars[host].inventory_hostname }}:2181,"
{% endfor %}
{% endif %}
output ishost1:2181,host2:2181,host3:2181,
How to trim last comma
There are several possible gotchas in your above template regarding variables access. Moreover, rather than trimming the last character in your string, the best solution is probably not to write it. Here is a better solution IMO in my below example fixing all the problems I'm referring to:
{% set zookeeperServers=[] %}
{% if hostvars[inventory_hostname].local_zk_server_id is defined %}
{% for host in groups[target_hosts] %}
{% zookeeperServers.append(hostvars[host].inventory_hostname + ":2181") %}
{% endfor %}
zookeeperServers="{{ zookeeperServers | join(',') }}"
{% endif %}

Ansible: get list of hosts in comma separated value

I have the following loop in a template:
{% for host in groups['dbnodes'] %}
{{ hostvars[host]['ansible_eth0']['ipv4']['address'] }}
{% endfor %}
the issue is that it gives the output in list of ip's and I need it in comma separated value. Any idea how to achieve that?
the answer I get look like this:
10.0.0.190
10.0.0.117
10.0.0.151
but I need it like this:
10.0.0.190,10.0.0.117,10.0.0.151
A quick fix to your Jinja2 template:
{% for host in groups['dbnodes'] -%}
{{ hostvars[host]['ansible_eth0']['ipv4']['address'] }}{% if not loop.last %},{% endif %}
{%- endfor %}

Short version of if var is defined for ansible templates

Jinja2 in Ansible templates allows this type of expression in templates:
{% if foobar is defined %} foo_bar = {{foobar}} {% endif %}
{% if barfoo is defined %} bar_foo = {{barfoo}} {% endif %}
etc.
Is there any shorter version to say 'do not print this line if its variable is not defined?
Something like foo_bar = {{foobar|skip_this_line_if_undefined}}?
You can use the default(omit) filter. For details have a look at the documentation.
You could use a macro.
{% macro line(key, value) -%}
{% if not value|none %}{{ key }} = {{ value }}{% endif %}
{%- endmacro %}
Then just call the macro for every key/value pair.
{{ line('foo_bar', foobar) }}
{{ line('bar_foo', barfoo) }}
Could be problematic in edge cases though. If foobar or barfoo are not defined it probably will raise an error. In the macro, value in any case would be defined, so the condition is defined doesn't make sense any more. But if null/none actually is a valid value for any of the variables, you hit the wall...
A bit longer but probably water proof:
{% macro line(key, value) -%}
{% if value != omit %}{{ key }} = {{ value }}{% endif %}
{%- endmacro %}
{{ line('foo_bar', foobar|default(omit)) }}
{{ line('bar_foo', barfoo|default(omit)) }}

Multiple groups when iterating in a template file

This article from Ansible.com shows how you can iterate over a group inside of a template file: https://support.ansible.com/hc/en-us/articles/201957887-How-to-loop-over-a-list-of-hosts-in-a-group-inside-of-a-template-
It shows the following code:
{% for host in groups['db_servers'] %}
{{ hostvars[host]['ansible_eth0']['ipv4']['address'] }}
{% endfor %}
It works beautifully, but the servers I want to iterate over are defined by being in multiple groups. So imagine that I want to iterate over all of the servers that are in BOTH the db_servers and qa groups. How can I do this?
I tried to specify the intersection of the group in the same manner I do in my playbook, but that doesn't work. So, when I try:
{% for host in groups['db_servers:&qa'] %}
{{ hostvars[host]['ansible_eth0']['ipv4']['address'] }}
{% endfor %}
I get the following error:
fatal: [54.173.247.115] => {'msg': "AnsibleUndefinedVariable: One or more undefined variables: 'dict object' has no attribute 'db_servers:&qa'", 'failed': True}
Any suggestions on how to iterate over multiple groups in a template file?
Ansible has the intersect filter. See Set Theory Filters.
{% for host in groups['db_servers'] | intersect(groups['qa']) %}
{{ hostvars[host]['ansible_eth0']['ipv4']['address'] }}
{% endfor %}
You could wrap your loop in another for the two server groups:
{% for svrs in ['db_servers', 'qa'] %}
{% for host in groups[svrs] %}
{{ hostvars[host]['ansible_eth0']['ipv4']['address'] }}
{% endfor %}
{% endfor %}

Resources