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
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.
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
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
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"
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 %}