How to loop over this dictionary in Ansible? - ansible

Say I have this dictionary
war_files:
server1:
- file1.war
- file2.war
server2:
- file1.war
- file2.war
- file3.war
and for now I just want to loop over each item (key), and then over each item in the key (value). I did this
- name: Loop over the dictionary
debug: msg="Key={{ item.key }} value={{ item.value }}"
with_dict: "{{ war_files }}"
And I get this. It is of course correct, but is NOT what I want.
ok: [localhost] => (item={'value': [u'file1.war', u'file2.war'], 'key': u'server1'}) => {
"item": {
"key": "server1",
"value": [
"file1.war",
"file2.war"
]
},
"msg": "Server=server1, WAR=[u'file1.war', u'file2.war']"
}
ok: [localhost] => (item={'value': [u'file1.war', u'file2.war', u'file3.war'], 'key': u'server2'}) => {
"item": {
"key": "server2",
"value": [
"file1.war",
"file2.war",
"file3.war"
]
},
"msg": "Server=server2, WAR=[u'file1.war', u'file2.war', u'file3.war']"
}
I want to get an output that says
"msg": "Server=server1, WAR=file1.war"
"msg": "Server=server1, WAR=file2.war"
"msg": "Server=server2, WAR=file1.war"
"msg": "Server=server2, WAR=file2.war"
"msg": "Server=server2, WAR=file3.war"
IOW, how can I write a task to iterates over the dictionary so it goes through each key, and then the items within each key? In essence, I have a nested array and want to iterate over it?

Hows this
- hosts: localhost
vars:
war_files:
server1:
- file1.war
- file2.war
server2:
- file1.war
- file2.war
- file3.war
tasks:
- name: Loop over subelements of the dictionary
debug:
msg: "Key={{ item.0.key }} value={{ item.1 }}"
loop: "{{ war_files | dict2items | subelements('value') }}"
dict2items, subelements filters are coming in Ansible 2.6.
FYI, if a filter for your objective doesn't exist, you can write your own in python without having to resort to jinja2 hacks. Ansible is easily extendable; filters in filter_plugins/*.py are searched by default adjacent to your plays/roles and are automatically included - see Developing Plugins for details.

Now Ansible allows this
- name: add several users
user:
name: "{{ item.name }}"
state: present
groups: "{{ item.groups }}"
with_items:
- { name: 'testuser1', groups: 'wheel' }
- { name: 'testuser2', groups: 'root' }

