How can I expand multiple lists in the same task - yaml

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.

Related

can't access dictionary values in jinja

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

Looping over a nested ansible dict

I have a given vars as a list of kafka topics and possible configurations, like:
kafka_topics:
foo:
retentiontime: 3600
deletepolicy: delete
bar:
retentiontime: 3600
compression: gzip
I have figured multiple ways with dict2items to set them explicit like:
- name: Set RetentionTime for Topics
debug:
msg: "Topic {{ item.key }} get {{ item.value.retentiontime }} for retentiontime "
loop: "{{ lookup('dict', kafka_topics) }}"
when: item.value.retentiontime is defined
But is it possible to get a output like
Topic foo get 3600 for retentiontime
Topic foo get delete for deletepolicy
Topic bar get 3600 for retentiontime
Topic bar get gzip for compression
without defining the value name by name with ansible?
I also tried with
- name: Loop over subelements of the dictionary
debug:
msg: "Key={{ item.0.key }} value={{ item.1 }}"
loop: "{{ lookup('dict', kafka_topics) | list | subelements('value') }}"
which prints Key=bar value={'compression': gzip} but now i'm stuck at seperating those two. Is there a way to extract from item.1 the key and the value?
The simplest option is Jinja. For example
- debug:
msg: |-
{% for k1,v1 in kafka_topics.items() %}
{% for k2,v2 in v1.items() %}
Topic {{ k1 }} get {{ v2 }} for {{ k2 }}
{% endfor %}
{% endfor %}
gives
msg: |-
Topic foo get 3600 for retentiontime
Topic foo get delete for deletepolicy
Topic bar get 3600 for retentiontime
Topic bar get gzip for compression
Create a dictionary of lists first if the iteration is needed. For example the template
shell> cat templates/kafka_facts_list.j2
{% for k1,v1 in kafka_topics.items() %}
{{ k1 }}:
{% for k2,v2 in v1.items() %}
- {key: {{ k2 }}, val: {{ v2 }}}
{% endfor %}
{% endfor %}
- set_fact:
dict2: "{{ lookup('template', 'kafka_facts_list.j2')|from_yaml }}"
- debug:
var: dict2
gives
dict2:
bar:
- key: retentiontime
val: 3600
- key: compression
val: gzip
foo:
- key: retentiontime
val: 3600
- key: deletepolicy
val: delete
Then use subelements
- debug:
msg: "Topic {{ item.0.key }} get {{ item.1.val }} for {{ item.1.key }}"
with_subelements:
- "{{ dict2|dict2items }}"
- value
gives
msg: Topic foo get 3600 for retentiontime
msg: Topic foo get delete for deletepolicy
msg: Topic bar get 3600 for retentiontime
msg: Topic bar get gzip for compression
#Vladimir's answer is indeed the most straightforward one if you need to write some debugging info as above or create a configuration file from a template.
But this might get a little trickier if you actually need to loop over this data in a classic task where you have to pass each value separately to the corresponding moldule's option.
In such cases, here is an alternative that will transform your current dict to list.
The goal is to go from:
"first topic name":
"option 1 name": "option 1 value"
"option 2 name": "option 2 value"
"second topic name": ...
to
- topic_name: "first topic name"
topic_options:
- option_name: "option 1 name"
option_value: "option 1 value"
- option_name: "option 2 name"
option_value: "option 2 value"
- topic_name: "second topic name"
...
This transformed data is then easily loopable with subelements as demonstrated in the following playbook:
---
- hosts: localhost
gather_facts: false
vars:
kafka_topics: {"foo": {"retentiontime": 3600, "deletepolicy": "delete"}, "bar": {"retentiontime": 3600, "compression": "gzip"}}
tasks:
- name: Transform our list to something easier to loop with subelements
vars:
current_topic:
topic_name: "{{ item.topic_name }}"
topic_options: "{{ item.topic_options | dict2items(key_name='option_name', value_name='option_value') }}"
set_fact:
my_topic_list: "{{ my_topic_list | default([]) + [current_topic] }}"
loop: "{{ kafka_topics | dict2items(key_name='topic_name', value_name='topic_options') }}"
- name: Show the transformed var
debug:
var: my_topic_list
- name: Loop over topics and their options
debug:
msg: "Topic `{{ topic.0.topic_name }}` has option `{{ topic.1.option_name }}` with value `{{ topic.1.option_value }}`"
loop: "{{ my_topic_list | subelements('topic_options') }}"
loop_control:
label: "{{ topic.0.topic_name }} - {{ topic.1.option_name }} - {{ topic.1.option_value }}"
loop_var: topic
Which gives:
PLAY [localhost] ***********************************************************************************************************************************************************************************************************************
TASK [Transform our list to something easier to loop with subelements] *****************************************************************************************************************************************************************
ok: [localhost] => (item={'topic_name': 'foo', 'topic_options': {'retentiontime': 3600, 'deletepolicy': 'delete'}})
ok: [localhost] => (item={'topic_name': 'bar', 'topic_options': {'retentiontime': 3600, 'compression': 'gzip'}})
TASK [Show the transformed var] ********************************************************************************************************************************************************************************************************
ok: [localhost] => {
"my_topic_list": [
{
"topic_name": "foo",
"topic_options": [
{
"option_name": "retentiontime",
"option_value": 3600
},
{
"option_name": "deletepolicy",
"option_value": "delete"
}
]
},
{
"topic_name": "bar",
"topic_options": [
{
"option_name": "retentiontime",
"option_value": 3600
},
{
"option_name": "compression",
"option_value": "gzip"
}
]
}
]
}
TASK [Loop over topics and their options] **********************************************************************************************************************************************************************************************
ok: [localhost] => (item=foo - retentiontime - 3600) => {
"msg": "Topic `foo` has option `retentiontime` with value `3600`"
}
ok: [localhost] => (item=foo - deletepolicy - delete) => {
"msg": "Topic `foo` has option `deletepolicy` with value `delete`"
}
ok: [localhost] => (item=bar - retentiontime - 3600) => {
"msg": "Topic `bar` has option `retentiontime` with value `3600`"
}
ok: [localhost] => (item=bar - compression - gzip) => {
"msg": "Topic `bar` has option `compression` with value `gzip`"
}
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Complex Variable Structure with Jinja2

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?

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"
]
}

