How to loop over a nested dictionary in ansible? - ansible

How can I loop over this dictionary?
# nested dictionary to loop over:
vars:
commands:
group1:
cmd1:
run: foo
cmd2:
run: bar
group2:
cmd3:
run: zoo
# expected loop:
- group1, cmd1={...}
- group1, cmd2={...}
- group2, cmd4={...}
I tried using various combinations of dict2items and with_nested but so far I was not able to achieve that goal, and I would really want to avoid using loops with include tasks or writing a python module that does the flattening.
I still hope there is a pure-ansible way of doing it.

It's possible to loop tasks included by included_tasks. For example the file
shell> cat inner-loop.yml
- debug:
msg: "{{ outer_item.key }}, {{ item.key }}={{ item.value }}"
loop: "{{ outer_item.value|dict2items }}"
included from the task
- include_tasks: inner-loop.yml
loop: "{{ commands|dict2items }}"
loop_control:
loop_var: outer_item
gives
"msg": "group1, cmd1={u'run': u'foo'}"
"msg": "group1, cmd2={u'run': u'bar'}"
"msg": "group2, cmd3={u'run': u'zoo'}"

Related

Ansible loop not execute if same value

can i know in an Ansible loop is there a way not to execute repeated statement . Below is my code
vars.yml
type_of_fruit:
- "lemon"
- "orange"
- "orange"
- "pineapple"
main.yml
- name: Type of frutis
debug:
msg: "{{ item }}"
loop: "{{ type_of_fruit }}"
Expected
"lemon"
"orange"
"pineapple"
Filter the items, e.q.
loop: "{{ type_of_fruit|unique }}"

Ansible how to get equivalent of nested for-loop in playbook

I am writing an ansible-playbook and am trying to accomplish the following:
there are the existing directories /home/user1 , /home/user2, ... , /home/user20
in each of these directories I want to create subdirectories /foo1 , ... , /foo5
Now I COULD just make use of a with_nested loop, where I provide two lists with all the indices that I need, but that is just silly.
Instead, I would like to define two number-ranges or -sequences and the relevant task is then looped over using the value-pairs from their cartesian product.
Is that reasonably possible and if so how do I go about it?
See ansible-doc -t lookup sequence and Forcing lookups to return lists e.g.
- debug:
msg: "Create /home/user{{ item.0 }}/foo{{ item.1 }}"
with_nested:
- "{{ query('sequence', 'start=1 end=3') }}"
- "{{ query('sequence', 'start=1 end=2') }}"
gives
msg: Create /home/user1/foo1
msg: Create /home/user1/foo2
msg: Create /home/user2/foo1
msg: Create /home/user2/foo2
msg: Create /home/user3/foo1
msg: Create /home/user3/foo2
The parametrized task below gives the same result
- debug:
msg: "Create /home/user{{ item.0 }}/foo{{ item.1 }}"
with_nested:
- "{{ query('sequence', user_range) }}"
- "{{ query('sequence', dir_range) }}"
vars:
user_start: 1
user_end: 3
user_range: "start={{ user_start }} end={{ user_end }}"
dir_start: 1
dir_end: 2
dir_range: "start={{ dir_start }} end={{ dir_end }}"

jinja2 turning lists into strings even when from_json is being used

