Jinja2 syntax error: expected token 'end of print statement' - ansible

The below Jinja2 template is used by Ansible to generate a named zone configuration file. It fails during playbook execution (using template module) but the error is a bit cryptic to me so I didn't manage to fix it.
{# This template defines a named zone based on the dictionnary of the containers metadata #}
# IN SOA {{ net_search_domain }}. admin.{{ net_search_domain }}. (
{{ ansible_date_time['epoch'] }} ; serial
3600 ; refresh
1800 ; retry
604800 ; expire
600 ) ; ttl
{{ ansible_hostname }} IN A {{ ansible_{{net_int_dmz_untrusted}}['ipv4']['address'] }}
{{ ansible_hostname }} IN AAAA {{ ansible_{{net_int_dmz_untrusted}}['ipv6']['address'] }}
{% for items in net_containers %}
{{ item.value.rproxy_be }} IN A {{ item.value.ipv4_container }}
{{ item.value.rproxy_be }} IN AAAA {{ item.value.ipv6_container }}
{% if {{ item.value.rproxy_fe }} != {{ item.value.rproxy_be }} %}
{{ item.value.rproxy_fe }} IN A {{ ansible_{{net_int_dmz_untrusted}}['ipv4']['address'] }}
{{ item.value.rproxy_fe }} IN AAAA {{ ansible_{{net_int_dmz_untrusted}}['ipv6']['address'] }}
{% endif %}
{% endfor %}
rtr IN AAAA xxxx:yyyy:zzzz:wwww:208:a2ff:fe0e:d127
printer IN AAAA xxxx:yyyy:zzzz:wwww:3205:5CFF:FE7C:6240
The error at Ansible run time:
failed: [192.168.11.6] (item={'file': '/home/nicolas/Documents/Configurations/Ansible/server/roles/containers/templates/named_domain.j2', 'target': '/srv/docker/dns/example.net'}) => {"changed": false, "item": {"file": "/home/nicolas/Documents/Configurations/Ansible/server/roles/containers/templates/named_domain.j2", "target": "/srv/docker/dns/example.net"},
"msg": "AnsibleError: template error while templating string: expected token 'end of print statement', got '{'. String: {# This template defines a named zone based on the dictionnary of the containers metadata #}
\n# IN SOA {{ net_search_domain }}. admin.{{ net_search_domain }}. (\n {{ ansible_date_time['epoch'] }} ; serial\n 3600 ; refresh
\n 1800 ; retry\n 604800 ; expire\n 600 ) ; ttl\n\n{{ ansible_hostname }} IN A {{ ansible_{{net_int_dmz_untrusted}}['ipv4']['address'] }}
\n{{ ansible_hostname }} IN AAAA {{ ansible_{{net_int_dmz_untrusted}}['ipv6']['address'] }}\n\n{% for items in net_containers %}\n{{ item.value.rproxy_be }} IN A {{ item.value.ipv4_container }}
\n{{ item.value.rproxy_be }} IN AAAA {{ item.value.ipv6_container }}\n{% if {{ item.value.rproxy_fe }} != {{ item.value.rproxy_be }} %}\n{{ item.value.rproxy_fe }} IN A {{ ansible_{{net_int_dmz_untrusted}}['ipv4']['address'] }}\n{{ item.value.rproxy_fe }} IN AAAA {{ ansible_{{net_int_dmz_untrusted}}['ipv6']['address'] }}
\n{% endif %}\n{% endfor %}\n\nrtr IN AAAA xxxx:yyyy:zzzz:wwww:208:a2ff:fe0e:d127\nprinter IN AAAA xxxx:yyyy:zzzz:wwww:5CFF:FE7C:6240\n\n\n"}

Adding an answer here as the first one solves half of the error.
The below Jinja2 template is the final one which provides the expected output:
# IN SOA {{ net_search_domain }}. admin.{{ net_search_domain }}. (
{{ ansible_date_time['epoch'] }} ; serial
3600 ; refresh
1800 ; retry
604800 ; expire
600 ) ; ttl
{% set addr4 = hostvars[inventory_hostname]['ansible_default_ipv4']['address'] %}
{% set addr6 = hostvars[inventory_hostname]['ansible_default_ipv6']['address'] %}
{{ ansible_hostname }} IN A {{ addr4 }}
{{ ansible_hostname }} IN AAAA {{ addr6 }}
{% for item in net_containers %}
{{ net_containers[item].rproxy_be }} IN A {{ net_containers[item].ipv4_container }}
{{ net_containers[item].rproxy_be }} IN AAAA {{ net_containers[item].ipv6_container }}
{% if net_containers[item].rproxy_be != net_containers[item].rproxy_fe %}
{{ net_containers[item].rproxy_fe }} IN A {{ addr4 }}
{{ net_containers[item].rproxy_fe }} IN AAAA {{ addr6 }}
{% endif %}
{% endfor %}

