Artem Yartsev
Artem Yartsev

Reputation: 33

How to iterate over multiple lists of a dicts in Ansible?

I need to create directories for repositories with Ansible. Paths should be like:

./centos/6/repo1/x86_64/
./centos/7/repo1/x86_64/
./rhel/7/repo2/noarch/

So combinations of distribution name, major release version, repo name and base arch could be arbitrary, and no extra directories would be created. It looks like Ansible file module is suitable for this job, so I create a list of variables with this kind of structure:

repos:
    - name: repo1
        os_list:
          - centos
          - rhel
        major_distribution_list:
          - 6
          - 7
          - 8    
        archs:
          - noarch
          - x86_64

Now I'm stuck trying to find a right loop control.
with_items only allows me to iterate through key-value pairs, not list elements.
with_subelements is a bit more handy, but it only lets me use one of the lists/subelements, while I need two and more:

    - name: Create dirs
        file:
          dest: './centos/7/{{ item[0].name }}/{{ item[1] | default("noarch") }}'
          state: directory
        loop:  '{{ repos | subelements("archs") }}'

This is the best I can get of with_subelements.
with_nested does combine as many elements as I like, but I can't find a way to feed it with the lists from the variable. The best I can do with it is to create the whole bunch of possible directories regardless of which are actually needed:

   - name: Create dirs
       file:
         dest: '/centos/{{ item[2] }}/{{ item[0].name }}/{{ item[1] | default("noarch") }}'
         state: directory
     with_nested:
       -  '{{ repos }}'
       -  [x86_64, noarch]
       -  [6, 7]

with_cartesian seems to be pretty much the same.

So the question is:

is there a way to use a complex variable with multiple lists and combine them all in one task?

Upvotes: 1

Views: 5610

Answers (2)

Vladimir Botka
Vladimir Botka

Reputation: 68034

An option would be to loop include_tasks. The play below

vars:
  repos:
    - name: repo1
      os_list:
        - centos
        - rhel
      major_distribution_list:
        - 6
        - 7
        - 8    
      archs:
        - noarch
        - x86_64
tasks:
  - include_tasks: repo-paths.yml
    loop: "{{ repos }}"
    loop_control:
      loop_var: repo
...

# cat repo-paths.yml
- debug: msg="./{{ item.0 }}/{{ item.1 }}/{{ item.2 }}/{{ item.3 }}"
  with_nested:
    - "{{ repo.os_list }}"
    - "{{ repo.major_distribution_list }}"
    - "{{ repo.name }}"
    - "{{ repo.archs }}"

gives:

"msg": "./centos/6/repo1/noarch"
"msg": "./centos/6/repo1/x86_64"
"msg": "./centos/7/repo1/noarch"
"msg": "./centos/7/repo1/x86_64"
"msg": "./centos/8/repo1/noarch"
"msg": "./centos/8/repo1/x86_64"
"msg": "./rhel/6/repo1/noarch"
"msg": "./rhel/6/repo1/x86_64"
"msg": "./rhel/7/repo1/noarch"
"msg": "./rhel/7/repo1/x86_64"
"msg": "./rhel/8/repo1/noarch"
"msg": "./rhel/8/repo1/x86_64"

Upvotes: 1

error404
error404

Reputation: 2823

Instead of mapping you should create nested dict files.

For eg.

os: centos: - rhel: -

This way you can control the loop per os and per distribution.

Also my suggestion would be use some values from ansible facts

Variables like os name, os major distribution, architecture can be easily taken from ansible facts which will reduce the dict creation and implementing the ansible module would be easy

Upvotes: 0

Related Questions