Below Ansible playbook, named ansible-variable-list.yml, performs a check on the target servers to determine if a specific list of operating system users exists.
[oracle@oel01db ansible-project]$ cat ./playbooks/ansible-variable-list.yml
---
- name: Check for existence of specified users
hosts: db_servers
vars:
users: ["tom", "mysql", "oracle", "postgres"]
tasks:
- name: Check if user exists using the 'id' command
# The loop iterates over the 'users' list
ansible.builtin.command: "id -u {{ item }}"
# Register the output of the command (success or failure)
register: user_check
# Ensure the task doesn't fail if the user is not found (command returns non-zero)
ignore_errors: true
loop: "{{ users }}"
- name: Report on user existence
ansible.builtin.debug:
msg: "User '{{ item.item }}' {{ 'EXISTS' if item.rc == 0 else 'DOES NOT EXIST' }} on {{ inventory_hostname }}"
# Loop over the results registered from the previous task
loop: "{{ user_check.results }}"
# Only show results for the users we checked
loop_control:
label: "{{ item.item }}"
[oracle@oel01db ansible-project]$ ansible-playbook -i ./inventory/hosts ./playbooks/ansible-variable-list.yml
PLAY [Check for existence of specified users] ***********************************************************************************************************************************************************************************************
TASK [Gathering Facts] **********************************************************************************************************************************************************************************************************************
ok: [192.168.0.156]
TASK [Check if user exists using the 'id' command] ******************************************************************************************************************************************************************************************
failed: [192.168.0.156] (item=tom) => {"ansible_loop_var": "item", "changed": true, "cmd": ["id", "-u", "tom"], "delta": "0:00:00.009775", "end": "2025-12-05 20:44:23.561919", "item": "tom", "msg": "non-zero return code", "rc": 1, "start": "2025-12-05 20:44:23.552144", "stderr": "id: tom: no such user", "stderr_lines": ["id: tom: no such user"], "stdout": "", "stdout_lines": []}
failed: [192.168.0.156] (item=mysql) => {"ansible_loop_var": "item", "changed": true, "cmd": ["id", "-u", "mysql"], "delta": "0:00:00.009717", "end": "2025-12-05 20:44:24.328979", "item": "mysql", "msg": "non-zero return code", "rc": 1, "start": "2025-12-05 20:44:24.319262", "stderr": "id: mysql: no such user", "stderr_lines": ["id: mysql: no such user"], "stdout": "", "stdout_lines": []}
changed: [192.168.0.156] => (item=oracle)
changed: [192.168.0.156] => (item=postgres)
...ignoring
TASK [Report on user existence] *************************************************************************************************************************************************************************************************************
ok: [192.168.0.156] => (item=tom) => {
"msg": "User 'tom' DOES NOT EXIST on 192.168.0.156"
}
ok: [192.168.0.156] => (item=mysql) => {
"msg": "User 'mysql' DOES NOT EXIST on 192.168.0.156"
}
ok: [192.168.0.156] => (item=oracle) => {
"msg": "User 'oracle' EXISTS on 192.168.0.156"
}
ok: [192.168.0.156] => (item=postgres) => {
"msg": "User 'postgres' EXISTS on 192.168.0.156"
}
PLAY RECAP **********************************************************************************************************************************************************************************************************************************
192.168.0.156 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1
[oracle@oel01db ansible-project]$
✅ 1. You define a list variable
This creates a list:
| Index | Value |
|---|---|
| 0 | tom |
| 1 | mysql |
| 2 | oracle |
| 3 | postgres |
✅ 2. First task loops over the users list
Meaning:
Ansible runs the command once for each user. So internally, it does:
Each iteration sets item to the current user.
✅ 3. Register collects output from ALL loop items
This is very important.
After the task finishes, this variable contains a structure like:
So register produces a list of results, one for each iteration.
When a task with a loop is registered, the variable (user_check) isn't a simple list; it's a dictionary containing a key called results. The value of user_check.results is a list of dictionaries, one for each iteration of the loop.
⚠️ 4. ignore_errors: true
When a user does NOT exist, the command fails (rc=1), but because you set this, Ansible continues running the playbook.
✅ 5. Second task loops over the results
Now, instead of looping over the users list, it loops over the results from the previous task.
Each item looks like this inside the loop:
In the second loop, the default item variable doesn't hold a single string; it holds a dictionary that contains the full details of a single execution of the previous task.
So this line evaluates:
Meaning:
-
rc=0 → user exists
-
rc!=0 → user does not exist
🎯 6. Final output message for each user
Example:
-
For tom → rc=1 → "DOES NOT EXIST"
-
For mysql → rc=1 → "DOES NOT EXIST"
-
For oracle → rc=0 → "EXISTS"
-
For postgres → rc=0 → "EXISTS"
🎛️ 7. loop_control just makes output pretty
loop_control defines how to display or manage information during the loop.
This controls what appears in the output logs.
Without it, Ansible prints very long JSON lines.
With it, you see clean output:
✅ Final Summary – How the loop concept works
First loop
➡️ Loops over usernames
➡️ Runs id
➡️ Stores all outputs in user_check.results
Second loop
➡️ Loops over the results of first loop
➡️ Prints EXISTS / DOES NOT EXIST
Logically the loop command belongs right after the module call, but in YAML the order does NOT matter as long as indentation is correct.
Why item.item?
Remember:
-
First
item= loop variable in second task -
Second
item= username from first task
Because each element in user_check.results looks like:
So inside second task:
-
item→ whole dictionary -
item.item→ the original username
item is the default variable name that Ansible uses inside any loop construct.
When you use the loop: keyword in an Ansible task, the current item being processed in that loop is automatically assigned to the variable item.
🔁 Key Points about item
Default Name: If you don't specify a custom variable name using
loop_control: loop_var:, Ansible always usesitem.Loop Data: It holds the actual data value from the list you are iterating over.
Example: If you loop over
[a, b, c],itemwill bea, thenb, thenc.
Result Data (Complex Loops): When you loop over the results of a previously registered task (like
user_check.results),itemholds the entire dictionary structure for that iteration, which is why you access nested data using dots, such asitem.rcoritem.item.
In your original playbook:
In the first task (
ansible.builtin.command: "id -u {{ item }}"),itemheld the usernames (tom,mysql, etc.).In the below task, when you switched the name to
user_resultusingloop_control: loop_var: user_result, you were essentially telling Ansible: "Don't use the defaultitem; useuser_resultfor the current loop data instead."
No comments:
Post a Comment