{{ ansible_hostname }} IN A {{ ansible_{{net_int_dmz_untrusted}}['ipv4']['address'] }}
You can't use nested expressions like that, as Jinja does not do recursive evaluation. To do what you're trying to do, treat the vars as a dict and look up the key that way:
{{ ansible_hostname }} IN A {{ vars["ansible_"+net_int_dmz_untrusted]['ipv4']['address'] }}
Separately, although you didn't ask this, you really will be much happier assigning those expressions to variables, instead of copy-pasting it all over the place:
{# This template defines a named zone based on the dictionnary of the containers metadata #}
{% set the_addr4 = vars["ansible_"+net_int_dmz_untrusted]['ipv4']['address'] %}
{% set the_addr6 = vars["ansible_"+net_int_dmz_untrusted]['ipv6']['address'] %}
{{ ansible_hostname }} IN A {{ the_addr4 }}
{{ ansible_hostname }} IN AAAA {{ the_addr6 }}

Related

taking mount name and size from ansible facts using jinja 2 for loop

I am losing my hope to solve this simple task...
All I need is take parameters "size_total" and "mount" from this loop:
{% for host in groups['test'] %}
{{ hostvars[host]['ansible_facts']['mounts'] }}
{% endfor %}
above gives me this:
[{'mount': '/', 'device': '/dev/mapper/rhel_rhel84-root', 'fstype': 'xfs', 'options': 'rw,seclabel,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota', 'size_total': 18238930944, 'size_available': 14193164288, 'block_size': 4096, 'block_total': 4452864, 'block_available': 3465128, 'block_used': 987736, 'inode_total': 8910848, 'inode_available': 8802255, 'inode_used': 108593, 'uuid': 'f47a2833-0a96-42cc-aafa-172e34efff8a'}, {'mount': '/boot', 'device': '/dev/vda1', 'fstype': 'xfs', 'options': 'rw,seclabel,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota', 'size_total': 1063256064, 'size_available': 852586496, 'block_size': 4096, 'block_total': 259584, 'block_available': 208151, 'block_used': 51433, 'inode_total': 524288, 'inode_available': 523979, 'inode_used': 309, 'uuid': '3126f0dc-4e4d-457e-8102-1f6f7dadc43c'}]
Question: How can I access values from that dictionary ? Is it dictionary or list of dictionaries ?
Thanks in advance!
EDIT:
Thank you #Vladimir Botka.
Now situation looks like this:
I have slightly prettified your example ( with human readable values) and I have values twice, like this. Is there a way to do it so that values are inserted into destination file only once ? I assume some if filter with list will be used here.
mount: / free space: 3.77 GB
mount: /boot free space: 200.91 MB
mount: / free space: 3.89 GB
mount: /boot free space: 200.91 MB
EDIT 2:
I have managed to fix repeating output of file system. In case if someone need it, nested loops solved the issue:
{% set slots = [] %}
{% set size = [] %}
{% set size2 = [] %}
{% for host in groups['test'] %}
{% for i in hostvars[host]['ansible_facts']['mounts'] if i not in slots %}
{{ slots.append(i.mount) }}
{{ size.append((i.size_total - i.size_available )|human_readable ) }}
{{ size2.append(i.size_total|human_readable ) }}
{% endfor %}
{% endfor %}
Filesystem: {{ slots[0] }} Size: {{ size2[0] }} / {{ size[0] }}
Filesystem: {{ slots[1] }} Size: {{ size2[1] }} / {{ size[1] }}
Try this
{% for host in groups['test'] %}
{% for i in hostvars[host]['ansible_facts']['mounts'] %}
mount: {{ i.mount }} size_total: {{ i.size_total }}
{% endfor %}
{% endfor %}

Ansible template mix up order of elements

I have a problem with may came up with a new Ansible version, as it worked before:
I'm passing this block to the ansible template
- monitoring-test-blackbox_exporter:
source: "{{ consul_template_template_dir }}/blackbox_exporter.ctmpl"
destination: "/etc/prometheus/file_sd/blackbox_exporter.json"
create_dest_dirs: true
command_timeout: "60s"
error_on_missing_key: false
grafana_link: "xtkCtBkiz"
This is the template:
# Template configuration
{% for ctmpl in consul_template_templates_config_node %}
# {{ ctmpl | first }}
template {
{% for option, value in ctmpl.items() %}
{% if value is sameas true %}
{{ option }} = true
{% elif value is sameas false %}
{{ option }} = false
{% elif value is string %}
{{ option }} = "{{ value|string }}"
{% elif value is number %}
{{ option }} = {{ value|int }}
{% endif %}
{% endfor %}
ctmpl | first always worked before to filter out first element monitoring-test-blackbox_exporter this is important as we use it later in the template configuration.
I tried several things with sort and select attributes neither of them worked. Does anyone have an idea to get it working again?

Creating text file from jinja template with inputs from csv file

I'm trying to create a text file which goes through the CSV file and populates variables into specific fields.
playbook.yml:
- hosts: localhost
tasks:
- name: "Reading user information"
read_csv:
path: /home/test/vlans.csv
delimiter: ','
register: vlans
- debug: var=vlans
- name: Creating VLANs configuration
template:
src: vlan.conf.j2
dest: /tmp/vlan.conf
Jinja2 Template vlan.conf.j2:
{% for item in vlans %}
!
vlan {{ item.VLAN }}
name {{ item.Description }}
vn-segment {{ item.VNI }}
interface nve1
member vni {{ item.VNI }}
{% endfor %}
and this is a test vlans.csv file:
Tenant,VRF ,VLAN,VNI,Subnet,Description,Good to go
Test,,5,20005,,LAB-Checkpoint-FW-Mgmt,Yes
Test,,208,20208,,LAB-DMZ,Yes Test,,209,20209,,LAB-CSR-MGMT,Yes
Test,10000,210,20210,192.168.12.1/28,LAB-VRF-to-FW,Yes
Prod,,761,20761,,PROD-CORE,Yes
Prod,105,840,20840,172.18.33.1/24,Backups,Yes
Prod,,841,20841,,Transport,Yes
I want to end up with file like in jinja2 template and not repeating line "interface nve 1"
In your question, interface nve1 is inside a loop. It will repeat multiple times in the resulting vlan.conf file.
Use multiple loops in the jinja template to decide what is repeated and what isn't:
{% for item in vlans.list %}
vlan {{ item.VLAN }}
name {{ item.Description }}
vn-segment {{ item.VNI }}
{% endfor %}
interface nve1
{% for item in vlans.list %}
member vni {{ item.VNI }}
{% endfor %}
Note that I've referred to vlans.list in the start of the loops instead of vlans. This is correct as per documentation, but different to the example in the question so it may need adjusting.

Ansible loop over range of letters in template

I'm trying to generate an Ansible template that increments on letters alphabetically rather than numbers. Is there a function similar to range(x) that could help me?
pseudo code example
{% for letter in range(a, d) %}
{{ letter }}
{% endfor %}
expected output
a
b
c
d
Alternatively is there a way to convert a number into it's alphabetical equivalent in Ansible?
{% for i in range(6) %}
{{ convert(i) }}
{% endfor %}
UPDATE
For those who are curious, here's how I ended up applying #zigam's solution. The goal was to create xml tags with every host from a hostgroup.
In my role defaults:
ids: "ABCDEFGHIGJKLMNPQRSTUVWXYZ"
In my template:
{% for host in groups['some_group'] %}
<host-id="{{ ids[loop.index] }}" hostName="{{ host }}" port="8888" />
{% endfor %}
You can iterate over a string:
{% for letter in 'abcd' %}
{{ letter }}
{% endfor %}
If you want to iterate over a range of the alphabet:
{% set letters='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' %}
{% for letter in letters[:6] %} {# first 6 chars #}
{{ letter }}
{% endfor %}
you can use a custom filter plugin to do what you want
in filter_plugins/scratch_filter.py:
def scratch_filter(n):
return chr(n)
class FilterModule(object):
''' Number to Character filter '''
def filters(self):
return {
'scratch_filter': scratch_filter
}
in scratch-template.j2:
{% for x in range(101, 113) %}
{{ x|scratch_filter }}
{% endfor %}
in scratch_playbook.yml
---
- hosts: localhost
tasks:
- name: test loop
template:
src: "{{ playbook_dir }}/scratch-template.j2"
dest: "{{ playbook_dir }}/scratch-template-output.txt"

AnsibleUndefinedVariable: 'list object' has no attribute

Yaml template
bgp:
local: 109
remote: 109
site:
- neighbor:
peer_ip: [ 172.16.110.3, 172.16.110.4 ]
vpnv4:
vrf: Site-1
network: 109.10.1.1
mask: 255.255.255.0
- neighbor:
peer_ip: [ 172.16.120.3, 172.16.120.4 ]
vpnv4:
vrf: Site-2
network: 109.10.1.1
mask: 255.255.255.0
Jinja2 template
router bgp {{ item.bgp.local }}
{% for i in item.bgp.site %}
address-family ipv4 vrf {{ i.vpnv4.vrf }}
network {{ i.vpnv4.network }} mask {{ i.vpnv4.mask }}
{% for b in item.bgp.site.neighbor.peer_ip %}
neighbor {{ b }} remote-as {{ item.bgp.remote }}
neighbor {{ b }} activate
{% endfor %}
{% endfor %}
If I remove this it works. I am suspecting an issue with "list" but no idea how to fix it.
{% for b in item.bgp.site.neighbor.peer_ip %}
neighbor {{ b }} remote-as {{ item.bgp.remote }}
neighbor {{ b }} activate
{% endfor %}
item.bgp.site is actually i of your outer loop.
Try: {% for b in i.neighbor.peer_ip %}

Resources