Please help me understand nested loops

26 views
Skip to first unread message

Werner van der Merwe

unread,
Jul 17, 2023, 9:41:03 AM7/17/23
to Ansible Project
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
```


Brian Coca

unread,
Jul 17, 2023, 9:52:05 AM7/17/23
to ansible...@googlegroups.com
This is really not a 'nested' loop, you are using the 'subelements'
lookup to change the data provided to a single loop.


--
----------
Brian Coca

Todd Lewis

unread,
Jul 17, 2023, 9:58:53 PM7/17/23
to ansible...@googlegroups.com, uto...@gmail.com
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.
--
Todd

Rick Fee

unread,
Jul 17, 2023, 10:25:47 PM7/17/23
to ansible...@googlegroups.com
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:
- {host: 'servername.domain.com', specific_ip: '192.168.1.46', specific_host: 'servername.domain.com', ora_sid: 'DogsDB', RCATHOST_IP: '192.168.1.49', db_domain: 'domain.com'}
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'}

--
You received this message because you are subscribed to the Google Groups "Ansible Project" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ansible-proje...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ansible-project/7f4ea37c-ce57-220a-d42d-2f6bd52173d0%40gmail.com.

Brian Coca

unread,
Jul 18, 2023, 10:06:13 AM7/18/23
to ansible...@googlegroups.com
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.

--
----------
Brian Coca

Reply all
Reply to author
Forward
0 new messages