Ansible: compose list of facts in jinja template - templates

I am setting up a cluster service with Ansible 1.9.2 and need to configure a JSON config file with a list of cluster servers to join.
Currently, I have this working as below. It produces correct, if ugly, output.
{
...
"join": [
{% for host in groups['cluster'] %}
"{{ hostvars[host]['ansible_default_ipv4']['address'] }}{% if not loop.last %}, {% endif %}
{% endfor %}
],
...
}
Is it possible for Ansible to create a list of specific host facts, or for Jinja to compose a list dynamically? I would hope for something I can leave in my template like this:
{
...
"join": {{ list_of_cluster_ips|to_nice_json }},
...
}
I tried some Jinja magic at the top of the template file to generate the list as below:
{% set list_of_cluster_ips = [] %}
{% for host in groups['cluster'] %}
{% do list_of_cluster_ips.append(host) %}
{% endfor %}
{
...
"join": {{ list_of_cluster_ips|to_nice_json }},
...
}
But Ansible doesn't support the 'do' function of Jinja and fails with fatal: [cluster-1] => {'msg': "AnsibleError: file: <template>, line number: 3, error: Encountered unknown tag 'do'. Jinja was looking for the following tags: 'endfor' or 'else'. The innermost block that needs to be closed is 'for'.", 'failed': True}.
Is there a way for Ansible to generate the list that can be used in the template?

Try this:
{%- set list_of_cluster_ips = [] %}
{%- for host in groups['cluster'] %}
{%- if list_of_cluster_ips.append(hostvars[host]['ansible_default_ipv4']['address']) %}
{%- endif %}
{%- endfor %}
{
...
"join": {{ list_of_cluster_ips|to_nice_json }},
...
}

Related

regex expression in jinja2

I have a jinja2 script with a for loop, I just want to see if there is any way I can use regex checks in an if statement.
So the script looks like:
{% for key, value in notes.items() -%}
if {{ value }} starts with http or https
then ~*^/{{ key }}(\?.*)?$ {{ value }};
otherwise
~*^/{{ key }}(\?.*)?$ /{{ value }};
{% endfor %}
Is that somewhow writable???
For example,
- hosts: localhost
gather_facts: false
vars:
_dict:
k1: v1
k2: https://example.com
k3: http://example.com
tasks:
- debug:
msg: |
{% for key, value in _dict.items() -%}
{% if value is regex('^(http|https).*$') %}
~*^/{{ key }}(\?.*)?$ {{ value }};
{% else %}
~*^/{{ key }}(\?.*)?$ /{{ value }};
{% endif %}
{% endfor %}
gives
msg: |-
~*^/k1(\?.*)?$ /v1;
~*^/k2(\?.*)?$ https://example.com;
~*^/k3(\?.*)?$ http://example.com;
Notes
Q: "No test named 'regex' found."
See Testing strings. The test match gives the same result
- debug:
msg: |
{% for key, value in _dict.items() -%}
{% if value is match('(http|https)') %}
~*^/{{ key }}(\?.*)?$ {{ value }};
{% else %}
~*^/{{ key }}(\?.*)?$ /{{ value }};
{% endif %}
{% endfor %}
Q: "The same result with 'match' and 'search'."
A: Test the first 4 characters of the value
{% if value[:4] == 'http' %}

Accessing multiple dictionaries in list using for loop in Django Templates

I have been working on a project and it requires me to access dictionaries that are in a list. I am using Django 2.0. This is my code here.
{% if dictdata %}
{% for x in range %}
{{ "in loop" }}
{{ dictdata.x.name }}
{% endfor %}
{{ dictdata.0.name }}
{{ dictdata.1.name }}
{% endif %}
The two statement after the for loop are working fine. But the one in the loop is not returning anything but printing the message 'in loop'. dictdatais a list containing dictionaries.
It is not clear what the value of your range varable is. So assuming dictdata is close to the following:
dictdata = [
{'name': 'Bob'},
{'name': 'John'}
]
You can loop over them in your template as follows:
{% for d in dictdata %}
{{ d.name }}
{% endfor %}
Documentation about looping in a template can be found here.

