How to Use Loops in Ansible Playbook

In the course of executing tasks in Ansible playbook, you might come across some tasks that are repetitive in nature. These are tasks that require you to create multiple plays, something which can be quite tedious. As with any programming language, loops in Ansible provide an easier way of executing repetitive tasks using fewer lines of code in a playbook.

When creating loops, Ansible provides these two directives: loop and with_*  keyword.

The loop keyword was recently added to Ansible 2.5. The loop keyword is usually used to create simple and standard loops that iterate through several items.

The with_*   keyword is used with a number of lookup plugins when iterating through values. Lookup plugins enable Ansible to access information from external sources such as external data stores, filesystems etc. The with_*  lookup is still very much in use and has not yet been deprecated.

Let’s now have a look at how you can implement Loops in Ansible.

Iterating over simple loops 

Consider a Playbook that adds a new user on the target system using the user module as shown:

---
- hosts: ubuntu_webserver
  tasks:
    - name: Create new user john
      user:
        name: john
        state: present

This looks okay. But what if we have multiple users to add to the target system? How would we go about that? One way would be to duplicate the tasks as shown in the example below where we are adding 3 users.

---
- hosts: ubuntu_webserver
  tasks:
    - name: Create new user john
      user:
        name: john
        state: present

    - name: Create new user mike
      user:
        name: mike
        state: present

    - name: Create new user andrew
      user:
        name: andrew
        state: present

As you can seem this calls for a lot of duplication and repetition.

To make things easier, the same playbook can be written using the loop directive. The loop directive executes the same task multiple times. It  stores the value of each item in a variable called item.So, instead of specifying the names of the users to be added, simply specify a variable called item enclosed between double curly braces as shown.

 name: ‘{{ item }}’

Therefore, each of the items within the loop will be referenced by the variable.

As you can see, our playbook is now much more organized with unnecessary duplication/repetition.

$ vi create_users.yaml
---
- hosts: ubuntu_webserver
  tasks:
    - name: Create new users
      user:
        name: '{{ item }}'
        state: present
      loop:
        - john
        - mike
        - andrew

Simple-playbook-loops

Also, you can use the with_items directive instead of the loop directive. Both will yield the same result.

---
- hosts: ubuntu_webserver
  become: yes
  tasks:
    - name: Create new users
      user:
        name: '{{ item }}'
        state: present

      with_items:
        - john
        - mike
        - andrew

You can now run the playbook to create the users using the ansible-playbook command as shown below:

$ ansible-playbook -i inventory.txt create_users.yaml

Ansible-Playbook-execution-loops

Iterating over a list of Dictionaries

In the first example, we looked at a simple standard loop where the array was a list of string values representing users to be added to the remote target.

But what if we need to add the uid to the loop such that each item now has two values: the username and uid. How would you pass 2 values in an array?

In this scenario, you will have to pass an array of dictionaries, each with 2 key-value pairs as shown. The keys would be the name and uid whilst the values will be the username and the ID of each user.

Under the ‘tasks‘ section, you can no longer define the variable item as before. Since we have 2 values, this will translate into two variables: item.name & item.uid.

The complete playbook is shown below:

$ vi create_users.yaml
---
- hosts: ubuntu_webserver
  become: yes
  tasks:
    - name: Create new users
      user:
        name: '{{ item.name }}'
        uid: '{{ item.uid }}'
        state: present

      loop:
        - name: john
          uid: 1020
        - name: mike
          uid: 1030
        - name: andrew
          uid: 1040

Playbook-iterating-over-mulitple-values

The array of dictionaries can also be represented in a JSON format.

loop:
  - { name: john , uid: 1020 }
  - { name: mike , uid: 1030 }
  - { name: andrew , uid: 1040}

When executed, you will get the following output:

$ ansible-playbook -i inventory.txt create_users.yaml

Ansible-playbook-loops-execution

Ansible Loops with indexes

Occasionally, you may want to keep track of the index values within your array of items. For this, use the ‘with indexed_items‘ lookup. The index value begins from 0 whilst The loop index begins from item.0 and the value from item.1

