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.