condition template in ansible

I have this template (set_ip.j2):
{% if '{{ansible_env.SSH_CONNECTION.split(' ')[2]}}' == '{{ ip_ssh }}' %}
address = {{ ip_db }}
name='db1'
{% endif %}
but this condition not work! I want address and name set by this value in the config file.
Never ever use nested expressions in Jinja2:
{% if ansible_env.SSH_CONNECTION.split(' ')[2] == ip_ssh %}
address = {{ ip_db }}
name='db1'
{% endif %}

How to use the current index to get the value of another array?

I've read this, and I have an array like that:
context[u'erreurs'] = {
'aa': {'titres': [], 'liste': [], 'urls': []},
'bb': {'titres': [], 'liste': [], 'urls': []},
'...': {'titres': [], 'liste': [], 'urls': []}
}
If there's an error, 'titres', 'liste' and 'urls' become array of strings, filled with adequates values.
In my template, if erreur is set I do this:
{% for idx, tab in erreurs.items %}
<ul>
{% for e in tab.liste %}
{% if user.is_authenticated %}
<li>{{ e }}</li>
{% else %}
<li>{{ e }}</li>
{% endif %}
{% endfor %}
</ul>
{% endfor %}
I would like to use the current index to use the value that is in another array, here: tab.urls. It doesn't work and gives me the error:
Could not parse the remainder: '[forloop.counter0]' from 'tab.urls[forloop.counter0]'
How to solve this?
Unfortunately, Django's templates don't support such syntax. You should put together a custom template filter:
# yourapp/templatetags/yourapp_tags.py:
from django import template
register = template.Library()
#register.filter
def at_index(array, index):
return array[index]
and use it like:
{% load yourapp_tags %}
{{ tab.urls|at_index:forloop.counter0 }}
You need to make an actual model that represents the data then the task becomes trivial
class YourModel(object):
titre = ''
liste = ''
url = ''
context[u'erreurs'] = {
'aa': [], # List of model
}
{% for idx, tab in erreurs.items %}
<ul>
{% for model in tab %}
{{ model.titre }}
{{ model.liste }}
{{ model.url }}
{% endfor %}
</ul>
{% endfor %}

Change variable in Ansible template based on group

I've got an Ansible inventory file a bit like this:
[es-masters]
host1.my-network.com
[es-slaves]
host2.my-network.com
host3.my-network.com
[es:children]
es-masters
es-slaves
I also have a Jinja2 template file that needs a certain value set to "true" if a host belongs to the "es-masters" group.
I'm sure that there's a simple way of doing it but after some Googling and reading the documentation, I've drawn a blank.
I'm looking for something simple and programmatic like this to go in the Jinja2 template:
{% if hostvars[host][group] == "es-masters" %}
node_master=true
{% else %}
node_master=false
{% endif %}
Any ideas?
You do it the other way around.
You check if the identifier (hostname or IP or whatever is in your inventory) is in the defined group. Not if the group is in the hostvars.
{% if ansible_fqdn in groups['es-masters'] %}
node_master=true
{% else %}
node_master=false
{% endif %}
But, what you better should do is this:
Provide default in template
# role_name/templates/template.j2
node_master={{ role_name_node_master | default(true) }}
Than override in group_vars
# group_vars/es-masters.yml
role_name_node_master: false
If your inventory does not identify hosts with ansible_fqdn, ansible_hostname, etc., you can also use group_names to check if the current host has "es-masters" as one of its groups.
{% if 'es-masters' in group_names %}
node_master=true
{% else %}
node_master=false
{% endif %}
See https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#accessing-information-about-other-hosts-with-magic-variables
To avoid error with non existing group you should check first if the group exists:
{% if 'es-masters' in group_names and ansible_fqdn in groups['es-masters'] %}
node_master=true
{% else %}
node_master=false
{% endif %}
{% if ansible_fqdn in groups['es-masters'] %}
{% set node_master=true %}
{% else %}
{% set node_master=false %}
{% endif %}
maybe like this? change the var which named node_master, rather than use a txt