padding/ljust with ansible template and jinja - ansible

I am looking for a way to create a template in ansible with this dictionary.
data= {
"_dictionary": {
"keyone": "abc",
"rtv 4": "data2",
"longtexthere": "1",
"keythree": "data3",
"keyfour": "data1234",
}
}
The template output should have this format:
keyone abc
keytwo data2
longtexthere 1
keythree data3
keyfour data1234
With python I can create it with:
w = max([len(x) for x in data['_dictionary'].keys()])
for k,v in data['_dictionary'].items():
print(' ', k.ljust(w), ' ', v)
But I can't a way to create it in a jinja2 template in ansible. I have not found a replacement for ljust.
Currently my template is this, but I got a output without format.
{% for key, value in data['_dictionary'].items() %}
{{ "%s\t%s" | format( key, value ) }}
{% endfor %}
Any ideas, sugestion?

For example
- debug:
msg: |
{% for k,v in data['_dictionary'].items() %}
{{ "{:<15} {}".format(k, v) }}
{% endfor %}
gives
msg: |-
keyone abc
rtv 4 data2
longtexthere 1
keythree data3
keyfour data1234
See format and Format String Syntax.
Q: "Create the format dynamicaly."
A: For example, find the longest key in the dictionary. Add 1 more space to the length of the first column. In the same way, calculate the length of the second column and create the format string in a separate variable
- debug:
msg: |
{% for k,v in data['_dictionary'].items() %}
{{ fmt.format(k, v) }} # comment
{% endfor %}
vars:
col1: "{{ data._dictionary.keys()|map('length')|max + 1 }}"
col2: "{{ data._dictionary.values()|map('length')|max + 1 }}"
fmt: "{:<{{ col1 }}} {:<{{ col2 }}}"
gives
msg: |-
keyone abc # comment
rtv 4 data2 # comment
longtexthere 1 # comment
keythree data3 # comment
keyfour data1234 # comment

Is working, at the end my j2 file is:
{% set col1 = data._dictionary.keys()|map('length')|max %}
{% set fmt = "{:<" + col1|string + "} {}" %}
{% for key, value in data._dictionary.items() %}
{{ fmt.format(key, value) }}
{% endfor %}
Thank you.

Related

Problems with Jinja2 and ansible making a sub dict

