Repeated lines from Jinja For Loop - ansible

I am new to coding and learning for Infrastructure as Code.
Trying to pull info from a variable YAML file within a Jinja file using a for loop. It is working but it is displaying the code two additional times and I am not sure why.
{% for item in underlay[inventory_hostname]['MLAG'] -%}
{% if item == 'Odd' -%}
neighbor 192.168.255.1 peer group LEAF_Peer
{%- else %}
neighbor 192.168.255.2 peer group LEAF_Peer
{% endif %}
{%- endfor %}
Sample of entry in Underlay.yml
leaf1-DC2:
interfaces:
loopback0:
ipv4: 10.2.0.11
mask: 32
loopback1:
ipv4: 10.2.1.11
mask: 32
Ethernet3:
ipv4: 10.2.2.0
mask: 31
Ethernet4:
ipv4: 10.2.2.2
mask: 31
Ethernet5:
ipv4: 10.2.2.4
mask: 31
BGP:
ASN: 65201
spine-peers:
- 10.2.2.1
- 10.2.2.3
- 10.2.2.5
spine-ASN: 65200
MLAG: Odd
The results are:
neighbor 192.168.255.2 peer group LEAF_Peer
neighbor 192.168.255.2 peer group LEAF_Peer
neighbor 192.168.255.2 peer group LEAF_Peer
But should be:
neighbor 192.168.255.2 peer group LEAF_Peer
I took the variable file down to a single device that only has the entry for MLAG once but that still produces two additional lines and do not understand why.
The goal is to create a for loop that looks at MLAG and if it is Odd then produce the one statement or Even produce the other.

This happens because a string is an iterable object in both Jinja and Python.
You can test this doing:
{% for item in 'Odd' %}
Displaying one line (because of `{{ item }}`)
{% endfor %}
That would give you
Displaying one line (because of `O`)
Displaying one line (because of `d`)
Displaying one line (because of `d`)
Your fix is as simple as dropping the for loop, totally:
{% if underlay[inventory_hostname].MLAG == 'Odd' -%}
neighbor 192.168.255.1 peer group LEAF_Peer
{% else -%}
neighbor 192.168.255.2 peer group LEAF_Peer
{% endif %}

Related

AnsibleUndefinedVariable: No variable found with this name: region1a"}

