Complex Variable Structure with Jinja2 - ansible

I am trying to figure out a complex variable structure with jinja2 template in Ansible. I have tried different solutions with dictsort and "if" loop inside "for" loop, but I do not see any progress. Any help would be appreciated.
I am trying to print the virtual_ro_id based on the ansible_hostname. And hostnames are server1.dc1.com & server2.dc1.com, same for dc2 . The var file is given below.
datacenters:
dc1:
server1:
- virtual_ro_id: "60"
server2:
- virtual_ro_id: "60"
dc2:
server1:
- virtual_ro_id: "61"
server2:
- virtual_ro_id: "61"
This is what my template syntax looks like:
{% for dc in lookup('dict', datacenters) %}
{% set dc_name=ansible_fqdn.split(.)[1] %}
{% if 'dc' == dc_name %}
ID: {{ dc.ansible_hostname.virtual_ro_id }}
{% endif %}
{% endfor %}
I usually get syntax error or no value gets by the template. Thanks in advance.

Given the inventory
shell> cat hosts
server1.dc1.com
server2.dc1.com
server1.dc2.com
server2.dc2.com
the task
- debug:
var: datacenters[mydomain][myhost][0]['virtual_ro_id']
vars:
myhost: "{{ inventory_hostname.split('.').0 }}"
mydomain: "{{ inventory_hostname.split('.').1 }}"
gives
ok: [server1.dc1.com] => {
"datacenters[mydomain][myhost][0]['virtual_ro_id']": "60"
}
ok: [server2.dc1.com] => {
"datacenters[mydomain][myhost][0]['virtual_ro_id']": "60"
}
ok: [server1.dc2.com] => {
"datacenters[mydomain][myhost][0]['virtual_ro_id']": "61"
}
ok: [server2.dc2.com] => {
"datacenters[mydomain][myhost][0]['virtual_ro_id']": "61"
}
Is this probably what you're looking for?

Related

Does Ansible Jinja2 Templating support list arguments in for loop?

