Reputation: 23
Can someone help me with nested loop construction?
I am creating simple backup role, which will prepare client servers for backup and it will prepare backup server also. Backups will be downloaded from clients by backup server at defined time. I want to define different time and different folders for each client (by host_vars).
My workflow:
backup server
client
backup server
I have first version of this role, which was applied to client group of servers. Backup server was configured with help of delegate_to:
statement. But there was problem with creating cron file on backup server, because there was something like 'race condition'. When playbook finishes, there was only one random entry in the backup's server cron file. But I expected that there should be entries for all client servers. I tried to open issue https://github.com/ansible/ansible/issues/74189 - I got answer, that I should change my access to this problem.
My second attempt was that I rewrite ansible role. Then I can apply it on backup server instead of client group. Now I am using delegate_to:
on the group of client servers.
Example of (simplified) expected cron job on backup server:
0 0 * * * backup -include /home --include /var/www --include /srv --exclude '**' backup.lab.local::/ /home/backup/srv1.lab.local
# -> similar entry for srv2
# -> similar entry for srv3
My scenario:
# hosts file
[backup_servers]
backup.lab.local
[testing_servers]
srv1.lab.local
srv2.lab.local
srv3.lab.local
Example of host_vars file:
# host_vars/srv1.lab.local
backup_folders:
- /home
- /var/www
- /srv
I am stuck on task which creates cron entries on backup server. I need to loop groups['testing_servers']
and inside this loop I need to create another loop of hostvars[<each_host_from_group>]['backup_folder']
.
How to do it, please?
Upvotes: 2
Views: 705
Reputation: 387
Regarding the mentioned GitHub issue, you can probably go around the issue (which is parallel write to crontab file on one host) by using throttle: 1
(documentation)
i.e. your cron playbook should contain:
tasks:
- name: "Set backup cron job"
ansible.builtin.cron:
name: "Backup cron job for {{ inventory_hostname }}"
user: root
minute: "{{ backup_minute }}"
hour: "{{ backup_hour }}"
day: "{{ backup_day }}"
month: "{{ backup_month }}"
weekday: "{{ backup_weekday }}"
job: "rdiff-backup --create-full-path --remote-schema \"ssh -C -i /home/backup/.ssh/id_rsa -p {{ ansible_port | default(22) }} -o 'StrictHostKeyChecking no' \\%s rdiff-backup --server\" --include /path/to/dir --exclude '**' {{ ansible_host }}::/ /home/backup/{{ inventory_hostname }}"
delegate_to: deb10.lab.local
throttle: 1 # <--------- THIS IS THE IMPORTANT LINE
The other way around you are mentioning using nested loop can be done either by using nested loop in Ansible, or by prebuilding nested structure, and then iterate over it:
You can build loop in loop as the documentation says, by using one task as outer loop with include_tasks
module, and one loop with renamed loop control variable (loop_var
).
As the example in the documentation shows, you need to separate it into two files:
# main.yml tasks file
- include_tasks: inner.yml
loop:
- 1
- 2
- 3
loop_control:
loop_var: outer_item
and
# inner.yml tasks file
- name: Print outer and inner items
ansible.builtin.debug:
msg: "outer item={{ outer_item }} inner item={{ item }}"
loop:
- a
- b
- c
Use product filter to create list of lists:
['alice', 'bob'] |product(['clientdb', 'employeedb', 'providerdb'])|list
creates
[('alice', 'clientdb'), ('alice', 'employeedb'), ('alice', 'providerdb'), ('bob', 'clientdb'), ('bob', 'employeedb'), ('bob', 'providerdb')]
so as example in documentation states, you can use code similar to following:
- name: Give users access to multiple databases
community.mysql.mysql_user:
name: "{{ item[0] }}"
priv: "{{ item[1] }}.*:ALL"
append_privs: yes
password: "foo"
loop: "{{ ['alice', 'bob'] |product(['clientdb', 'employeedb', 'providerdb'])|list }}"
Upvotes: 0
Reputation: 39159
I wouldn't loop over backup_folder
, I would join
those values.
Given that you properly loop over groups['testing_servers']
hosts, e.g.
0 0 * * * backup {{ ([''] + hostvars[item]['backup_folder']) | join(' --include ') }} --exclude '**' backup.lab.local::/ /home/backup/{{ item }}
Would give:
0 0 * * * backup --include /home --include /var/www --include /srv --exclude '**' backup.lab.local::/ /home/backup/srv1.lab.local
Note that: this odd construct: ([''] + hostvars[item]['backup_folder'])
is there to create an empty element at the begining of your list, to make it start with --include
, otherwise you will have
... backup /home --include /var/www --include /srv --exclude '**' ...
## ^--- missing "--include " here
Upvotes: 1