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

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

Related

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

How to loop over a nested dictionary in 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'}"

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: Sequence range using loop variable

I need to create a certain number of systemd unit files based on name and included variable with number of files to create, like:
app-name#1.service
app-name#2.service
app-name#3.service
script-name#1.service
script-name#2.service
script-name#3.service
script-name#4.service
It works with a nested loop using range function, but I not understand how to use loop variable (item.1.workers) into range() parameters.
- hosts: localhost
vars:
apps:
- name: app-name
workers: 3
- name: script-name
workers: 5
connection: local
tasks:
- name: test
debug:
msg: "{{ item.1.name }}#{{ item.0 }}.service"
loop: "{{ range(1, 3) | product(apps) | list }}"
Let's create the lists of workers in the first task and loop with subelements in the second task
- set_fact:
apps1: "{{ apps1|default([]) +
[{'name': item.name,
'workers': range(1, item.workers + 1)|list}] }}"
loop: "{{ apps }}"
- debug:
msg: "{{ item.0.name }}#{{ item.1 }}.service"
loop: "{{ apps1|subelements('workers') }}"
gives
msg: app-name#1.service
msg: app-name#2.service
msg: app-name#3.service
msg: script-name#1.service
msg: script-name#2.service
msg: script-name#3.service
msg: script-name#4.service
msg: script-name#5.service

Adding field to dict items

Consider the following play. What I am trying to do is add a field, tmp_path which is basically the key and revision appended together to each element in the scripts dict.
---
- hosts: localhost
connection: local
gather_facts: no
vars:
scripts:
a.pl:
revision: 123
b.pl:
revision: 456
tasks:
- with_dict: "{{ scripts }}"
debug:
msg: "{{ item.key }}_{{ item.value.revision }}"
# - with_items: "{{ scripts }}"
# set_fact: {{item.value.tmp_path}}="{{item.key}}_{{item.value.revision}}"
# - with_items: "{{ scripts }}"
# debug:
# msg: "{{ item.value.tmp_path }}"
...
Obviously the commented code doesn't work, any idea how I can get this working? Is it possible to alter the scripts dict directly, or should I somehow be creating a new dict to reference instead?
By the way welcome to correct the terminology for what I am trying to do.
OK, I think I got a solution (below), at least to let me move forwards with this. Disadvantages are it has removed the structure of my dict and also seems a bit redundant having to redefine all the fields and use a new variable, If anyone can provide a better solution I will accept that instead.
---
- hosts: localhost
connection: local
gather_facts: no
vars:
scripts:
a.pl:
revision: 123
b.pl:
revision: 456
tasks:
- with_dict: "{{ scripts }}"
debug:
msg: "{{ item.key }}_{{ item.value.revision }}"
- with_dict: "{{ scripts }}"
set_fact:
new_scripts: "{{ (new_scripts | default([])) + [ {'name': item.key, 'revision': item.value.revision, 'tmp_path': item.key ~ '_' ~ item.value.revision}] }}"
# - debug:
# var: x
# - with_dict: "{{ scripts }}"
- with_items: "{{ new_scripts }}"
debug:
msg: "{{ item.tmp_path }}"
...
BTW credit to the following question which pointed me in the right direction:
Using Ansible set_fact to create a dictionary from register results

Resources