Looping through a product of two arrays

Viewed 42

I have this vars file in Ansible:

for_create:
  client: ["VK","SB"]
  folders: ["toula","tina"]

for_delete:
  client: ["VK","SB"]
  folders: ["invoices","scripts"]

for_rename:
  client: ["VK", "SB"]
  old_name: ["home"]
  new_name: ["town"] 

Is it possible to do something like that in YAML? The following code in Ruby

clients = ["VK", "SB"]
folders =  ["toula","tina"]

clients.each do |client|
    folders.each do |folder|
        puts "folder #{folder} for client #{client} created"
    end
end 

To sum up I want each client to create the folders ["toula", "tina"]

I have tried a lot but I can't manage to make it loop twice for each folder.

The output of the above code

folder toula for client VK created
folder tina for client VK created
folder toula for client SB created
folder tina for client SB created
1 Answers

Iterate the lists with_nested. For example,

    - debug:
        msg: "folder {{ item.1 }} for client {{ item.0 }} created"
      with_nested:
        - "{{ for_create.client }}"
        - "{{ for_create.folders }}"
      vars:
        for_create:
          client: [VK, SB]
          folders: [toula, tina]

gives (abridged)

  msg: folder toula for client VK created
  msg: folder tina for client VK created
  msg: folder toula for client SB created
  msg: folder tina for client SB created

To rename folders zip the lists

    - debug:
        msg: "folder {{ item.1 }} renamed to {{ item.2 }} for client {{ item.0 }}"
      with_nested:
        - "{{ for_rename.client }}"
        - "{{ for_rename.old_name|zip(for_rename.new_name) }}"
      vars:
        for_rename:
          client: [VK, SB]
          old_name: [home]
          new_name: [town]

gives (abridged)

  msg: folder home renamed to town for client VK
  msg: folder home renamed to town for client SB

Q: "Is it possible to check if the folder exists inside the loop?"

A: Yes. It is. Use the parameters creates or removes. For example, given the tree

shell> tree /tmp/home/
/tmp/home/
├── SB
│   └── home
└── VK
    └── home

The playbook below

shell: cat pb.yml
- hosts: localhost
  tasks:
    - command:
        cmd: "mv {{ main_path }}/{{ item.1 }} {{ main_path }}/{{ item.2 }}" 
        removes: "{{ main_path }}/{{ item.1 }}"
      with_nested:
        - "{{ for_rename.client }}"
        - "{{ for_rename.old_name|zip(for_rename.new_name) }}"
      vars:
        main_path: "/tmp/home/{{ item.0 }}"
        for_rename:
          client: [VK, SB]
          old_name: [home]
          new_name: [town]

moves the files only if exist

shell> ansible-playbook pb.yml

PLAY [localhost] *****************************************************************************

TASK [command] *******************************************************************************
changed: [localhost] => (item=['VK', 'home', 'town'])
changed: [localhost] => (item=['SB', 'home', 'town'])

PLAY RECAP ***********************************************************************************
localhost: ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
shell> tree /tmp/home/
/tmp/home/
├── SB
│   └── town
└── VK
    └── town

2 directories, 2 files

The playbook is idempotent. The commands will not execute if the files are missing

shell> ansible-playbook pb.yml

PLAY [localhost] *****************************************************************************

TASK [command] *******************************************************************************
ok: [localhost] => (item=['VK', 'home', 'town'])
ok: [localhost] => (item=['SB', 'home', 'town'])

PLAY RECAP ***********************************************************************************
localhost: ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

You can use the module command the same way also to create or remove files. The next option is using the module file. For example, the playbook below

shell> cat pb.yml
- hosts: localhost
  tasks:
    - file:
        state: touch
        path: "{{ main_path }}/{{ item.1 }}"
      with_nested:
        - "{{ for_create.client }}"
        - "{{ for_create.folders }}"
      vars:
        main_path: "/tmp/home/{{ item.0 }}"
        for_create:
          client: [VK, SB]
          folders: [toula, tina]

creates the files

shell> ansible-playbook pb.yml

PLAY [localhost] *****************************************************************************

TASK [file] **********************************************************************************
changed: [localhost] => (item=['VK', 'toula'])
changed: [localhost] => (item=['VK', 'tina'])
changed: [localhost] => (item=['SB', 'toula'])
changed: [localhost] => (item=['SB', 'tina'])

PLAY RECAP ***********************************************************************************
localhost: ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
shell> tree /tmp/home/
/tmp/home/
├── SB
│   ├── tina
│   ├── toula
│   └── town
└── VK
    ├── tina
    ├── toula
    └── town

This task is not idempotent because the files will receive updated file access and modification times (similar to the way touch works from the command line). Preserve access_time and modification_time to make the task idempotent

    - file:
        state: touch
        path: "{{ main_path }}/{{ item.1 }}"
        access_time: preserve
        modification_time: preserve
      ...
Related