EDIT: At the time of writing this answer, Ansible 2.6 wasn't out. Please read the answer provided by #tmoschou, as it is much better.
Well, I couldn't find a very easy way to do it, however, with a little bit of jinja2, we can achieve something of this sort:
/tmp ❯❯❯ cat example.yml
---
- hosts: 127.0.0.1
vars:
war_files:
server1:
- file1.war
- file2.war
server2:
- file1.war
- file2.war
- file3.war
tasks:
- set_fact:
war_files_list_of_dicts: |
{% set res = [] -%}
{% for key in war_files.keys() -%}
{% for value in war_files[key] -%}
{% set ignored = res.extend([{'Server': key, 'WAR':value}]) -%}
{%- endfor %}
{%- endfor %}
{{ res }}
- name: let's debug the crap out of this
debug: var=war_files_list_of_dicts
- name: Servers and their WARs!!!
debug:
msg: "Server={{ item.Server }}, WAR={{ item.WAR }}"
with_items: "{{ war_files_list_of_dicts }}"
And, when the playbook is run:
/tmp ❯❯❯ ansible-playbook example.yml
[WARNING]: provided hosts list is empty, only localhost is available
PLAY [127.0.0.1] ***************************************************************
TASK [setup] *******************************************************************
ok: [127.0.0.1]
TASK [set_fact] ****************************************************************
ok: [127.0.0.1]
TASK [let's debug the crap out of this] ****************************************
ok: [127.0.0.1] => {
"war_files_list_of_dicts": [
{
"Server": "server1",
"WAR": "file1.war"
},
{
"Server": "server1",
"WAR": "file2.war"
},
{
"Server": "server2",
"WAR": "file1.war"
},
{
"Server": "server2",
"WAR": "file2.war"
},
{
"Server": "server2",
"WAR": "file3.war"
}
]
}
TASK [Servers and their WARs!!!] ***********************************************
ok: [127.0.0.1] => (item={'WAR': u'file1.war', 'Server': u'server1'}) => {
"item": {
"Server": "server1",
"WAR": "file1.war"
},
"msg": "Server=server1, WAR=file1.war"
}
ok: [127.0.0.1] => (item={'WAR': u'file2.war', 'Server': u'server1'}) => {
"item": {
"Server": "server1",
"WAR": "file2.war"
},
"msg": "Server=server1, WAR=file2.war"
}
ok: [127.0.0.1] => (item={'WAR': u'file1.war', 'Server': u'server2'}) => {
"item": {
"Server": "server2",
"WAR": "file1.war"
},
"msg": "Server=server2, WAR=file1.war"
}
ok: [127.0.0.1] => (item={'WAR': u'file2.war', 'Server': u'server2'}) => {
"item": {
"Server": "server2",
"WAR": "file2.war"
},
"msg": "Server=server2, WAR=file2.war"
}
ok: [127.0.0.1] => (item={'WAR': u'file3.war', 'Server': u'server2'}) => {
"item": {
"Server": "server2",
"WAR": "file3.war"
},
"msg": "Server=server2, WAR=file3.war"
}
PLAY RECAP *********************************************************************
127.0.0.1 : ok=4 changed=0 unreachable=0 failed=0

Here is my preferred way to loop over dictionaries:
input_data.yml contains the following:
----
input_data:
item_1:
id: 1
info: "Info field number 1"
item_2:
id: 2
info: "Info field number 2"
I then use a data structure like the above in a play using the keys() function and iterate over the data using with_items:
---
- hosts: localhost
gather_facts: false
connection: local
tasks:
- name: Include dictionary data
include_vars:
file: data.yml
- name: Show info field from data.yml
debug:
msg: "Id: {{ input_data[item]['id'] }} - info: {{ input_data[item]['info'] }}"
with_items: "{{ input_data.keys() | list }}"
The above playbook produces the following output:
PLAY [localhost] ***********************************************************
TASK [Include dictionary data] *********************************************
ok: [localhost]
TASK [Show info field from data.yml] ***************************************
ok: [localhost] => (item=item_2) => {
"msg": "Id: 2 - info: Info field item 2"
}
ok: [localhost] => (item=item_3) => {
"msg": "Id: 3 - info: Info field item 3"
}
ok: [localhost] => (item=item_1) => {
"msg": "Id: 1 - info: Info field item 1"
}
PLAY RECAP *****************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0

dict2items
I found myself wanting to iterate over a heterogeneous set of keys and their associated values and use the key-value pair in a task. The dict2items filter is the least painful way I've found. You can find dict2items in Ansible 2.6
Example Dict
systemsetup:
remotelogin: "On"
timezone: "Europe/Oslo"
usingnetworktime: "On"
sleep: 0
computersleep: 0
displaysleep: 0
harddisksleep: 0
allowpowerbuttontosleepcomputer: "Off"
wakeonnetworkaccess: "On"
restartfreeze: "On"
restartpowerfailure: "On"
Example Task
---
- debug:
msg: "KEY: {{ item.key }}, VALUE: {{ item.value }}"
loop: "{{ systemsetup | dict2items }}"

One way of doing it that worked for me was using with_dict. Note the dict should not be named. Just the key value pairs.
- name: ssh config
lineinfile:
dest: /etc/ssh/sshd_config
regexp: '^#?\s*{{item.key}}\s'
line: '{{item.key}} {{item.value}}'
state: present
with_dict:
LoginGraceTime: "1m"
PermitRootLogin: "yes"
PubkeyAuthentication: "yes"
PasswordAuthentication: "no"
PermitEmptyPasswords: "no"
IgnoreRhosts: "yes"
Protocol: 2

Related

ansible_facts.packages "skipping" my package check

I would like to check if my package is installed, but I would like to have all package types with a name.
Let me explain: I am looking for the OB2 package, but there are plenty of them, so I am looking for OB2* but it skips my search.
I tested with the name without wildcard (*) but it doesn't work any better:
- name: Gather the package facts
ansible.builtin.package_facts:
manager: auto
- name: Print the package facts
ansible.builtin.debug:
var: ansible_facts.packages
- name: Check whether a package called OB2 is installed
ansible.builtin.debug:
msg: "{{ ansible_facts.packages['OB2*'] | length }} versions of OB2are installed!"
when: "'OB2*' in ansible_facts.packages"
Is there a solution to find all outputs that start with OB2 within ansible_facts.package?
ansible_facts.packages is a dictionary. There are no keys in that dictionary named OB2* nor OB2 since no package has this exact name.
If you want to get all keys which names start with the string "OB2", one way is to filter out all others.
Transform you dict to a key/value list with the dict2items filter
Use the selectattr filter and apply the match test to find the relevant entries.
Since the key name is also contained in the parameter name inside each element in the value list , you can retain only the value using the map(attribute='someattr') filter
Last, flatten the list to get one single list containing all packages version
Here is a playbook illustrating the concept. For the example, I used as a prefix "zlib". Just change it back to whatever suits your needs
---
- hosts: localhost
gather_facts: false
vars:
package_prefix: "zlib"
filtered_packages: "{{ ansible_facts.packages | dict2items
| selectattr('key', 'match', package_prefix)
| map(attribute='value') | flatten }}"
tasks:
- name: gather package facts
ansible.builtin.package_facts:
- name: debug the raw variable
debug:
var: filtered_packages
- name: count relevant packages
vars:
pkg_num: "{{ filtered_packages | count }}"
debug:
msg: "There are {{ pkg_num }} packages
which name starts with {{ package_prefix }}"
- name: show some info about relevant packages
debug:
msg: "Package named {{ item.name }} is in category {{ item.category }}
and has version {{ item.version }}"
loop: "{{ filtered_packages }}"
Which gives on my Ubuntu local machine:
PLAY [localhost] *****************************************************************************************************************
TASK [gather package facts] ******************************************************************************************************
ok: [localhost]
TASK [debug the raw variable] ****************************************************************************************************
ok: [localhost] => {
"filtered_packages": [
{
"arch": "amd64",
"category": "libs",
"name": "zlib1g",
"origin": "Ubuntu",
"source": "apt",
"version": "1:1.2.11.dfsg-2ubuntu1.3"
},
{
"arch": "amd64",
"category": "libdevel",
"name": "zlib1g-dev",
"origin": "Ubuntu",
"source": "apt",
"version": "1:1.2.11.dfsg-2ubuntu1.3"
}
]
}
TASK [count relevant packages] ***************************************************************************************************
ok: [localhost] => {
"msg": "There are 2 packages which name starts with zlib"
}
TASK [show some info about relevant packages] ************************************************************************************
ok: [localhost] => (item={'name': 'zlib1g', 'version': '1:1.2.11.dfsg-2ubuntu1.3', 'arch': 'amd64', 'category': 'libs', 'origin': 'Ubuntu', 'source': 'apt'}) => {
"msg": "Package named zlib1g is in category libs and has version 1:1.2.11.dfsg-2ubuntu1.3"
}
ok: [localhost] => (item={'name': 'zlib1g-dev', 'version': '1:1.2.11.dfsg-2ubuntu1.3', 'arch': 'amd64', 'category': 'libdevel', 'origin': 'Ubuntu', 'source': 'apt'}) => {
"msg": "Package named zlib1g-dev is in category libdevel and has version 1:1.2.11.dfsg-2ubuntu1.3"
}
PLAY RECAP ***********************************************************************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
You have to filter the keys, a direct address is not possible, because you don't know the exact name.
- name: Gather the package facts
ansible.builtin.package_facts:
- name: Filter package names
set_fact:
filtered_package_names: "{{ ansible_facts.packages | list
| map('regex_search', '^vim.*') | select('string') | list }}"
- name: Print filtered packages
debug:
var: filtered_package_names
- name: Print package details from all filtered packages
debug:
msg: "{{ ansible_facts.packages[item] }}"
with_items: "{{ filtered_package_names }}"
With list a list of the keys is created, then you can filter this list with regex_search, afterwards the list is reduced to the filter result.
== Edit begin
There is a smarter filter method. Instead of using map(regex_search) / select(string), you could use directly select(match), so the filtering would look like:
- name: Filter package names
set_fact:
filtered_package_names: "{{ ansible_facts.packages | list
| select('match', '^vim.*') | list }}"
== Edit end
The result is a list of package names that match your regex.
If you need more information about one of the packages, you can then use ansible_facts.packages[_your_item] to get the rest of the information.
Example output of the above tasks:
TASK [Gather the package facts] ****************************************************************************************
ok: [localhost]
TASK [Filter package names] ********************************************************************************************
ok: [localhost]
TASK [Print filtered packages] *****************************************************************************************
ok: [localhost] => {
"filtered_package_names": [
"vim",
"vim-common",
"vim-runtime",
"vim-tiny"
]
}
TASK [Print package details] *******************************************************************************************
ok: [localhost] => (item=vim) => {
"msg": [
{
"arch": "amd64",
"category": "editors",
"name": "vim",
"origin": "Ubuntu",
"source": "apt",
"version": "2:8.1.2269-1ubuntu5.7"
}
]
}
ok: [localhost] => (item=vim-common) => {
"msg": [
{
"arch": "all",
"category": "editors",
"name": "vim-common",
"origin": "Ubuntu",
"source": "apt",
"version": "2:8.1.2269-1ubuntu5.7"
}
]
}
ok: [localhost] => (item=vim-runtime) => {
"msg": [
{
"arch": "all",
"category": "editors",
"name": "vim-runtime",
"origin": "Ubuntu",
"source": "apt",
"version": "2:8.1.2269-1ubuntu5.7"
}
]
}
ok: [localhost] => (item=vim-tiny) => {
"msg": [
{
"arch": "amd64",
"category": "editors",
"name": "vim-tiny",
"origin": "Ubuntu",
"source": "apt",
"version": "2:8.1.2269-1ubuntu5.7"
}
]
}

