All about Ansible loops with examples

Introduction

Ansible loop is used to simplify repetitive tasks in a playbook. Loops can be used for tasks like installing multiple packages, creating multiple users, copying multiple files, etc.

Accessing a register variable in a loop from the previous task

Ansible task outputs are stored in a register variable. If there is a loop in the task and the output is stored in a register variable, you need to use the variable_name.results in loop to access this variable. Each item of the loop variable is called item. Print the {{item}} using debug and verify the structure of the output so that you can use it accordingly.

Here is an example.

Suppose there are three files with contents as below. You need to read and write all contents to another file.

# cat /home/new1.txt
100
# cat /home/new2.txt
200
# cat /home/new3.txt
300
---
- hosts: localhost
  vars:
   paths:
    - /home/new1.txt
    - /home/new2.txt
    - /home/new3.txt
  tasks:
  - name: List the files and directories
    shell: cat "{{item}}"
    loop: "{{paths}}"
    register: op
  - name: print
    debug:
     msg: "{{item}}"
    loop: "{{op.results}}"
  
main.yml

The print statements prints output for each of the item. Below is the sample output for item number 3.

ok: [localhost] => (item={'cmd': 'cat "/home/new3.txt"', 'stdout': '300', 'stderr': '', 'rc': 0, ' ... 'item': '/home/new3.txt', 'ansible_loop_var': 'item'}) => {
    "msg": {
        "ansible_loop_var": "item",
        "changed": true,
        "cmd": "cat \"/home/new3.txt\"",
        "delta": "0:00:00.004129",
        "end": "2021-08-29 11:28:33.578183",
        "failed": false,
        "invocation": {
            "module_args": {
                "_raw_params": "cat \"/home/new3.txt\"",
                "_uses_shell": true,
                "...
            }
        },
        "item": "/home/new3.txt",
        "rc": 0,
        "start": "2021-08-29 11:28:33.574054",
        "stderr": "",
        "stderr_lines": [],
        "stdout": "300",
        "stdout_lines": [
            "300"
        ]
    }
}

You might notice that there is a lot of information in the output. What we require is the value 300. This value is assigned to the variable stdout and stdout_lines. This can be accessed using item.stdout or item.stdout_lines . Let's see how to write all these contents to a new file in the below task.

  - name: Add lines to a file
    lineinfile:
     path: /home/new_all.txt
     line: "{{item.stdout}}"
     create: yes
    loop: "{{op.results}}"

Execute the below command to run the playbook:

ansible-playbook main.yml

Verify the contents of the new file.

# cat /home/new_all.txt
100
200
300

How to use with_subelements or subelements in Ansible to iterate over loops.

In some scenarios, we will not be able to access the variable in a nested array using a single loop. In such cases, subelements can be useful.

Here is an example.

---
- hosts: localhost
  vars:
    accounts:
      - name: Ved
        account:
          - name: AD1
            days: 7
          - name: AD2
            days: 14
      - name: Ted
        account:
          - name: AD3
            days: 10
  tasks:
    - name: List accounts
      debug:
        msg: "Accounts={{ item.0.name }} Name={{ item.1.name }} Days={{ item.1.days }}"
      loop: "{{ accounts|subelements('account') }}"
  
main.yml

Older Ansible versions use the below syntax.

 - name: List accounts
   debug:
      msg: "Accounts={{ item.0.name }} Name={{ item.1.name }} Days={{ item.1.days }}"
   with_subelements:
    - "{{accounts}}"
    - account

The output will be similar to the below.

ok: [localhost] => (item=[{'name': 'Ved', ..... 'days': 7}]) => {
    "msg": "Accounts=Ved Name=AD1 Days=7"
}
ok: [localhost] => (item=[{'name': 'Ved', ..... 'days': 14}]) => {
    "msg": "Accounts=Ved Name=AD2 Days=14"
}
ok: [localhost] => (item=[{'name': 'Ted', ,.... 'days': 10}]) => {
    "msg": "Accounts=Ted Name=AD3 Days=10"
}

How to call a loop inside another loop in Ansible.

Let's discuss how to implement a double loop in Ansible.

Suppose you have a variable file as below.

---
apps:
   - name: Test1
     path: 'D:\Logs'
     users:
      - user: User1
        perm: Read
      - user: User2
        perm: Write
   - name: Test2
     path: 'D:\Log1'
     users:
      - user: User3
        perm: Read
      - user: User4
        perm: Write
vars.yml

You might have noticed that this is a nested array. We cannot access the User1 variable value with a single loop. Let's see how to access it with nested loops.

We will create two playbooks. The outer playbook is referred to as main.yml and inner playbook is referred to as access.yml.

---
hosts: localhost
vars_files:
    - vars.yml
tasks:
- name: Include the playbook for inner loop
  vars:
   value: "{{ [testing.users] | flatten }}"
  include_tasks: access.yml
  loop: "{{ apps | flatten(levels=1) }}"
  loop_control:
   loop_var: testing
  when: testing.users is defined
main.yml
---
- name: Print.
  debug:
    msg: |
    "Path: {{ testing.path}}
     USER: {{ item.user}}
     PERM: {{  item.perm }}"
  loop: "{{ value }}"
access.yml

The main.yml calls the access.yml in a loop. The loop variable used is apps. We have used loop_var to name the loop variable which can be used later in the access.yml playbook.

The output will be similar to the following.

TASK [Print.] ********
ok: [localhost] => (item={u'user': u'User1', u'perm': u'Read'}) => 
{"msg": ""Path: D:\Logs\n USER: User1\n PERM: Read"\n"}

ok: [localhost] => (item={u'user': u'User2', u'perm': u'Write'}) => 
{"msg": ""Path: D:\Logs\n USER: User2\n PERM: Write"\n"}

TASK [Print.] ********
ok: [localhost] => (item={u'user': u'User3', u'perm': u'Read'}) => 
{"msg": ""Path: D:\Log1\n USER: User3\n PERM: Read"\n"}

ok: [localhost] => (item={u'user': u'User4', u'perm': u'Write'}) => 
{"msg": ""Path: D:\Log1\n USER: User4\n PERM: Write"\n"}
Output

How to use loops in Jinja Templates

Jinja templates are Ansible template files that can store dynamic variables. These variables get replaced by the values passed during playbook execution. Jinja templates in ansible are saved with an extension .j2 We can also use loops in these Jinja templates. Let's see an example of Jinja templates where we need to write all user details to a file

---
- hosts: localhost
  vars:
    users:
     - name: John
       id: 1
     - name: Tom
       id: 2
  tasks:
    - name: Copy template
      template:
       src: file.j2
       dest: users.txt
template_test.yml

The jinja template file.j2 can be defined as below.

{% for item in users %}
  "Name is {{ item.name }} and ID is {{ item.id}}"
{% endfor %}

Here we are iterating through the users variable using a for loop. The variable item is used to iterate through each of the values in the loop. Unlike Ansible playbook, in Ansible Jinja templates it is not necessary to name this variable as "item", it can be named as "user" or any other name as you wish.

Execute the below command to run the playbook and verify the contents of users.txt

ansible-playbook template_test.yml

Output:

# cat users.txt
  "Name is John and ID is 1"
  "Name is Tom and ID is 2"

Vipin

Vipin

I am a dreamer. I admire the web. I admire anything about the web.