can't access dictionary values in jinja - ansible

I'm creating a dict, it works fine. but after I create it i can't access the values of in the dict only the keys.
the dict:
- set_fact:
dictonary_obj: |
{% set record_info = dict() %}
{% for item in list%}
{% set _dummy = record_info.update({ item: 0 }) %}
{%endfor%}
"{{ record_info }}"
the access:
- debug:
var: dictonary_obj[item]
with_items: "{{list}}"

Remove the quotation
- set_fact:
dictonary_obj: |
{% set record_info = dict() %}
{% for item in list1 %}
{% set _dummy = record_info.update({ item: 0 }) %}
{%endfor%}
{{ record_info }}
For example, given the list
list1: [a, b, c]
the dictionary is
dictonary_obj:
a: 0
b: 0
c: 0
and the loop works as expected
- debug:
var: dictonary_obj[item]
loop: "{{ list1 }}"
ok: [localhost] => (item=a) =>
ansible_loop_var: item
dictonary_obj[item]: '0'
item: a
ok: [localhost] => (item=b) =>
ansible_loop_var: item
dictonary_obj[item]: '0'
item: b
ok: [localhost] => (item=c) =>
ansible_loop_var: item
dictonary_obj[item]: '0'
item: c
Try the simpler option below which gives the same result
- set_fact:
dictonary_obj: "{{ _dict|from_yaml }}"
vars:
_dict: |
{% for item in list1 %}
{{ item }}: 0
{% endfor %}

Related

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

Convert list of dicts to list of strings in Ansible

Here's something I think should be straight-forward in Ansible, but I'm having trouble finding a canonical best practice.
In Ansible, I have a list of dicts in YAML:
config:
- id: x
value: X
- id: y
value: Y
I want to generate a list of strings that can be passed as a whole to the mysql_query module.
I've tried various elegant ways of doing this using Ansible filters, but that best I've come up with is to generate a list of newline-separate strings in a single template that iterates over config and then trim | split('\n') the resulting single string.
Template:
{% for item in config %}
{{ item.id }} => {{ item.value }}
{% endfor %}
Playbook task:
set_fact:
converted_items: "{{ lookup('template', './template.j2') | trim | split('\n') }}"
But this feels like a kludge.
What am I missing here?
[Note this is a canned example, to keep things simple.]
if you want to transform list of dicts to list of strings, just use loop and union:
- name: vartest
hosts: localhost
vars:
config:
- id: x
value: X
- id: y
value: Y
tasks:
- name: transform value
set_fact:
result: "{{ result | default([]) | union([item.id ~ ' => ' ~ item.value]) }}"
loop: "{{ config }}"
- name: display result
debug:
var: result
equivalent of union: result | default([]) + [item.id ~ ' => ' ~ item.value]
result:
ok: [localhost] => {
"result": [
"x => X",
"y => Y"
]
}
You don't have to use a file. Put the Jinja template into the local vars, e.g.
- set_fact:
converted_items: "{{ _converted_items|trim|split('\n') }}"
vars:
_converted_items: |
{% for item in config %}
{{ item.id }} => {{ item.value }}
{% endfor %}
gives
converted_items:
- x => X
- y => Y
There are many options of how to transform the data, e.g. the task below gives the same result
- set_fact:
converted_items: "{{ _keys|zip(_vals)|map('join', ' => ')|list }}"
vars:
_keys: "{{ config|map(attribute='id')|list }}"
_vals: "{{ config|map(attribute='value')|list }}"

Joining elements of nested lists in Ansible

I have a nested list str that looks like below:
[["22","ABC","XYZ"],["555","IJK","PQR"],...]
I have to combine the elements of the inside list with a / then join them with a , to form a string as:
22/ABC/XYZ,555/IJK/PQR,...
I tried with set_fact and jinja2 but no luck.
- set_fact:
str1: |-
{%- set fs = "" -%}
{%- set im = "" -%}
{%- for i in str -%}
{%- for elem in i -%}
{%- set im = im + "/" + elem -%}
{%- endfor -%}
{%- set fs = fs + "," + im -%}
{%- endfor -%}
{{ fs }}
- debug: var=str1
Output:
TASK [debug var=str1] **********************************
ok: [host1] => {
"str1": ""
Expected output:
TASK [debug var=str1] **********************************
ok: [host1] => {
"str1": "22/ABC/XYZ,555/IJK/PQR"
Thanks
First map the filter join(/) to the items of the list and then join(,) them
- set_fact:
str1: "{{ str|map('join', '/')|join(',') }}"
- debug: var=str1
gives
str1: 22/ABC/XYZ,555/IJK/PQR
Use wiht_list to create a var with a list with items joined with / and later join it with ,.
- set_fact:
str1: "{{ str1 | default([]) + [ item | join('/') ] }}"
with_list: "{{ str }}"
- debug:
msg: "{{ str1 | join(',') }}"

How to create list for with_item from included list

I have this list:
"mylist": [
{ "files": [{"path": "path11"}, {"path": "path12"}] },
{ "files": [{"path": "path21"}, {"path": "path22"}] }
]
and I need to run role with with_items, where items should be path elements from my list.
For example:
- debug: msg="{{ item }}"
with_items: "{{ mylist | some_magic }}"
Needed output:
TASK [test : debug] **********************************
ok: [host] => (item=path11 ) => {
"msg": "path11"
}
ok: [host] => (item=path12 ) => {
"msg": "path12"
}
ok: [host] => (item=path21 ) => {
"msg": "path21"
}
...
Is it possible?
This is what I have already tried:
Constructions look like this:
- debug: msg="{{ item }}"
with_items: "{% for files in mylist | map(attribute='files') %}{% for file in files %}{{file.path}}{% endfor %}{% endfor %}"
Returned value as expected is not a list.
Wrong constructions look like this:
- debug: msg="{{ item }}"
with_items: "{{ mylist | map(attribute='files') | map(attribute='path') | list }}"
It is a legacy with_subelements loop pattern.
Using the loop keyword (Ansible 2.5 and later) you can iterate with:
- debug:
msg: "{{ item.1.path }}"
loop: "{{ mylist | subelements('files') }}"
Or the same using JMESPath (this shows how to create a list out of the whole data structure):
- debug:
msg: "{{ item }}"
loop: "{{ mylist | json_query('[].files[].path') }}"

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