Please help me understand nested loops

Hi,

This code works, I just don’t understand the indexing and loop behaviour. Hoping someone can help me understand the indexing, and why the variables are equal to the values.

Here the code:

engineers:
- username: user1
state: present
keys:
- ssh-rsa AAAAB3Nza....
- ssh-rsa AAAAB3Nza....
- username: user2
state: present
keys:
- ssh-rsa AAAAB3NzaC....
- username: user3
state: present
keys:
- ssh-rsa AAAAB3NzaC1....

and I have a working code snippet:

- name: Manage authorized keys
authorized_key:
user: "{{ item.0.username }}"
state: present
key: "{{ item.1 }}"
with_subelements:
- "{{ engineers }}"
- keys
loop_control:
loop_var: item

This is really not a 'nested' loop, you are using the 'subelements'
lookup to change the data provided to a single loop.

Is it loop behaviour in general that’s confusing, or the effect of with_subelements? Remember that the with_* construct is implemented as a lookup. Once you understand what the subelements lookup does, then this specific loop should make more sense.

Here’s a playbook and its output log, using a your data with some modifications to make it easier to see what’s going on. I’ve added a ‘user4’ with no ‘keys’ to show how to handle that case, too.

The first debug task loops they same way your playbook loops, using with_subelements.

The second debug task doesn’t loop, but invokes the subelements lookup directly, with parameters identical to those passed to the prior task’s with_subelements. The subelements lookup produces a list which, not surprisingly, matches what the prior task looped over.

[utoddl@tango ansible]$ **cat test-subelements2.yml** 
---
- name: Demonstrate subelements
  hosts: localhost
  gather_facts: false
  vars:
    engineers:
      - username: user1
        state: present
        keys:
          - user1keyA
          - user1KeyB
      - username: user2
        state: present
        keys:
          - user2keyA
      - username: user3
        state: present
        keys:
          - user3keyA
      - username: user4
        state: present
        note: Look! No 'keys'!
  tasks:
    - name: **Show subelements one per loop**
      ansible.builtin.debug:
        msg: "{{ item }}"
      with_subelements:
        - "{{ engineers }}"
        - keys
        - {'skip_missing': true}

    - name: **Show the same subelements all in one go**
      ansible.builtin.debug:
        msg: "{{ **lookup('subelements', engineers, 'keys', {'skip_missing': true})** }}"
[utoddl@tango ansible]$ **ansible-playbook test-subelements2.yml** 

PLAY [Demonstrate subelements] *********************************************************

TASK [**Show subelements one per loop**] ***************************************************
ok: [localhost] => (item=[{'username': 'user1', 'state': 'present'}, 'user1keyA']) => {
    "msg": [
        {
            "state": "present",
            "username": "user1"
        },
        "user1keyA"
    ]
}
ok: [localhost] => (item=[{'username': 'user1', 'state': 'present'}, 'user1KeyB']) => {
    "msg": [
        {
            "state": "present",
            "username": "user1"
        },
        "user1KeyB"
    ]
}
ok: [localhost] => (item=[{'username': 'user2', 'state': 'present'}, 'user2keyA']) => {
    "msg": [
        {
            "state": "present",
            "username": "user2"
        },
        "user2keyA"
    ]
}
ok: [localhost] => (item=[{'username': 'user3', 'state': 'present'}, 'user3keyA']) => {
    "msg": [
        {
            "state": "present",
            "username": "user3"
        },
        "user3keyA"
    ]
}

TASK [**Show the same subelements all in one go**] *****************************************
ok: [localhost] => {
    "msg": [
        [
            {
                "state": "present",
                "username": "user1"
            },
            "user1keyA"
        ],
        [
            {
                "state": "present",
                "username": "user1"
            },
            "user1KeyB"
        ],
        [
            {
                "state": "present",
                "username": "user2"
            },
            "user2keyA"
        ],
        [
            {
                "state": "present",
                "username": "user3"
            },
            "user3keyA"
        ]
    ]
}

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

Do let us know if something is still unclear.

I don’t know what with_subelements: is but here is what I know of with_nested that works for me - this is creating a Oracle databse server…with the database name…which some of these files from db_create_files_1 needs the ora_sid to replace it inside the files, that is all mapped out with the when if the inventory_host name equals the host in the var then use that ora_sid. If this helps

:

  • name: Create scripts for database configuration files on the target without ORA_SID placing in the root of DBcreate
    ansible.builtin.template:
    src: “{{ Oracle_Template_Dir }}/create/{{ item.0.file_name }}”
    dest: “{{ DB_Create_Dir }}/{{ item.0.file_name | replace(‘.j2’, ‘’) }}.{{ item.0.ext }}”
    mode: ‘a+x’
    with_nested:
  • “{{ db_create_files_1 }}” # ← for the vars with item.0.x pulling from this entry in the variable file
  • “{{ specific_hosts_conf }}” # ← for the vars with item.1.x pulling from this entry in the variable file
    when: (item.1.ora_sid|upper not in Database_Name.stdout) and (inventory_hostname == item.1.host)

Defined in the variable…

specific_hosts_conf:

db_create_files_1: #ora_stage/DBcreate/

  • {file_name: ‘profile19c_new.j2’, ext: ‘’}
  • {file_name: ‘cr_temp_profile.j2’, ext: ‘sql’}
  • {file_name: ‘cron.j2’, ext: ‘header’}
  • {file_name: ‘tnsnames.j2’, ext: ‘ora’}
  • {file_name: ‘sqlnet.ora.j2’, ext: ‘default’}
  • {file_name: ‘init.j2’, ext: ‘new’}
  • {file_name: ‘listener.j2’, ext: ‘ora’}

The way 'with_' works is more or less the same a a loop with a lookup
in it's item list

with_nested ... == loop: '{{ lookup("nested", ... )}}'

Most find the `loop` syntax a bit clearer, it is less 'magical', but
the with_ syntax is more convenient.