Ansible template mix up order of elements - ansible

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?

Related

How to exclude an host in Ansible Jinja2 template for-loop?

I have a template named foo.yml.j2 used in an Ansible task to generate a foo.yml file:
{% for host in ansible_play_hosts_all %}
{{ host }},
{% endfor %}
Everything works fine except I need something like the following statement: For every host in ansible_play_hosts_all except for host==bar do this or that.
Is this achievable or the only way to do this is to categorize my hosts in different groups and use ansible_play_hosts_group?
You can use the reject filter to take your host out of the list so that it is not part of your loop:
{% for host in ansible_play_hosts_all | reject('==', 'bar') %}
{{ host }},
{% endfor %}
There are more options. The trivial one is using the condition in Jinja
{% for host in ansible_play_hosts_all %}
{% if host != 'bar' %}
{{ host }},
{% endif %}
{% endfor %}
Fit the format to your needs.
Example of a complete playbook for testing
- hosts: host_1,host_2,host_3
gather_facts: false
tasks:
- debug:
msg: |
{% for host in ansible_play_hosts_all %}
{% if host != 'host_2' %}
{{ host }},
{% endif %}
{% endfor %}
run_once: true
gives (abridged)
msg: |-
host_1,
host_3,
The next option is to remove blacklisted hosts from the loop, e.g.
blacklist: [host_2]
The template below gives the same result
{% for host in ansible_play_hosts_all|difference(blacklist) %}
{{ host }},
{% endfor %}

How to trim last character when using ansible jinja loop

My template like as blow
{% if hostvars[inventory_hostname].local_zk_server_id is defined %}
zookeeperServers={% for host in {{ groups[{{ target_hosts }}] %}}
"{{ hostvars[host].inventory_hostname }}:2181,"
{% endfor %}
{% endif %}
output ishost1:2181,host2:2181,host3:2181,
How to trim last comma
There are several possible gotchas in your above template regarding variables access. Moreover, rather than trimming the last character in your string, the best solution is probably not to write it. Here is a better solution IMO in my below example fixing all the problems I'm referring to:
{% set zookeeperServers=[] %}
{% if hostvars[inventory_hostname].local_zk_server_id is defined %}
{% for host in groups[target_hosts] %}
{% zookeeperServers.append(hostvars[host].inventory_hostname + ":2181") %}
{% endfor %}
zookeeperServers="{{ zookeeperServers | join(',') }}"
{% endif %}

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"

How to set concatenated string variable in Ansible template based on a condition

I need to create a string in a template that can change between hosts, and it needs to be in the form of:
"cores": "0,1,2,3"
And the reason the string is "0,1,2,3" in this example is because the host has 4 processor cores.
So I got stuck with something which seems too convoluted to me and I'm not even sure how to use this core_count variable in my template file.
{% set core_count = '' %}
{% for i in range(ansible_processor_cores) %}
{% set core_count = core_count ~ i %}
{% if not loop.last %}
{% set core_count = core_count ~ ',' %}
{% endif %}
{% endfor %}
There are many handy lookup plugins in Ansible. Take sequence:
- hosts: localhost
gather_facts: yes
tasks:
- debug:
msg: '"cores": "{{ lookup("sequence","start=0 count="+(ansible_processor_cores|string)) }}"'

Short version of if var is defined for ansible templates

Jinja2 in Ansible templates allows this type of expression in templates:
{% if foobar is defined %} foo_bar = {{foobar}} {% endif %}
{% if barfoo is defined %} bar_foo = {{barfoo}} {% endif %}
etc.
Is there any shorter version to say 'do not print this line if its variable is not defined?
Something like foo_bar = {{foobar|skip_this_line_if_undefined}}?
You can use the default(omit) filter. For details have a look at the documentation.
You could use a macro.
{% macro line(key, value) -%}
{% if not value|none %}{{ key }} = {{ value }}{% endif %}
{%- endmacro %}
Then just call the macro for every key/value pair.
{{ line('foo_bar', foobar) }}
{{ line('bar_foo', barfoo) }}
Could be problematic in edge cases though. If foobar or barfoo are not defined it probably will raise an error. In the macro, value in any case would be defined, so the condition is defined doesn't make sense any more. But if null/none actually is a valid value for any of the variables, you hit the wall...
A bit longer but probably water proof:
{% macro line(key, value) -%}
{% if value != omit %}{{ key }} = {{ value }}{% endif %}
{%- endmacro %}
{{ line('foo_bar', foobar|default(omit)) }}
{{ line('bar_foo', barfoo|default(omit)) }}

Resources