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
Related
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.
Need 3 variables to change on every iteration.
ver = [1,2,3]
tes = [a,b,c]
bet = [1a.2b.3c]
{% for v,t,b in ver,tes,bet %}
{{ v }} {{ t }} {{ b}}
{% endfor %}
o/p:
1 a 1a
2 b 2b
3 c 3c
I ran the same, got a very messy o/p. There should be only 3 iterations printing 3 variables in each iteration.
How can i perform the above operations using the for loop ? In the above case
Any suggestions on where I'm going wrong, would help Thanks !
You can use the following code:
{% for i in range([ver|count, tes|count, bet|count]|min) %}
{{ ver[i] }} {{ tes[i] }} {{ bet[i] }}
{% endfor %}
It uses only built in filters and functions:
count
min (to handle sequences with different length, similarly as zip in python)
range
Entire code in python:
import jinja2
ver = [1, 2, 3, 4]
tes = ['a', 'b', 'c']
bet = ['1a', '2b', '3c']
t = jinja2.Template('''{% for i in range([ver|count, tes|count, bet|count]|min) %}
{{ ver[i] }} {{ tes[i] }} {{ bet[i] }}
{% endfor %}
''')
print(t.render(ver=ver, tes=tes, bet=bet))
It looks like you need something that behaves like Python's zip function. Here's one option, in which we simply expose zip to the Jinja environment:
>>> import jinja2
>>> ver=[1,2,3]
>>> tes=['a','b','c']
>>> bet=['1a','2b','3c']
>>> t=jinja2.Template('''{% for v,t,b in zip(ver,tes,bet) %}
... {{ v }} {{ t }} {{ b}}
... {% endfor %}
... ''')
>>> t.environment.globals.update(zip=zip)
>>> print(t.render(ver=ver,tes=tes,bet=bet))
1 2 3
a b c
1a 2b 3c
>>>
Have a file that contains line in a format like below:
category1 name=item1
category1 attributes=attr1, attr2
category1 name=item2
category1 attributes=attr1, attr2, attr3
category2 name=item1
category2 attributes=attr1
Looking to get a dictionary in ansible like below:
- category1:
- name: item1
attributes: [attr1, attr2]
- name: item2
attributes: [attr1, attr2, attr3]
- category2:
- name: item1
attributes: [attr1]
I was trying with jinja2 and below is what I get as of now. Problem is that the code is getting complicated after every step. Is there any way using filter or other methods that could produce the expected result with more readable code?
Code:
- set_fact:
converted_data: >-
{%- set res = dict() -%}
{%- for line in content.stdout_lines -%}
{%- set key_val = line.split(' ', 1) -%}
{%- set second_level = [] -%}
{%- if res[key_val[0]] is defined -%}
{%- set second_level = res[key_val[0]] -%}
{%- endif -%}
{%- set val_split = key_val[1].split('=') -%}
{%- set _ = second_level.append( dict({ val_split[0]: (val_split[1].split(',') | map('trim') | list) }) ) -%}
{%- set _ = res.update({key_val[0]: second_level}) -%}
{%- endfor -%}
{{ res }}
Produces:
category1:
- name:
- item1
- attributes:
- attr1
- attr2
- name:
- item2
- attributes:
- attr1
- attr2
- attr3
category2:
- name:
- item1
- attributes:
- attr1
I would strongly advise you to stop writing this in Jinja and switch to Python. It can be a lookup plugin, a module, or just a very simple script to read from stdin and output a json.
Jinja is not designed for algorithms. Whilst you can implement any efficiently computable function using the turing-complete Jinja2 template language, it's going to be very very painful for you and for anyone else reading your code.
If you do it in Python, you can add few tests (just define few 'test_case1', 'test_case2' functions inside your script and use pytest to run them). Without tests you never will get a stable code for all cases (like if some line is missing, or there is a reordering, or duplicates, etc).
I'm using nunjucks to create a JSON export file. I have a list of variables that have the same name except for an incrementing number on the end. I'm using a for loop in the following way:
{% for i in range(1, 6) -%}
{% set items = ["{{ answer",i, " }}"] %}
"Solution{{ i }}" : "{{ items | join }}",
{%- endfor %}
I want answer1 to grab the variable answer1, but instead it is giving me a string "{{ anwser1 }}" .
Any idea how to use the for loop to point to each variable (answer1, answer2, answer3, etc)?
You can add some global function or filter to get access to a context (vars scope) by name.
const nunjucks = require('nunjucks');
const env = new nunjucks.Environment();
env.addGlobal('getContext', function (prop) {
return prop ? this.ctx[prop] : this.ctx;
});
const tpl = `{% for i in range(1, 4)%} {{ getContext('a' + i) }} {% endfor %}`;
const output = env.renderString(tpl, {a1: 10, a2:20, a3: 30, b: 1, c: 2});
console.log(output);
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