I am trying to run a nested for loop in order to retrieve a nested value.
I would like to retrieve some_value_4 when some_value_3 matches a criteria that's predefined.
{
"some_dict": {
"some_key_0": "value_0",
"some_key_1": "value_1"
},
"testval_dict": {
"test_key_0": "some_value_0",
"test_key_1": "some_value_1",
"test_key_2": "some_value_2",
"test_key_3": "some_value_3",
"test_key_4": "some_value_4"
}
}
The playbook:
- hosts: localhost
tasks:
- set_fact:
mydict: "{{ lookup('file', '/tmp/file.json' ) | from_json }}"
- debug:
msg: |
"{% for item in mydict %}
{{ item }}
{% endfor %}"
when run, it alreay looks like dict names are treated as string and nothing more:
ansible-playbook /tmp/test_playbook.yml -c local -i ', localhost'
TASK [debug] ******************************************************************
ok: [localhost] => {}
MSG:
" somee_dict
testval_dict
"
Then when I add an itme.key to the debug task, the playbook fails:
MSG:
The task includes an option with an undefined variable. The error was: 'str object' has no attribute 'value'
Thank you.
edit for clarification
In the real example, I will not know the names of the dicts, so I cannot use some_dict or testval_dict, that is why I'm trying to go over this data source in an item.key or item.value form.
Q: "{% for item in mydict %} ... dict names are treated as string and nothing more."
A: This is correct. A dictionary, when evaluated as a list, returns the list of its keys. See some examples below
- debug:
msg: "{{ mydict.keys()|list }}"
- debug:
msg: "{{ mydict[item] }}"
loop: "{{ mydict.keys()|list }}"
- debug:
msg: "{{ mydict|difference(['testval_dict']) }}"
give
msg:
- some_dict
- testval_dict
msg:
some_key_0: value_0
some_key_1: value_1
msg:
test_key_0: some_value_0
test_key_1: some_value_1
test_key_2: some_value_2
test_key_3: some_value_3
test_key_4: some_value_4
msg:
- some_dict
See How to iterate through a list of dictionaries in Jinja template?
If you need to loop over the dictionary, you can use with_dict loop functionality. This way, if you loop over mydict and get item.key you will get somee_dict and testval_dict.
tasks:
- set_fact:
mydict: "{{ lookup('file', '/tmp/file.json')|from_json }}"
# This will get the top level dictionaries somee_dict and testval_dict
- debug:
var: item.key
with_dict: "{{ mydict }}"
And if you get item.value of mydict you will get the sub-dictionary:
- debug:
var: item.value
with_dict: "{{ mydict }}"
Will produce (showing output only for testval_dict):
"item.value": {
"test_key_0": "some_value_0",
"test_key_1": "some_value_1",
"test_key_2": "some_value_2",
"test_key_3": "some_value_3",
"test_key_4": "some_value_4"
}

Ansible: exclude item(s) from lookup result

The following Ansible play:
- name: Fetch domains
vars:
domain_lines: "{{ lookup('file', domain_file).splitlines() }}"
debug:
msg: "Domains: {{ domain_lines }}"
reads and splits the lines from the following "domain_file":
test1.domain.tld
# test2.domain.tld
test3.domain.tld
test4.domain.tld
This works nicely. But now I'd like to skip/remove the lines starting with a hashtag. What's the best way to achieve this?
Use reject. The play below
- set_fact:
domain_selected: "{{ domain_lines|reject('regex', '^#(.*)$')|list }}"
- debug:
var: domain_selected
gives
"domain_selected": [
"test1.domain.tld",
"test3.domain.tld",
"test4.domain.tld"
]

Ansible with_items if item is defined

Ansible 1.9.4.
The script should execute some task only on hosts where some variable is defined. It works fine normally, but it doesn't work with the with_items statement.
- debug: var=symlinks
when: symlinks is defined
- name: Create other symlinks
file: src={{ item.src }} dest={{ item.dest }} state=link
with_items: "{{ symlinks }}"
when: symlinks is defined
But I get:
TASK: [app/symlinks | debug var=symlinks] *********************
skipping: [another-host-yet]
TASK: [app/symlinks | Create other symlinks] ******************
fatal: [another-host-yet] => with_items expects a list or a set
Maybe I am doing something wrong?
with_items: "{{ symlinks | default([]) }}"
The reason for this behavior is conditions work differently inside loops. If a loop was defined the condition is evaluated for every item while iterating over the items. But the loop itself requires a valid list.
This is also mentioned in the docs:
Note that when combining when with with_items (see Loops), be aware that the when statement is processed separately for each item. This is by design:
tasks:
- command: echo {{ item }}
with_items: [ 0, 2, 4, 6, 8, 10 ]
when: item > 5
I think this is a bad design choice and for this functionality they better should have introduced something like with_when.
As you have already figured out yourself, you can default to an empty list.
with_items: "{{ symlinks | default([]) }}"
Finally if the list is dynamically loaded from a var, say x, use:
with_items: "{{ symlinks[x|default('')] | default([])}}"
This will default to an empty list when 'x' is undefined
Accordingly, fall back to an empty dict with default({}):
# service_facts skips, then dict2items fails?
with_dict: "{{ ansible_facts.services|default({})|dict2items|selectattr('key', 'match', '[^#]+#.+\\.service')|list|items2dict }}"

Resources