ansible "when statements should not include jinja2 templating delimiters" - ansible

I have read a number of examples of how to re-write the when clause to avoid the warning about jinja2 template delimiters {{ and }} - but have not seen any on avoid use of {% for ... %}.
I have roughly the following step:
- name: Wait for Started status to clear
uri:
url: http://{{ container.ip }}:8080/upgrade/api/v1/upgrades/{{ theuuid }}
return_content: yes
register: progress_status
until: >
{% for c in (progress_status.content | from_json).data.states %}
{{ c.state != 'Started' }}
{% if not loop.last %}and{% endif %}
{% endfor %}
retries: 30
delay: 15
The uri call returns a list of containers and their 'state' in a json form. I want to keep querying this uri until none of the containers are in the Started state.
This generates the warning due to the {% for ... %} loop.
I could do a less specific test by look at the entire response for the json string representing a state of Started. But that seems more cryptic than looking at the state of each container (building up a boolean expression of true and true and ...). But this is my alternative for now.
Or I could use a block and do the query, and then build the condition as a fact, and then use the fact as the until on the block. But that too seems harder to read.
Suggestions for how to handle this sort of pattern?

You can use selectattr/rejectattr filters to fetch/delete specific items from lists:
until: (progress_status.content | from_json).data.states | rejectattr('state','equalto','Started') | list | length == 0
Here we take (progress_status.content | from_json).data.states list, remove all elements that have state=='Started' and check length of remaining list.
Task will complete when the list after reject is empty meaning all items have Started state.

Related

Why does Jinja2 render the string "on" to boolean True?

I'm using Jinja2 with YAML and have the following structure:
{%- set example = [ (20, "on"), (40, "off")] %}
- name: example_yaml
loop:
{%- for value, state in example %}
- TheNumber: {{ value }}
TheState: {{ state }}
{%- endfor %}
When the first loop is rendered, TheNumber is correct with 20, but TheState ends up being True. I've looked through the documentation and have tried adding a string filter like this:
{{ state | string }}
But that did not work either. I have also Tried switching the string "on" to something else like "StateShouldBeOn" just to test with. With that I get what I expect TheState = "StateShouldBeOn".
My question is, why is it that "on" renders to a boolean value?
Try to use 'on' instead of "on". That should help.

How to get desired output using Ansible jinja2 if else statement

Below is tasK:
- name: primary slot on active
debug: msg={{slotid.stdout_lines}}
register: slotidoutput
output1:
TASK [primary slot on active] *************************************************************************
ok: [1.1.1.1] => {
"msg": [
[
"Primary Slot ID 1"
]
]
}
some devices dosen't have primary slot ID so at that time output will be like :
output2:
TASK [primary slot on active] ***********************************************************************
ok: [2.2.2.2] => {
"msg": []
}
so i am working on a jinja2 template
{% if 'Primary Slot ID' in slotidoutput %}
{{slotidoutput.msg[0][0]}}
{% else %}
Single Slot
{% endif %}
i am always getting value as "Single Slot" even though i ran on multi slot device which has output1
Single Slot
desired print value for device 1.1.1.1 is:
Primary Slot ID 1
desired print value for device 2.2.2.2 is:
Single Slot
I am sure that i am doing some mistake in jinja if else statement. Can some one check and let me know.
There are few conditions that we need to address for the template to work properly.
sometimes slotid.stdout_lines is a nested list
in some cases it is an empty list []
conditional check with in will perform exact match on list items, and search match on a string
Also, register on debug output seems unnecessary as it holds the same data structure as slotid.stdout_lines.
So tasks and template such as below should address the above issues:
Tasks:
# Flatten nested list to single level
- set_fact:
slotidout: "{{ slotid.stdout_lines|flatten }}"
# Using random name for template
- template:
src: testtemplate.j2
dest: /tmp/testtemplate
Template testtemplate.j2:
{% if slotidout[0] is defined %}
{% if "Primary" in slotidout[0] %}
{{ slotidout[0] }}
{% endif %}
{% else %}
Single slot
{% endif %}
This should create the file with appropriate values based on conditions. You may adjust the conditions based on your requirements.