Ansible subelements() filter where a subelement is an empty list

Trying to use subelement() filter where sometimes the list will be empty. I would still like to get a list where item.0 is populated but maybe item.1 is Undefined or None. Not sure this is possible.
Sample Playbook
- hosts: localhost
connection: local
vars:
result:
virtual_machine:
interfaces:
- name: interface 1
ip_addresses:
- address: 192.168.1.22
enabled: yes
- name: interface 2
ip_addresses: []
tasks:
- name: starting var
debug: var=result
- name: filter data
set_fact:
vm_interfaces: >
{{ result.virtual_machine.interfaces
| default({})
| subelements('ip_addresses', skip_missing=True)
| list }}
- name: new filtered var
debug: var=vm_interfaces
- name: loop through new var
debug:
msg: '{{ item.0.name }} {{ item.1.address }}'
loop: '{{ vm_interfaces }}'
loop_control:
label: '{{ item.0.name }}'
Sample Output
TASK [starting var] *************************************************************************************************
ok: [localhost] => {
"result": {
"virtual_machine": {
"interfaces": [
{
"ip_addresses": [
{
"address": "192.168.1.22",
"enabled": true
}
],
"name": "interface 1"
},
{
"ip_addresses": [],
"name": "interface 2"
}
]
}
}
}
TASK [filter data] **************************************************************************************************
ok: [localhost]
TASK [new filtered var] *********************************************************************************************
ok: [localhost] => {
"vm_interfaces": [
[
{
"ip_addresses": [
{
"address": "192.168.1.22",
"enabled": true
}
],
"name": "interface 1"
},
{
"address": "192.168.1.22",
"enabled": true
}
]
]
}
TASK [loop through new var] *****************************************************************************************
ok: [localhost] => (item=interface 1) => {
"msg": "interface 1 192.168.1.22"
Current less Ansible-y Solution
The way I am currently solving this problem now is to use nested Include_tasks essentially in (2) loops. This works, but my understanding is one should strive to manipulate the data to the point you can use loop.
Here's a possible way to do it. I'm simply replacing the empty ip list with a list containing an empty object. Adapt to your own needs.
- hosts: localhost
gather_facts: false
vars:
result:
virtual_machine:
interfaces:
- name: interface 1
ip_addresses:
- address: 192.168.1.22
enabled: yes
- name: interface 2
ip_addresses: []
replace_empty_interface:
ip_addresses: [{}]
vm_interfaces_empty: >
{{
result.virtual_machine.interfaces
| default({})
| selectattr('ip_addresses', 'defined')
| selectattr('ip_addresses', '==', [])
| map('combine', replace_empty_interface)
| list
}}
vm_interfaces_filled: >
{{
result.virtual_machine.interfaces
| default({})
| selectattr('ip_addresses', 'defined')
| rejectattr('ip_addresses', '==', [])
| list
}}
vm_interfaces: >
{{
(vm_interfaces_empty + vm_interfaces_filled)
| subelements('ip_addresses')
| list
}}
tasks:
- debug:
msg: "{{ item.0 }} | {{ item.1 }}"
loop: "{{ vm_interfaces }}"
Result:
TASK [debug] ***********************************************************************************************************************************************************************************
Sunday 09 May 2021 10:14:13 +0200 (0:00:00.088) 0:00:00.088 ************
ok: [localhost] => (item=[{'name': 'interface 2', 'ip_addresses': [{}]}, {}]) => {
"msg": "{'name': 'interface 2', 'ip_addresses': [{}]} | {}"
}
ok: [localhost] => (item=[{'name': 'interface 1', 'ip_addresses': [{'address': '192.168.1.22', 'enabled': True}]}, {'address': '192.168.1.22', 'enabled': True}]) => {
"msg": "{'name': 'interface 1', 'ip_addresses': [{'address': '192.168.1.22', 'enabled': True}]} | {'address': '192.168.1.22', 'enabled': True}"
}
PLAY RECAP *************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Ansible set_fact from dictionary based on item type

I am reading a dictionary from a Kubernetes config map (cm_config below) and am using it to replace variables set in defaults/main.yml like this:
- name: 'Overwrite defaults'
set_fact: "{{ item.key }}={{ item.value }}"
with_dict: "{{ cm_config }}"
This works fine as long as the items are simple variables. But as soon as an item is another dictionary, I'd like to combine the values.
How can I integrate this into the above task? I thought about running the loop twice, with some kind of type check. Not sure how this would work. Additionally, I believe there might be a better way?
One solution below to achieve your requirement in a single task whit just a bit of jinja2 templating and a vars lookup to get existing dict content. The key is to calculate the value based on the variable type.
Note that this does not take into account the situations when the var is a list which will be replaced as all other type of values. This will not either deal with type mismatch between existing vars and config map. e.g. if your existing var is string and the corresponding one in config map a dict it will break.
The following playbook:
---
- hosts: localhost
gather_facts: false
vars:
cm_config:
label1: toto
label2:
a_value: 1
other_value: 2
label3:
a_value: 3
other_value: 4
label4: tata
label1: I am set in play
label3:
some_value: I'm a poor lonesome cowboy
tasks:
- name: show initial state
debug:
var: "{{ item.key }}"
with_dict: "{{ cm_config }}"
- name: Process values from config map
vars:
my_value: >-
{% if item.value is mapping %}
{{ lookup('vars', item.key, default={}) | combine(item.value) }}
{% else %}
{{ item.value }}
{% endif %}
set_fact:
"{{ item.key }}": "{{ my_value }}"
with_dict: "{{ cm_config }}"
- name: Show the result after processing config map
debug:
var: "{{ item.key }}"
with_dict: "{{ cm_config }}"
gives the following result:
PLAY [localhost] ****************************************************************************************************************************************************************************************************************************
TASK [show initial state] *******************************************************************************************************************************************************************************************************************
ok: [localhost] => (item=label1) => {
"ansible_loop_var": "item",
"item": {
"key": "label1",
"value": "toto"
},
"label1": "I am set in play"
}
ok: [localhost] => (item=label2) => {
"ansible_loop_var": "item",
"item": {
"key": "label2",
"value": {
"a_value": 1,
"other_value": 2
}
},
"label2": "VARIABLE IS NOT DEFINED!"
}
ok: [localhost] => (item=label3) => {
"ansible_loop_var": "item",
"item": {
"key": "label3",
"value": {
"a_value": 3,
"other_value": 4
}
},
"label3": {
"some_value": "I'm a poor lonesome cowboy"
}
}
ok: [localhost] => (item=label4) => {
"ansible_loop_var": "item",
"item": {
"key": "label4",
"value": "tata"
},
"label4": "VARIABLE IS NOT DEFINED!"
}
TASK [Process values from config map] *******************************************************************************************************************************************************************************************************
ok: [localhost] => (item={'key': 'label1', 'value': 'toto'})
ok: [localhost] => (item={'key': 'label2', 'value': {'a_value': 1, 'other_value': 2}})
ok: [localhost] => (item={'key': 'label3', 'value': {'a_value': 3, 'other_value': 4}})
ok: [localhost] => (item={'key': 'label4', 'value': 'tata'})
TASK [Show the result after processing config map] ******************************************************************************************************************************************************************************************
ok: [localhost] => (item=label1) => {
"ansible_loop_var": "item",
"item": {
"key": "label1",
"value": "toto"
},
"label1": " toto "
}
ok: [localhost] => (item=label2) => {
"ansible_loop_var": "item",
"item": {
"key": "label2",
"value": {
"a_value": 1,
"other_value": 2
}
},
"label2": " {'a_value': 1, 'other_value': 2} "
}
ok: [localhost] => (item=label3) => {
"ansible_loop_var": "item",
"item": {
"key": "label3",
"value": {
"a_value": 3,
"other_value": 4
}
},
"label3": " {'some_value': \"I'm a poor lonesome cowboy\", 'a_value': 3, 'other_value': 4} "
}
ok: [localhost] => (item=label4) => {
"ansible_loop_var": "item",
"item": {
"key": "label4",
"value": "tata"
},
"label4": " tata "
}
PLAY RECAP **********************************************************************************************************************************************************************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Print only one item in list in ansible

I have a variable that contains a list
- debug:
var: plugin_versions
Output
ok: [localhost] => {
"plugin_versions": [
{
"name": "ace-editor",
"version": "1.1"
},
{
"name": "analysis-core",
"version": "1.95"
},
{
"name": "ant",
"version": "1.9"
}
]
Now I want to print out only the name
What Ive tried is
- debug:
var: plugin_versions.name
- debug:
var: plugin_versions[name]
But in both cases I get
TASK [plugins : debug] ***************************************************************************************************************
ok: [localhost] => {
"plugin_versions.name": "VARIABLE IS NOT DEFINED!"
}
TASK [plugins : debug] ***************************************************************************************************************
ok: [localhost] => {
"plugin_versions[name]": "VARIABLE IS NOT DEFINED!"
}
Im kinda clueless what else can I do here to print out the name only.
you could do it in a few ways. The plugin_versions is a list of dictionaries, you can print the name attribute of each dictionary using loops, here are 2 loop examples you could use:
---
- hosts: localhost
gather_facts: false
vars:
plugin_versions:
- name: ace-editor
version: '1.1'
- name: analysis-core
version: '1.95'
- name: ant
version: '1.9'
tasks:
- name: print variable - with_items
debug:
msg: "{{ item.name }}"
with_items:
- "{{ plugin_versions }}"
- name: print variable - with map filter
debug:
var: item
with_items:
- "{{ plugin_versions | map(attribute='name') | list }}"
output:
[http_offline#greenhat-29 tests]$ ansible-playbook -i hosts test.yml
PLAY [localhost] *******************************************************************************************************************************************************************************************************
TASK [print variable - with_items] *************************************************************************************************************************************************************************************
ok: [localhost] => (item={'name': 'ace-editor', 'version': '1.1'}) => {
"msg": "ace-editor"
}
ok: [localhost] => (item={'name': 'analysis-core', 'version': '1.95'}) => {
"msg": "analysis-core"
}
ok: [localhost] => (item={'name': 'ant', 'version': '1.9'}) => {
"msg": "ant"
}
TASK [print variable - with map filter] ********************************************************************************************************************************************************************************
ok: [localhost] => (item=ace-editor) => {
"item": "ace-editor"
}
ok: [localhost] => (item=analysis-core) => {
"item": "analysis-core"
}
ok: [localhost] => (item=ant) => {
"item": "ant"
}
PLAY RECAP *************************************************************************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0
[http_offline#greenhat-29 tests]$
hope it helps
Since you have a list (plugin_versions) you'll need to access each element by index:
---
- hosts: all
vars:
plugin_versions:
- { name: "ace-editor", version: "1.1"}
- { name: "analysis-core", version: "1.95"}
- { name: "ant", version: "1.9"}
tasks:
- debug: var=plugin_versions[0].name
also, if you want to loop through all the items in the list you can do something like this:
- debug: "var=item['name']"
with_items: "{{ plugin_versions }}"
You can iterate over the items of your variable with:
- debug:
var: item.name
with_items: "{{plugin_versions}}"

Ansible: approach to loop through a complex variable in order to access different layers under the same task

What would be the correct approach to loop through a complex dictionary variable in an Ansible play in order to be able to call items from different layers of the variable in the same module/task?
The variable in question looks like this (but it could be any number of layers down, so far I only have about 3 layers down):
installation:
v10:
installs: yes
das_username: dasusr
das_group: dasadm
package_name: v10.tar.gz
binaries_folder: binaries
install_path: /opt/V10
instances:
inst1:
enabled: yes
username: inst1
group: dbiadm
port: 50000
databases:
jts:
name: 'JTS'
state: present
pagesize: 32768
rtc:
name: 'CCM'
state: absent
pagesize: 16384
rqm:
name: 'QM'
state: absent
pagesize: 32768
inst2:
enabled: no
username: inst2
group: dbiadm
port: 50005
databases:
warehouse:
name: 'DW'
state: present
pagesize: 32768
v9:
installs: no
instances:
inst3:
enabled: no
databases:
rqm:
name: 'RM'
state: absent
pagesize: 32768
lqe:
name: 'LQE'
state: absent
pagesize: 16384
Tasks include creating databases using the install_path (layer 1) and databases parameters (layer 3) under the same shell module or in a template.
For example (the task should look something like this) but with the correct method of looping:
- name: Creating the databases
become_user: "{{ item.0.instances.username }}"
shell: "./db2 create database {{ item.1.name }} using pagesize {{ item.1.pagesize }}"
args:
chdir: "{{ item.0.install_path }}/bin"
with_subelements:
- "{{ installation }}"
- instances.databases.clm
Thank you!
It's not a trivial feat to do such thing in ansible, becase loops in ansible are one-dimensional. There is a hackish way though, you can use nested includes with loop control. So let's say i put all of your variables inside vars.yml and have a following files:
playbook-nested.yml
- hosts: localhost
tasks:
- name: pass version forward
include: instances.yml v="{{ version_item }}"
with_items: "{{ installation }}"
loop_control:
loop_var: version_item
instances.yml
- name: Pass instance forward
include: databases.yml i={{ instance_item }}
with_items: "{{ installation[v].instances }}"
loop_control:
loop_var: instance_item
databases.yml
- name: echo command
debug: msg="./db2 create database {{ item.name }} using pagesize {{ item.pagesize }}"
with_items: "{{ installation[v].instances[i].databases.clm }}"
when: installation[v].instances[i].databases.clm is defined
So the output of command ansible-playbook playbook-nested.yml -e #vars.yml will be:
PLAY [localhost] ***************************************************************
TASK [setup] *******************************************************************
ok: [localhost]
TASK [pass version forward] ****************************************************
included: /home/user/instances.yml for localhost
included: /home/user/instances.yml for localhost
TASK [Pass instance forward] ***************************************************
included: /home/user/databases.yml for localhost
TASK [echo command] ************************************************************
ok: [localhost] => (item={u'state': u'absent', u'name': u'RQM', u'pagesize': 32768}) => {
"item": {
"name": "RQM",
"pagesize": 32768,
"state": "absent"
},
"msg": "./db2 create database RQM using pagesize 32768"
}
ok: [localhost] => (item={u'state': u'absent', u'name': u'LQE', u'pagesize': 16384}) => {
"item": {
"name": "LQE",
"pagesize": 16384,
"state": "absent"
},
"msg": "./db2 create database LQE using pagesize 16384"
}
TASK [Pass instance forward] ***************************************************
included: /home/user/databases.yml for localhost
included: /home/user/databases.yml for localhost
TASK [echo command] ************************************************************
ok: [localhost] => (item={u'state': u'present', u'name': u'JTS', u'pagesize': 32768}) => {
"item": {
"name": "JTS",
"pagesize": 32768,
"state": "present"
},
"msg": "./db2 create database JTS using pagesize 32768"
}
ok: [localhost] => (item={u'state': u'absent', u'name': u'CCM', u'pagesize': 16384}) => {
"item": {
"name": "CCM",
"pagesize": 16384,
"state": "absent"
},
"msg": "./db2 create database CCM using pagesize 16384"
}
ok: [localhost] => (item={u'state': u'absent', u'name': u'QM', u'pagesize': 32768}) => {
"item": {
"name": "QM",
"pagesize": 32768,
"state": "absent"
},
"msg": "./db2 create database QM using pagesize 32768"
}
TASK [echo command] ************************************************************
skipping: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=8 changed=0 unreachable=0 failed=0
I'm not sure what does clm mean and do i have to loop over databases as well, but i think the concept is clear to you now.
You can also use the nested_loops on the lower level i guess.

Resources