Consider the playbook below:

$ vi indexes.yaml
---
- hosts: ubuntu_webserver
  tasks:
  - name: Indexes with Ansible loop
    debug:
      msg: "The car at {{ item.0 }} is {{ item.1 }}"
    with_indexed_items:
      - "Nissan"
      - "Mercedes"
      - "Toyota"
      - "Mazda"
      - "BMW"

Ansible-loops-with-indexes

When executed, the playbook displays the index value of each item in the array list.

$ ansible-playbook -i inventory.txt indexes.yaml

Ansible-indexes-loops-execution

Conditionals in Ansible Loops

In Ansible loops you can use the conditional statement when to control the looping based on the nature of variables or system facts. Consider the playbook below where we have a list of packages that need to be installed.

We have specified an array called ‘packages‘ that contains a list of packages that need to be installed. Each of the items on the array contains the name of the package to be installed and the property called ‘required‘ which is set to ‘True‘ for 2 packages and ‘False‘ for one package.

$ vi install-packages.yaml
---
- name: Install software
  become: yes
  hosts: all
  vars:
    packages:
      - name: neofetch
        required: True

      - name: cpu-checker
        required: True

      - name: screenfetch
        required: False
  tasks:
    - name: Install "{{ item.name }}" on Ubuntu
      apt:
        name: "{{ item.name }}"
        state: present

      when:
        - item.required == True
        - ansible_facts['distribution'] =="Ubuntu"

      loop: "{{ packages }}"

Ansible-loops-with-conditionals

The ‘when‘ conditional statement seeks to install the software packages with the property ‘required‘ set to ‘True’ on target systems which are Ubuntu distros. Below is the output of the playbook when executed.

The verbose output clearly shows the packages that are being installed and the one which is ignored based on the conditional statement.

Ansible-loops-when-execution

And this brings us to the end of this topic. We do hope that you have a decent understanding of Loops in Ansible playbooks. Feel free to reach out for any clarification.

Also Read : How to Use Jinja2 Template in Ansible Playbook

6 thoughts on “How to Use Loops in Ansible Playbook”

  1. Hello,
    Thanks for your excellent article. I would like to get some clarification on the last out put “Install “{{ item.name }}” on Ubuntu”.
    Even loop has executed successfully ,but item.name still doesnt work well!!!. Do you have any advice to make that work.
    Thanks

    Reply
    • Hey Ramkumar. For the loop to work, ensure that in your setup you have a system running Ubuntu OS. Otherwise, the packages in the loop won’t be installed. Also, can you provide the output you got after running the playbook? I’d like to know the error that you are getting.

      Reply
  2. Hello James,
    Thanks for your reply. i was managed to install packages successfully.
    But see the output from your snippet for specific to TASK [Install “{{ item.name }}” on ubuntu”] doestn show particular package name when it was executed instead still shows “{{ item.name }} . Any idea !!!

    Reply
    • Hey Ramkumar, I now understand what you are saying.

      It appears that way because that’s the name we gave to the task in the Playbook. So during runtime, the name of the task is displayed, In this case – Install “{{ item.name }}” on Ubuntu. The variable “{{ item.name }}” cannot be replaced since it’s just a task name.

      Reply
  3. I’m already familiar with the syntax you use in “Iterating over a list of Dictionaries”, but wouldn’t it actually be possible to add the whole dictionary directly instead of set the entries one by one? E.g. instead of doing this `name: ‘{{ item.name }}’` are you able to directly add the dictionary defined in the loop, since it already has the proper naming?

    Reply
  4. Very nice article.
    I want to do something similar, but I don’t know if it’s possible to do the same dinamicaly.
    The intention is don’t edit the yml file and that one read the user information from files.

    Maybe I have a folder that contains:
    /users/john.yml
    /users/mike.yml
    /users/andrew.yml

    And the playbook look inside folder /users/ get all of them an iterate.
    In that way I can add or remove yml users files without editing the main function.

    Is that possible?
    Thanks in advance

    Reply

Leave a Reply to Michael Cancel reply