How to add a column in a line in Ansible Jinja2 template using for loop?

I want to update a line in Jinja2 template as follows
Let's say, I have a variable x equal to 4, then, I want a row/line as:
abc 1 2 3
in my template:
{% for val in range(1, x | int) %}
abc {{ val }} {{ val+1 }} .......
{% endfor %}
If I use for loop it adds a new line, Is their way I can achieve aforementioned requirement?
Here you are:
abc{% for val in range(1, x|int) %} {{ val }}{% endfor %}
Jinja2 is a templating engine. If you put a newline character in the source template, it will be present in the output.
You can either write everything in one line, or use whitespace control to remove unnecessary space.

How can one generate a dict in Jinja/Ansible as a var?

I need to dynamically generate a dict and assign it to an Ansible var so that the same data is available to every host and does not need to be regenerated on each one. This will be looped over by a task and template. It is possible to build lists by nesting Jinja in a var definition like so:
var_list: >
[
{% set data = [] %}
{% for host in groups['all'] %}
{{ data.append(host) }}
{% endfor %}
{{ data }}
]
However, I am not able to do so with dicts:
var_dict: >
{
{% set data = dict() %}
{% for host in groups['all'] %}
{{ data | combine({host: []}) }}
{% endfor %}
{{ data }}
}
This results in a giant, garbled string: {u'host.subdom.dom.tld': []} {}...
What I am expecting is a var set to a true dict object that can have its components referenced individually (like {{ var_dict.host1[0] }}), with a structure similar to the following JSON:
{
"host1": [],
"host2": []
}
Is there a way to cause this to expand into a proper dict when assigned to the variable? Or is there perhaps another way to generate a dict from a loop without that awful set_fact hackery?
Either of your templates is broken (the first one produces nested lists, the second one prints data in loop with this expression {{ data | combine({host: []}) }}, the value of data remains empty until the end).
Jinja2 is a templating engine, you don't really need to create data structures to print them later. You can form your output directly.
For the list:
var_list: >
[ {% for host in groups['all'] %}'{{ host }}',{% endfor %} ]
For the dictionary:
var_dict: >
{ {% for host in groups['all'] %}'{{ host }}': [],{% endfor %} }
As you expect the values to be interpreted by Ansible, you don't need to pay attention to the trailing coma, otherwise there is loop.last.

Business Catalyst Liquid sorting

I can see that Liquid allows you to sort a collection using the below syntax:
{% assign sorted_items = items.all|sort:'Email' %}
{% for item in sorted_items %}
<div>Name: {{item.name}}</div>
<div>Email: {{item.email}}</div>
{% endfor %}
However this does not appear to work in Business Catalyst.
If I use this to render the result to the page it simply renders "null".
{{sorted_items | json }}
Should I be able to do this in Business Catalyst, or am I completely wasting my time trying to find a solution to sort my WebApp data?
You can sort the data like this:
{module_data resource="customers" version="v3" fields="firstName,email1" collection="myData"}
<pre>{{myData|json}}</pre>
{% capture emails -%}
{% for item in myData.items -%}
,{{ item.email1.value }} - {{ item.firstName }};
{% endfor %}
{% endcapture %}
<pre>{{ emails | split: "," | sort }}</pre>
The comma is not spelling mistake : )
After you split the string in array you can do whatever you need to do with it.
The answer from Daut is not good. Any solution in the for loop will only sort the number of items fetched from the module and the max amount for that is 500.
If you are using module_data you just use its actual sort!
{module_data resource="customers" version="v3" order="firstName" fields="firstName,email1" collection="myData"}
module_data supports both WHERE for filtering and ORDER to order the results.

Resources