Yet another Ansible iteration

I always struggle with iterations in Ansible and this time is no different.
Anyway, I have a custom fact being returned as a list of dictionaries my_list which I need to iterate to build a new variables named after the key = 'name'
[localhost] => {
"ansible_facts": {
"my_list": [
{
"name": "asd-ASX030-vc-0",
"key_2": [
"A",
"B"
]
},
{
"name": "asd-ASX030-vc-1",
"key_2": [
"C",
"D"
]
}
]
}
...
}
I can run a single list index.
tasks:
- set_fact: set_fact: {"{{item[1]['name'].split('-')[2:] | join('_')}}":"{{item[1]['key_2']}}"}
loop: "{{ my_list }}"
What I'm after is.
vc_0 = ["A", "B"]
vc_1 = ["C", "D"]
Simple:
set_fact: {"{{item['name'].split('-')[2:] | `enter code here`join('_')}}":"{{item['key_2']}}"}
loop: "{{ my_list }}"
Doesn't work:
"The task includes an option with an undefined variable. The error was: 'list object' has no attribute 'key_2'
I would like to avoid hard coding list indexes, how can I do this dynamically?
Is this the code (without formatting) that you're looking for?
shell> cat play.yml
- hosts: localhost
vars:
my_list:
- name: "asd-ASX030-vc-0"
key_2: ["A", "B"]
- name: "asd-ASX030-vc-1"
key_2: ["C", "D"]
tasks:
- debug:
msg: "{{ item.name }} = {{ item.key_2 }}"
loop: "{{ my_list }}"
shell> ansible-playbook play.yml | grep msg
msg: asd-ASX030-vc-0 = [u'A', u'B']
msg: asd-ASX030-vc-1 = [u'C', u'D']
I think you use Jinja expressions to construct the variables:
set_fact:
var2: {% for item in ansible_facts %}\
{{ item.name }}\
{% if not loop.last %},\
{% endif %}\
{% endfor %}

Resources