I need to read a csv file with diferent IPs and make a dictionary with a jinja2 filter for modificate the IP depending the IPNumber value. The yml file is like:
- read_csv:
path: vms.csv
key: Number
fieldnames: Name,IP1,IP2,IP3,IP4,IPNumber
delimiter: ';'
register: vms
- name: vms to dict
debug:
msg:
- {{'Name':{{ item.value.Name }},
{% if item.value.IPNumber == "1" %}
'IP':{{ item.value.IP1 }},
{% endif %}
{% if item.value.IPNumber == "2"%}
'IP':{{ item.value.IP2 }},
{% endif %}
{% if item.value.IPNumber == "3"%}
'IP':{{ item.value.IP3 }},
{% endif %}
{% if item.value.IPNumber == "4"%}
'IP':{{ item.value.IP4 }},
{% endif %}}}
loop: "{{ vms.dict | dict2items }}"
register: vms2
But I get the error:
The error appears to be in '/etc/ansible/roles/vms.yml': line 17, column 16, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
'Name':{{ item.value.Name}},
{% if item.value.IPNumber == "1" %}
^ here
I know is a syntax problem but I dont guess where the problem is.
I need some help.
The following task should create your dictionary as per your requirement inside a var you can reuse elsewhere. Rename my_ip_dict to whatever suits your project better.
- name: Create my IP dictionary
set_fact:
my_ip_dict: >-
{{
my_ip_dict | default({})
| combine({item.value.Name: item.value['IP' + item.value.IPNumber]})
}}
loop: "{{ vms.dict | dict2items }}"
- name: Check the result:
debug:
var: my_ip_dict
Note that I have dropped all the if/else structures by calling directly the correct field depending on IPNumber. I took for granted it always has a value in the valid scope or the other existing IP* fields. If this is not the case, you can always default that value e.g. item.value['IP' + item.value.IPNumber] | default('N/A')
You should put only variables/expressions within {{ or {%. To me 'Name' looks like normal text and should be outside.
Example:
# Notice the quotes `"` symbol at the beginning and end of debug message
- debug:
msg:
- "Name: {{ item.value.Name }},
{% if item.value.IPNumber == "1" %}
IP: {{ item.value.IP1 }}
# and so on...
{% endif %}"
This at least should address the error message.

Ansible templates and jinja {%block%}

I need to generate a single file on the remote host using multiple template files and Jinja's {% block block_name %} in my Ansible role
For example,
main.conf.j2:
value1 = 123
value2 = 456
{% block test %} {% endblock %}
value3 = 789
{% block example %} {% endblock %}
value4 = abcd
test.conf.j2:
{% block test %}
more text here
{% endblock %}
example.conf.j2
{% block example %}
....
example_param = 'example!'
....
{% endblock %}
What's the next step? I must use {% extends 'nginx.conf.j2' %} in test.conf.j2 and example.conf.j2? And if so - how will look my Ansible task? Or even something else?
If I trying something like this:
- name: Copy config
template:
src: "{{ item }}"
dest: "{{ conf_file_path }}"
with_items:
- "main.conf.j2"
- "test.conf.j2"
- "example.conf.j2"
- "abcd.conf.j2"
it works only for main.conf.j2 and test.conf.j2, but ignores example.conf.j2 and other templates
Q: "What's the next step? I must use {% extends 'nginx.conf.j2' %} ... ?"
A: Yes. extends is needed. For example
- template:
src: test.j2
dest: test
with the templates
shell> cat main.j2
value1 = 123
{% block test %}
value = default value in main.j2
{% endblock %}
value3 = 789
shell> cat test.j2
{% extends 'main.j2' %}
{% block test %}
value = custom value in test.j2
{% endblock %}
gives
shell> cat test
value1 = 123
value = custom value in test.j2
value3 = 789
Q: "How will look my Ansible task?"
- name: Copy config
template:
src: "{{ item }}"
dest: "{{ conf_file_path }}"
with_items:
- "main.conf.j2"
- "test.conf.j2"
- "example.conf.j2"
- "abcd.conf.j2"
A: The loop will repeatedly overwrite the dest file in each iteration. See template.
FWIW. It's possible to use blockinfile and loop the lookup of the templates. For example
- template:
src: main2.j2
dest: test
- blockinfile:
marker: "# {mark} ANSIBLE MANAGED BLOCK {{ item }}"
path: test
block: "{{ lookup('template', item) }}"
loop:
- test.conf.j2
- example.conf.j2
with the templates
shell> cat main2.j2
value1 = 123
# BEGIN ANSIBLE MANAGED BLOCK test.conf.j2
value_test = default value in main2.j2
# END ANSIBLE MANAGED BLOCK test.conf.j2
# BEGIN ANSIBLE MANAGED BLOCK example.conf.j2
value_example = default value in main2.j2
# END ANSIBLE MANAGED BLOCK example.conf.j2
value3 = 789
shell> cat test.conf.j2
value_test = custom value in test.conf.j2
shell> cat example.conf.j2
value_example = custom value in example.conf.j2
give
shell> cat test
value1 = 123
# BEGIN ANSIBLE MANAGED BLOCK test.conf.j2
value_test = custom value in test.conf.j2
# END ANSIBLE MANAGED BLOCK test.conf.j2
# BEGIN ANSIBLE MANAGED BLOCK example.conf.j2
value_example = custom value in example.conf.j2
# END ANSIBLE MANAGED BLOCK example.conf.j2
value3 = 789

multiple variables in same for loop (Jinja2, Yaml)

I have a list in yaml file
users:
name:
- abc
- pqr
age:
- 10
- 12
I want to iterate over above values in jinja2 template.
Member in name is associated with member in age of same index. So I want to iterate over both in single line only.
{% for n in users['name'] and for a in users['age'] %}
{{ n }}
{{ a }}
{% endfor %}
For loop in this code isn't working.
I checked official documentation but I could not find any example like this.
Can anyone please help me with this?
The template below
{% for item in users.name|zip(users.age)|list %}
{{ item.0 }}
{{ item.1 }}
{% endfor %}
gives:
$ cat test.txt
abc
10
pqr
12

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 iterate csv file in ansible

i have a jinja2 template including a section that need data from a csv file
how can i read a csv file and split it into a list then iterate it in the jinja2 template? sth. like this:
{% for line in csv_data %}
{{ line[0] }} = {{ line[1] }}
{% endfor %}
in my task file, i am trying to use lookup to read the csv file into csv_data, but it seems lookup can only query and get one line not the whole file, or just the whole file in raw format
vars:
csv_data: "{{ lookup('file', 'test.csv') }}"
figured a not so good method:
{% for line in csv_data.split("\n") %}
{% set list = line.split(",") %}
{{ list[0] }}={{ list[1] }}
{% endfor %}

Resources