String concatenation in Jinja2 but removing the last comma - ansible

I am trying to write switch port VLAN configuration and it breaks because of the last comma on the output, I am trying get an output as follows
vlan trunk allowed 2600,2610,2620,2630,2640,2650
but getting
vlan trunk allowed 2600,2610,2620,2630,2640,2650,
my code currently looks like this
{% for i in interfaces|default([]) %}
interface {{ i.port }}
no shutdown
mtu {{ mtu }}
description Link_to_{{ i.peer|lower }}
no routing
vlan trunk native 1
vlan trunk allowed {% for vc in vlans_core %}{% if vc.src == inventory_hostname and vc.dst == i.peer %}{{ vc.tag }},{% endif %}{% endfor %}{% for vc in vlans_core %}{% if vc.dst == inventory_hostname and vc.src == i.peer %}{{ vc.tag }},{% endif %}{% endfor %}
{% endfor %}
I tired the join but it only produced errors.
{{ vc.tag|join(", ") }}
I have also tried loop.last but this also didn't work
{% if not loop.last %},{% endif %}
Any help would be appreciated.

This is a bit hard to answer without an example datastructure. So I'm not sure the following entirely meets your requirement since I only tested against my own guessed test data and I have no exact expected result to compare to. Think about those points for your next question.
Note also that the solution is ugly and could most probably be improved with a better explanation of your final goal (e.g. "I need to filter out vlan tags based on the following condition: ..."
Here I only give an example following the exact original conditions written down in your example template. The solution is to set a variable at the beginning of your outer loop (on interfaces) where you will store relevant tags to later join them at the needed line.
{% for i in interfaces|default([]) %}
{% set trunk_allowed =
(
(
vlans_core
| selectattr('src', '==', inventory_hostname)
| selectattr('dst', '==', i.peer)
)
+
(
vlans_core
| selectattr('dst', '==', inventory_hostname)
| selectattr('src', '==', i.peer)
)
)
| map(attribute='tag')
%}
interface {{ i.port }}
no shutdown
mtu {{ mtu }}
description Link_to_{{ i.peer|lower }}
no routing
vlan trunk native 1
vlan trunk allowed {{ trunk_allowed | join(',') }}
{% endfor %}
I suspect the var definition could be simplified to the following (but I'm not entirely sure, give it a try)
{% set trunk_allowed =
vlans_core
| selectattr('src', 'in', [inventory_hostname, i.peer])
| selectattr('dst', 'in', [inventory_hostname, i.peer])
| map(attribute='tag')
%}

I tried a few and found this has worked, by using the joiner function, which adds string except on the first instance.
{% for i in interfaces|default([]) %}
interface {{ i.port }}
no shutdown
mtu {{ mtu }}
description Link_to_{{ i.peer|lower }}
no routing
vlan trunk native 1
vlan trunk allowed {% set comma = joiner(",") %}{% for vc in inter_core_vlans|default([]) %}
{% if vc.src == inventory_hostname and vc.dst == i.peer %}{{ comma() }}{{ vc.tag }}{% endif %}
{% if vc.dst == inventory_hostname and vc.src == i.peer %}{{ comma() }}{{ vc.tag }}{% endif %}{% endfor %}
{% endfor %}

I don't know what this syntax it. But logically you should put a check on length and item being processed(to check if its last item) and then if its not last item then include ,
Below may not be syntax-wise correct but approach wise some hint
vlan trunk allowed {% for vc in vlans_core %}{% if vc.src == inventory_hostname and vc.dst == i.peer %}{{ vc.tag }}{{(i<interfaces.length) ?,:''}}{% endif %}{% endfor %}{% for vc in vlans_core %}{% if vc.dst == inventory_hostname and vc.src == i.peer %}{{ vc.tag }}{{(i<interfaces.length) ?,:''}}{% endif %}{% endfor %}

Related

Ansible: Remove last comma in nested loop?

I have the following in a jinja2 template. Essentially I'm looping through a dictionary, picking out the name and if it doesn't start with "sh", I'm adding it to this config. However, I would like to have the server comma separated.
servers = {% for item in list_search_peers.json['entry'] %}{% if not item.name.startswith('sh-') %}{{ item.name }}{% endif %}{% endfor %}
Normally i would do something like:
servers = {% for item in list_search_peers.json['entry'] %}{% if not item.name.startswith('sh-') %}{{ item.name }}{% if not loop.last %},{% endif %}{% endif %}{% endfor %}`
However loop.last won't work because I am filtering out one of the values.
My output is:
servers = x.x.x.x:8089,y.y.y.y:8089,
But i want it without the last ,:
servers = x.x.x.x:8089,y.y.y.y:8089
I was trying {% set } to set a variable but had no luck there.
Filter the list first. For example
- set_fact:
my_list: "{{ list_search_peers.json.entry|
rejectattr('name', 'match', '^sh-(.*)$')|
list }}"
then use it in the template
servers = {% for item in my_list %}{{ item.name }}{% if not loop.last %},{% endif %}{% endfor %}

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)) }}

Ansible/Jinja: condition with an undefined statement

I need to iterate over all hosts and generate config file for hosts that are not contained in group somegroup:
{% for host in groups.all if host not in groups['somegroup'] %}
But if somegroup does not exist, it fails (argument of type 'StrictUndefined' is not iterable).
How do I write this correctly to avoid two different for cycles:
{% if groups['somegroup'] is defined %}
{% for host in groups.all if host not in groups['somegroup'] %}
...
{% endfor %}
{% else %}
{% for host in groups.all %}
...
{% endfor %}
{% endif %}
I think you're looking for the default filter:
{% for host in groups.all if host not in groups['somegroup'] | default([]) %}

Resources