Search code examples
loopsansiblenested-loops

Ansible iterate over a nested list with default values


The goal is to iterate over a list which contains another list. If the nested list is empty I want to use the values of the parent instead. Sadly I can't find a possible solution.

List to iterate:

  users:
    - name: Mario
      username: admin_mario
      state: present
      role: admin
      ssh: []
    - name: luigi
      username: just_luigi
      state: present
      role: qa
      ssh:
          - username: qa_luigi
          role: qa
          state: present
          - username: admin_luigi
          role: admin
          state: present

So I would like to iterate over this list if ssh is empty use username, state and role as defaults.

- name: test
  include_tasks: "mytasks.yml"
  loop: "{{ users|subelements(ssh) }}"
  loop_control:
    loop_var: user
  when: users is defined
- name: test
  include_tasks: "mytasks.yml"
  loop: "{{ users|default({'ssh': [{'username': user.username}]})|subelements('ssh') }}"
  loop_control:
    loop_var: user

Sadly I couldn't figure out a solution with the Dokumentation e.g. with_items, loop control, subelements nothing seems to work as expected.


Solution

  • Create defaults

      defaults: "{{ users|json_query('[].{username: username,
                                          state: state,
                                          role: role}') }}"
    

    gives

      defaults:
      - role: admin
        state: present
        username: admin_mario
      - role: qa
        state: present
        username: just_luigi
    

    Create list of ssh

      ssh_str: |
        {% for i,j in users|zip(defaults) %}
        {% if i.ssh|length == 0 %}
        - ssh: [{{ j }}]
        {% else %}
        - {}
        {% endif %}
        {% endfor %}
      ssh: "{{ ssh_str|from_yaml }}"
    

    Optionally,
      ssh_str: |
        {% for i,j in users|zip(defaults) %}
        - {{ i.ssh|ternary({}, {'ssh': [j]}) }}
        {% endfor %}
    

    gives

      ssh:
      - ssh:
        - role: admin
          state: present
          username: admin_mario
      - {}
    

    Combine the items of the lists

        - debug:
            msg: "{{ item.0.name }} {{ item.1.username }} {{ item.1.role }} {{ item.1.state }}"
          with_subelements:
            - "{{ users|zip(ssh)|map('combine') }}"
            - ssh
    

    gives (abridged)

      msg: Mario admin_mario admin present
      msg: luigi qa_luigi qa present
      msg: luigi admin_luigi admin present
    

    Example of a complete playbook for testing

    - hosts: localhost
    
      vars:
    
        users:
          - name: Mario
            username: admin_mario
            state: present
            role: admin
            ssh: []
          - name: luigi
            username: just_luigi
            state: present
            role: qa
            ssh:
              - username: qa_luigi
                role: qa
                state: present
              - username: admin_luigi
                role: admin
                state: present
    
        defaults: "{{ users|json_query('[].{username: username,
                                            state: state,
                                            role: role}') }}"
        ssh_str: |
          {% for i,j in users|zip(defaults) %}
          {% if i.ssh|length == 0 %}
          - ssh: [{{ j }}]
          {% else %}
          - {}
          {% endif %}
          {% endfor %}
        ssh: "{{ ssh_str|from_yaml }}"
    
      tasks:
    
        - debug:
            var: defaults
        - debug:
            var: ssh
    
        - debug:
            msg: "{{ item.0.name }} {{ item.1.username }} {{ item.1.role }} {{ item.1.state }}"
          with_subelements:
            - "{{ users|zip(ssh)|map('combine') }}"
            - ssh