I want to get the output of information after comparing whether the region is the capital?
Help me figure out how to use "lookup" correctly?
{% if capital == lookup('vars', item) %} yes {% else %} no {% endif %}
or
{% if capital.action == {{ item }} %} yes {% else %} no {% endif %}
I get the following error
failed: [localhost] (item=region1a) => {"ansible_loop_var": "item", "changed": false, "item": "region1a", "msg": AnsibleError: template error while templating string: expected token ':', got '}'
here {{ item }} is a variable = region1a
I have these variables
vars:
AllCountry:
- name1
- name2
name1:
- region1a
- region1b
name2:
- region2a
- region2b
capital:
- region1a
- region2a
what am I wrong about?
It seems to me like this is what you are looking to achieve:
{% if item in capital %} yes {% else %} no {% endif %}
But that is really not foolproof. Imagine you have two regions named the same in two countries and one is the capital of one of the countries when the other is not the capital of the other one, how would you do it here?
I would really go with a more tied kind of dictionary like:
countries:
country1:
regions:
- region1
- region2
- region3
capital: region1
country2:
regions:
- region4
- region5
capital: region5
But without your actual use case and what you are trying to build with all this, this is hard to advise on the right type of data structure to construct.

How to fix "StackLevelError (Stack Overflow)" in Jekyll navigation template

I am trying to write a recursive Jekyll navigation template (include) as described in "Nested tree navigation with recursion". I have a minimal example committed in jekyll-min, which basically has:
two top-level dirs, each with one page
another dir under the second top-level dir, containing one page
a navigation template (_includes/docs_contents.html) that loops through the top-level dirs and initiates recursive traversal for each
a recursive include (_includes/nav.html) that accepts a navigation entry, renders its title and child links, and invokes itself recursively for any dirs in its children list
a layout (_layouts/doc.html) that renders the navigation pane and content for each page
I'm using Ruby v2.7.0 and Jekyll v3.8.5.
# docs structure
_docs
|
|_a/
| |_index.md
|
|_b/
|_index.md
|
|_1/
|_index.md
# _data/docs-nav.yml
- title: a
docs:
- link: /a/
- title: b
docs:
- link: /b/
- title: 1
docs:
- link: /b/1/
# _includes/nav.html
{% assign section=include.nav %}
<div class="ui accordion">
<div class="title active">
<i class="dropdown icon"></i>
{{ section.title }}
</div>
<div class="content active">
<div class="ui vertical text menu">
{% for item in section.docs %}
{% if item.link %}
{%- assign p = site.documents | where: "url", item.link | first %}
<a {%- if page.url== p.url %} class="current item" {% endif %} class="item" href="{{ p.url }}">
{{ p.menu_name | default: p.title }}
</a>
{% endif %}
{% if item.docs %}
{% include nav.html nav=item %}
{% endif %}
{% endfor %}
</div>
</div>
</div>
# _includes/docs_contents.html
<div class="unit one-fifth hide-on-mobiles">
<aside>
{% for section in site.data.docs_nav %}
{% include nav.html nav=section %}
{% endfor %}
</aside>
</div>
# _layouts/doc.html
---
title: Docs
description: version 1.0
---
<html>
<body>
{% include docs_contents.html %}
{{ content }}
</body>
</html>
As far as I understand, for each page the navigation template render should work like this:
_layouts/doc.html
_includes/docs_contents.html: iterate root level entries, calling _nav for each
_nav(/a/ entry): render title, iterate docs, render /a/ link, and quit
_nav(/b/ entry): render title, iterate docs, render /b/ link, and then call _nav(/b/1/ entry)
_nav(/b/1/ entry): render title, iterate docs, render /b/1/ link, and quit
_nav(/b/ entry) (already in stack): quit
_includes/docs_contents.html: quit
However, when I perform a bundle exec jekyll build I get:
Liquid Exception: Liquid error (/mnt/e/ThirdParty/jekyll-min/_includes/docs_contents.html line 17):
Nesting too deep included in /_layouts/doc.html
jekyll 3.8.5 | Error: Liquid error (/mnt/e/ThirdParty/jekyll-min/_includes/docs_contents.html line 17):
Nesting too deep included
Traceback (most recent call last):
[...]
What is the problem with my content or the recursive template? I have been struggling with this for hours with no luck.
JEKYLL_LOG_LEVEL=debug
didn't produce any additional useful info.
The actual document structure is more complex and could go arbitrarily deep, so writing a non-recursive template to manually handle nested levels may not be an option.
Excellent question.
With help of {{ myvar | inspect }} and a flag limiting recursion, I've successfully debugged your code and understood why this infinite recursion occurs.
It comes from the fact that the section variable in docs_contents.html is assigned by in a for loop and freezed : it cannot be changed.
The first time you include nav.html, {% assign section=include.nav %} is not changing section and your code just use the one assigned in your for loop.
When you recurse and call nav.html a second time it will use the same freezed global section variable and recurse indefinitely.
The solution is to change your variable name in nav.html from section to something else. eg: sub_section, and it will work, because this new variable will not be freezed and can be reassigned as needed during recursion.
{% assign sub_section=include.nav %}
{{ sub_section.title }}
{% for item in sub_section.docs %}
...
If you want to experiment here is my test code with some comments :
docs_contents.html
{% for section in site.data.docs_nav %}
{% comment %} ++++ Try to reassign "section" ++++ {% endcomment %}
{% assign section = "yolo from docs_contents.html" %}
{% assign recursion = 0 %}
<pre>
>> docs_contents.html
++++ "recursion" var is assigned and becomes global
recursion : {{ recursion | inspect }}
++++ "section" is freezed to loop value ++++
including nav with include nav.html nav=section >> {{ section | inspect }}
</pre>
{% include nav.html nav=section %}
{% endfor %}
nav.html
{% comment %} ++++ Try to reassign "section" ++++ {% endcomment %}
{% assign section = "yolo from nav.html" %}
<pre>
>> nav.hml
recursion : {{ recursion }}
include.nav : {{ include.nav | inspect }}
++++ "section" is freezed to loop value ++++
section : {{ section | inspect }}
</pre>
{% comment %} ++++ useless assignement ++++ {% endcomment %}
{% assign section=include.nav %}
{% for item in section.docs %}
{% if item.link %}
{%- assign p = site.documents | where: "url", item.link | first %}
<a {%- if page.url== p.url %} class="current item" {% endif %} class="item" href="{{ p.url }}">
{{ p.menu_name | default: p.title }}
</a>
{% endif %}
{% comment %}++++ limiting recursion to 2 levels ++++{% endcomment %}
{% if item.docs and recursion < 2 %}
{% comment %}++++ incrementing "recursion" global variable ++++{% endcomment %}
{% assign recursion = recursion | plus: 1 %}
{% include nav.html nav=item %}
{% endif %}
{% endfor %}

"ScannerError: mapping values are not allowed here" related to jinja template

I am running an Ansible playbook. In a YAML file "jinja.yaml", I have the following jinja2 template.
{% set cnt = 0 %}
{% for x in range(4, 20) %}
{% for y in range(1, 251) %}
- pool_name: pool_{{ cnt }}
{% set cnt = cnt + 1 %}
pool_member: 10.30.{{ x }}.{{ y }}
{% endfor %} {% endfor %}
Here is the snippet in the Ansilbe code:
tasks:
- name: XXX
set_fact:
members: "{{ lookup('template', 'jinja.yaml') | from_yaml}}"
When I ran the Ansible playbook, I got the following error message:
return loader.get_single_data()
File "/usr/lib/python2.7/dist-packages/yaml/constructor.py", line 37, in get_single_data
node = self.get_single_node()
File "/usr/lib/python2.7/dist-packages/yaml/composer.py", line 36, in get_single_node
document = self.compose_document()
File "/usr/lib/python2.7/dist-packages/yaml/composer.py", line 55, in compose_document
node = self.compose_node(None, None)
File "/usr/lib/python2.7/dist-packages/yaml/composer.py", line 82, in compose_node
node = self.compose_sequence_node(anchor)
File "/usr/lib/python2.7/dist-packages/yaml/composer.py", line 111, in compose_sequence_node
node.value.append(self.compose_node(node, index))
File "/usr/lib/python2.7/dist-packages/yaml/composer.py", line 84, in compose_node
node = self.compose_mapping_node(anchor)
File "/usr/lib/python2.7/dist-packages/yaml/composer.py", line 127, in compose_mapping_node
while not self.check_event(MappingEndEvent):
File "/usr/lib/python2.7/dist-packages/yaml/parser.py", line 98, in check_event
self.current_event = self.state()
File "/usr/lib/python2.7/dist-packages/yaml/parser.py", line 428, in parse_block_mapping_key
if self.check_token(KeyToken):
File "/usr/lib/python2.7/dist-packages/yaml/scanner.py", line 116, in check_token
self.fetch_more_tokens()
File "/usr/lib/python2.7/dist-packages/yaml/scanner.py", line 220, in fetch_more_tokens
return self.fetch_value()
File "/usr/lib/python2.7/dist-packages/yaml/scanner.py", line 576, in fetch_value
self.get_mark())
ScannerError: mapping values are not allowed here
in "<unicode string>", line 2, column 32:
pool_member: 10.30.4.1
^
fatal: [10.6.177.160]: FAILED! => {
"msg": "Unexpected failure during module execution.",
"stdout": ""
}
I am 99% sure it's the syntax error in the jinja.yaml but I just did not fix it. Any help will
appreciate it.
You are solving the wrong problem; if you want a list[dict], then don't try to build yaml text using jinja templating only to later convert it into the actual data structure you wanted: just construct the list[dict] you want without the intermediate serialization:
- name: XXX
set_fact:
members: >-
{%- set cnt_holder = {"cnt": 0} -%}
{%- set results = [] -%}
{%- for x in range(4, 20) -%}
{%- for y in range(1, 251) -%}
{%- set _ = results.append({
"pool_name": ("pool_%d" | format(cnt_holder.cnt)),
"pool_member": ("10.30.%d.%d"|format(x, y)),
}) -%}
{%- set _ = cnt_holder.update({"cnt": cnt_holder.cnt + 1}) -%}
{%- endfor -%}
{%- endfor -%}
{{ results }}
As you'll observe, your original code block had a bug: one cannot reassign variables (which goes doubly so from within a lexical block like a for loop); that "feature" of jinja2 is actually documented in the fine manual
However, you can mutate existing data structures (in fact that is the whole point of results.append). So, we are side-stepping jinja2's set behavior and storing the mutable cnt in a global dict so we can mutate it. That's why my {% set cnt_holder differs from your syntax

Jinja2 - global variable update in for loop

I'm working on a configuration script for a certain service and I'd like to have it templated for our configuration management tools (Ansible). There's a particular action however, which seems to be a Jinja2 limitation (if that word is acceptable in this case) which I can't overcome:
{% set min = 0 %}
{% set max = 5500 %}
{% for item in list_of_items %}
for i in {min..max}; do command {{ item }} --arg 1 commnand_stuff $i; done
{% set min = max + 1 %}
{% set max = max * 2 %}
#fi
{% endfor %}
The expected (desired) result is:
- iteration 1 - min = 0, max = 5500
- iteration 2 - min = 5501, max = 11000
..
The actual result is:
- min and max have a constant value through all loop iterations - min=0 and max=5500.
So, how do I modify a global variable in Jinja2 in for loop?
set does not work inside a loop. See assigning a variable inside a loop.
It is possible to use loop.index instead. The template below
{% for item in list_of_items %}
{{ 5500 * (loop.index-1) + 1 }}..{{ 5500 * loop.index }}
{% endfor %}
gives
1..5500
5501..11000
11001..16500

Jinja add automatic indentation

In ansible I use a template written in jinja2,
I have an inner for loop which automatically adds space to my config file, which i do not want to.
stick store-response payload_lv(43,1) if serverhello
option ssl-hello-chk
{% set count = 1 %}
{% for ip in sv.ips %}
server server{{ count }} {{ ip }}:443 check
{% set count = count + 1 %}
{% endfor %}
Result is
stick store-response payload_lv(43,1) if serverhello
option ssl-hello-chk
server server1 10.2.0.16:443 check
server server2 10.2.0.20:443 check
Add this line at the top of your template, will preserve the indentation
#jinja2: trim_blocks: True, lstrip_blocks: True

Resources