I'm trying to dynamically create a template from a list and I'm wondering if Ansible supports something like
{% for server in [sg-bend1, sg-bend2] %}
check program {{ server }}_test
with path /home/ubuntu/t.sh {{ server }}
if status != 0 then alert
{% endfor %}
theoretically this should produce
check program sg-bend1_test
with path /home/ubuntu/t.sh sg-bend1
if status != 0 then alert
check program sg-bend2_test
with path /home/ubuntu/t.sh sg-bend2
if status != 0 then alert
According the provided description I understand your question is related to Jinja2 Templating and syntax only.
One approach you could try is the following
{% for i in range(1,3) %}
check program sg-bend{{ i }}_test
with path /home/ubuntu/t.sh sg-bend{{ i }}
if status != 0 then alert
{% endfor %}
Similar Q&A
How to make a for loop in Jinja?
... table with Jinja2 and Python
How can I test Jinja2 templates in Ansible?
Documentation
Jinja2 Template Designer Documentation - List of Control Structures
As far as I understand the documentation the solution should be to provide the list in a variable
{% for SERVER in SERVERS %}`
or an other syntax
{% for SERVER in ('test1', 'test2') %}
Example
---
- hosts: localhost
become: false
gather_facts: false
vars:
SERVERS: ['test1', 'test2']
tasks:
- name: Show result
debug:
msg: "{% for SERVER in SERVERS %}{{ SERVER }}{% endfor %}"
- name: Show result
debug:
msg: "{% for SERVER in ('test1', 'test2') %}{{ SERVER }}{% endfor %}"
will result into an output of
TASK [Show result] ******
ok: [localhost] =>
msg: test1test2
TASK [Show result] ******
ok: [localhost] =>
msg: test1test2

Is there any way to loop through Ansible list of dictionary registered variable in combination with Jinja2?

In my inventory I have 3 servers in a group. I want to be able to increase that size in the future so I can add more nodes into the network and generate the template with Jinja2.
- name: Gathering API results
shell:
cmd: "curl {{ groups['nodes'][node_index] }}/whatever/api/result "
loop: "{{ groups['nodes'] }}"
loop_control:
index_var: node_index
register: api_value
If I run some debug tasks hardcoding which list I want to use everyhing works fine
- debug: "msg={{ api_value.results.0.stdout }}"
- debug: "msg={{ api_value.results.1.stdout }}"
- debug: "msg={{ api_value.results.2.stdout }}"
output:
ok: [server-1] => {
"msg": "random-value-a"
ok: [server-2] => {
"msg": "random-value-b"
ok: [server-3] => {
"msg": "random-value-c"
The problem is when I try to increase the list number in Jinja template. I tried several for loops combination, nested for loops and many other things but nothing seems to be working.
For example I want my Jinja template look similar like this:
{% for vm in groups['nodes'] %}
NODE_{{ loop.index }}={{ api_value.results.{loop.index}.stdout }}
{% endfor %}
This way I want to achieve this output:
NODE_0=random-value-a
NODE_1=random-value-b
NODE_2=random-value-c
Is there any other way to workaround this? Or maybe is something I could do better in the "Gathering API results" task?
Given the inventory
shell> cat hosts
[nodes]
server-1
server-2
server-3
Either run it in the loop at a single host, e.g.
- hosts: localhost
gather_facts: false
vars:
whatever_api_result:
server-1: random-value-a
server-2: random-value-b
server-3: random-value-c
tasks:
- command: "echo {{ whatever_api_result[item] }}"
register: api_value
loop: "{{ groups.nodes }}"
- debug:
msg: "{{ api_value.results|json_query('[].[item, stdout]') }}"
gives
msg:
- - server-1
- random-value-a
- - server-2
- random-value-b
- - server-3
- random-value-c
Then, in the Jinja template, fix the index variable
- debug:
msg: |-
{% for vm in groups.nodes %}
NODE_{{ loop.index0 }}={{ api_value.results[loop.index0].stdout }}
{% endfor %}
gives what you want
msg: |-
NODE_0=random-value-a
NODE_1=random-value-b
NODE_2=random-value-c
Optionally, iterate api_value.results. This gives the same result
- debug:
msg: |-
{% for v in api_value.results %}
NODE_{{ loop.index0 }}={{ v.stdout }}
{% endfor %}
Or run it in the group, e.g.
- hosts: nodes
gather_facts: false
vars:
whatever_api_result:
server-1: random-value-a
server-2: random-value-b
server-3: random-value-c
tasks:
- command: "echo {{ whatever_api_result[inventory_hostname] }}"
register: api_value
delegate_to: localhost
- debug:
msg: "{{ api_value.stdout }}"
(delegate to localhost for testing)
gives
ok: [server-1] =>
msg: random-value-a
ok: [server-2] =>
msg: random-value-b
ok: [server-3] =>
msg: random-value-c
Then, in the Jinja template, use hostvars
- debug:
msg: |-
{% for vm in groups.nodes %}
NODE_{{ loop.index0 }}={{ hostvars[vm].api_value.stdout }}
{% endfor %}
run_once: true
gives also what you want
msg: |-
NODE_0=random-value-a
NODE_1=random-value-b
NODE_2=random-value-c
Optionally, iterate hostvars. This gives the same result
- debug:
msg: |-
{% for k,v in hostvars.items() %}
NODE_{{ loop.index0 }}={{ v.api_value.stdout }}
{% endfor %}
run_once: true

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 template with list of hosts excluding current

I'm super fresh to ansible and creating a playbook that in one of the tasks should copy templated file and replace values in 2 lines. First line should have current hostname, and in second semicolon separated list of all other hosts (used in the play) - it will be different group
First line is super easy, as it's just:
localnode={{ inventory_hostname }}
but I'm having problem with exclusion in the second line. I'd like something similar to:
{% for host in groups.nodes -%} # but without inventory_hostname
othernodes={{ host }}{% if not loop.last %};{% endif %}
{%- endfor %}
Given the inventory of:
nodes:
hosts:
hosta:
hostb:
hostc:
hostd:
I'd like to get following output (example for hostd):
localnode=hostd
othernodes=hosta,hostb,hostc
I'll be very grateful for all hints on possible solution
Create the list of hosts without inventory_hostname and use it in the template
- set_fact:
list_other_hosts: "{{ groups.nodes|difference([inventory_hostname]) }}"
Simplify the template
othernodes={{ list_other_hosts|join(';') }}
As an example, the inventory
shell> cat hosts
test_jails:
hosts:
test_01:
test_02:
test_03:
and the play
- hosts: test_jails
tasks:
- set_fact:
list_other_hosts: "{{ groups.test_jails|
difference([inventory_hostname]) }}"
- debug:
msg: "{{ msg.split('\n') }}"
vars:
msg: |-
localnode={{ inventory_hostname }}
othernodes={{ list_other_hosts|join(';') }}
give
TASK [debug] ********************************************************
ok: [test_01] => {
"msg": [
"localnode=test_01",
"othernodes=test_02;test_03"
]
}
ok: [test_02] => {
"msg": [
"localnode=test_02",
"othernodes=test_01;test_03"
]
}
ok: [test_03] => {
"msg": [
"localnode=test_03",
"othernodes=test_01;test_02"
]
}

How can I expand multiple lists in the same task

I have a situation where I have two different variables, which I want to reference in a single command:
For example, I am expecting the following as output :
list 1
item a
item b
list 2
another item from different var 10
-name : Run a module which executes a command on a host eg. via ssh
command:
host: {{ device_ip }}
cmd_str:
- 'list 1 '
- ' {{ item item[0].name }}'
- 'list 2 '
- ' {{ another item from different var item[1].id }}'
with_items:
- {{ var1 }}
- {{ var2 }}
var1:
- { name:a, address:test }
- { name:b, address:test2 }
var2:
- { name:x, id:10 }
What do I write instead of "with_items" to get this done?
The issue is how do I expand two different variables, in the same place without having to iterate the entire command (this is do-able if I move with_items to the same indentation level as the module invocation)
I cannot understand what you actually want to do, but the following playbook demonstrates:
Passing multiple vars in a single item using dict
Iterating over each var using Jinja2 template
playbook.yml:
---
- hosts: all
gather_facts: no
vars:
var1:
- { name: a, address: test }
- { name: b, address: test2 }
var2:
- { name: x, id: 10 }
tasks:
- debug:
msg: |
list 1
{% for x in item.1 %}
item {{x.name}}
{% endfor %}
list 2
{% for x in item.2 %}
another item from different var {{x.id}}
{% endfor %}
with_items:
- { 1: "{{var1}}", 2: "{{var2}}" }
- shell: |
>/tmp/output.txt # truncate file
{% for x in item.1 %}
echo item {{x.name}} >>/tmp/output.txt
{% endfor %}
{% for x in item.2 %}
echo another item from different var {{x.id}} >>/tmp/output.txt
{% endfor %}
with_items:
- { 1: "{{var1}}", 2: "{{var2}}" }
Sample session:
$ ansible-playbook -i localhost, playbook.yml
PLAY [all] ********************************************************************
TASK: [debug ] ****************************************************************
ok: [localhost] => (item={1: [{'name': 'a', 'address': 'test'}, {'name': 'b', 'address': 'test2'}], 2: [{'name': 'x', 'id': 10}]}) => {
"item": {
"1": [
{
"address": "test",
"name": "a"
},
{
"address": "test2",
"name": "b"
}
],
"2": [
{
"id": 10,
"name": "x"
}
]
},
"msg": "list 1\n item a\n item b\nlist 2\n another item from different var 10\n"
}
TASK: [shell >/tmp/output.txt # truncate file
{% for x in item.1 %}
echo item {{x.name}} >>/tmp/output.txt
{% endfor %}
{% for x in item.2 %}
echo another item from different var {{x.id}} >>/tmp/output.txt
{% endfor %}
] ***
changed: [localhost] => (item={1: [{'name': 'a', 'address': 'test'}, {'name': 'b', 'address': 'test2'}], 2: [{'name': 'x', 'id': 10}]})
PLAY RECAP ********************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0
Output shown in msg from debug module:
list 1
item a
item b
list 2
another item from different var 10
Output in /tmp/output.txt from shell module:
item a
item b
another item from different var 10
Here is the Ansible docs page on loops: http://docs.ansible.com/ansible/playbooks_loops.html
I think you are looking for either nested loops, subelements, or looping over parallel sets.

Resources