In this blog post, we will cover how to use 'Ansible loops'. We'll start with what 'loops' are by using a simple everyday example, so everyone can understand. Then, we'll explain why we use loops in Ansible and how they can help us. We'll look at the most basic type of loop, and then see a few different kinds. We'll show how these loops are used in real situations. And finally, we'll share some common mistakes to avoid when using loops in Ansible. Let's get started.
Why We Use Loops in Ansible?
Loops in Ansible are like shortcuts for doing the same task many times. Instead of repeating the same steps over and over again, we use a loop to do the task as many times as we need. This saves time and makes our tasks less error-prone because we only have to write the instructions once.
Let's imagine you're setting up a new Linux server. It needs various software packages installed, like 'nginx', 'mysql', and 'php'. Without loops, you would have to write a separate task for installing each package in your Ansible playbook, like this.
- name: Install nginx
apt:
name: nginx
state: present
- name: Install mysql
apt:
name: mysql
state: present
- name: Install php
apt:
name: php
state: present
This works, but it's repetitive and hard to manage. If you have to install ten or twenty packages, the playbook becomes long and hard to read.
Now, let's use a loop to do the same task. You list all the packages you want to install and then write a single task that loops over the list.
- name: Install Packages
apt:
name: "{{ item }}"
state: present
loop:
- nginx
- mysql
- php
This playbook does exactly the same job as the first one, but it's much shorter and easier to understand. If you need to add more packages, you just add them to the list. The loop will take care of installing each one. I hope you can start to see the power of loops in Ansible.
{{ item }}
is used as a placeholder for each element in the list you're looping through. We will go through this in a lot more detail later in the post.Iterating over a simple list
Ansible provides an easy and readable way to loop over a set of tasks using the loop
keyword. The purpose of a loop is to repeat the same task multiple times, which simplifies the playbook and reduces repetition. Here is the basic syntax of an Ansible loop.
tasks:
- name: Task description
ansible_module:
key: "{{ item }}"
loop: [item1, item2, item3]
ansible_module
refers to the module you're using for the task. Ansible has many modules for various tasks, such asapt
for managing packages,file
for file management, etc.key: "{{ item }}"
is where we tell Ansible what to change in each loop. The{{ item }}
is a placeholder that Ansible replaces with each item in the loop list.loop
is the keyword that starts the loop, followed by the list of items to loop over.
Going back to the previous package installation example, without a loop, we'd need to write a separate task for each package. With a loop, we can install all packages with a single task.
---
- name: Install packages using Ansible
hosts: servers
tasks:
- name: Install Packages
apt:
pkg: "{{ item }}"
state: present
loop:
- nginx
- mysql
- php
- We're using the
apt
module, which manages packages on Debian-based systems. - The line
pkg: "{{ item }}"
tells Ansible what to install in each loop iteration. Ansible replaces{{ item }}
with each package name from the list. - The
loop
keyword starts the loop, followed by a list of packages we want to install.
When you run this playbook, Ansible will install the 'nginx', 'mysql', and 'php' packages one by one. If you need to install more packages, you simply add them to the list. This is why loops are so powerful in Ansible: they let us manage multiple items with a single task, no matter how many items we have.
Looping over a Dictionary
To loop over this dictionary, you can use the dict2items
filter, which converts the dictionary into a list of items. Each item in this list is another dictionary with a 'key' and a 'value'.
---
- name: Assign roles to servers
hosts: localhost
gather_facts: no
tasks:
- name: Print server role info
ansible.builtin.debug:
msg: "Server: {{ item.key }}, Role: {{ item.value }}"
loop: "{{ servers | dict2items }}"
vars:
servers:
server1: 'web'
server2: 'database'
servers
is a dictionary where each key is a server name and the value is the role of that server.- We're using the
dict2items
filter to convert theservers
dictionary into a list of items. Each item is a smaller dictionary with a 'key' (the server name) and a 'value' (the role). ansible.builtin.debug
is a module that prints messages to the console. We're using it to print out each server and its corresponding role.msg: "Server: {{ item.key }}, Role: {{ item.value }}"
is where we tell Ansible what to print. It takes each server name and its role and prints them in a readable format.
When you run this playbook, Ansible will print out a message for each server, showing the role assigned to it. This is a straightforward way to loop over dictionaries in Ansible when each key has a single corresponding value.
➜ ansible_local ansible-playbook loop_dict.yml
PLAY [Assign roles to servers] ********************************************************************************************************
TASK [Print server role info] *********************************************************************************************************
ok: [127.0.0.1] => (item={'key': 'server1', 'value': 'web'}) => {
"msg": "Server: server1, Role: web"
}
ok: [127.0.0.1] => (item={'key': 'server2', 'value': 'database'}) => {
"msg": "Server: server2, Role: database"
}
Looping over Nested Lists
Product Filter
you can use the product
filter for creating a Cartesian Product of the given lists, which means every item of the first list is paired with every item of the second list. This can be useful in scenarios where you need to perform a task with every possible combination of items from multiple lists. Here's an example using the product
filter.
---
- name: Install packages on servers
hosts: localhost
gather_facts: no
tasks:
- name: Install packages
ansible.builtin.debug:
msg: "Installing {{ item.1 }} on {{ item.0 }}"
loop: "{{ ['server1', 'server2'] | product(['nginx', 'mysql', 'php']) | list }}"
➜ ansible_local ansible-playbook nested_loop_product.yml
PLAY [Install packages on servers] ****************************************************************************************************
TASK [Install packages] ***************************************************************************************************************
ok: [127.0.0.1] => (item=['server1', 'nginx']) => {
"msg": "Installing nginx on server1"
}
ok: [127.0.0.1] => (item=['server1', 'mysql']) => {
"msg": "Installing mysql on server1"
}
ok: [127.0.0.1] => (item=['server1', 'php']) => {
"msg": "Installing php on server1"
}
ok: [127.0.0.1] => (item=['server2', 'nginx']) => {
"msg": "Installing nginx on server2"
}
ok: [127.0.0.1] => (item=['server2', 'mysql']) => {
"msg": "Installing mysql on server2"
}
ok: [127.0.0.1] => (item=['server2', 'php']) => {
"msg": "Installing php on server2"
Subelements
In Ansible, you can also loop over nested lists using the subelements
filter. This is useful when you want to perform a task for each combination of elements in the outer and inner lists. Here's a basic structure of how to use the subelements
filter.
---
- name: Install packages on servers
hosts: localhost
gather_facts: no
tasks:
- name: Install packages
ansible.builtin.debug:
msg: "Installing {{ item.1 }} on {{ item.0.name }}"
loop: "{{ servers | subelements('packages') }}"
vars:
servers:
- name: server1
packages:
- nginx
- mysql
- name: server2
packages:
- httpd
- postgresql
servers
is a list of dictionaries. Each dictionary has a 'name' key for the server name and a 'packages' key for the list of packages.- We're using the
subelements('packages')
filter to loop over both the servers and their packages. ansible.builtin.debug
is a module that prints messages to the console. We're using it to print out each server-package combination.msg: "Installing {{ item.1 }} on {{ item.0.name }}"
is where we tell Ansible what to print. It takes each server name and each package and prints them in a readable format.
➜ ansible_local ansible-playbook nested_list_loop.yml
PLAY [Install packages on servers] ****************************************************************************************************
TASK [Install packages] ***************************************************************************************************************
ok: [127.0.0.1] => (item=[{'name': 'server1', 'packages': ['nginx', 'mysql']}, 'nginx']) => {
"msg": "Installing nginx on server1"
}
ok: [127.0.0.1] => (item=[{'name': 'server1', 'packages': ['nginx', 'mysql']}, 'mysql']) => {
"msg": "Installing mysql on server1"
}
ok: [127.0.0.1] => (item=[{'name': 'server2', 'packages': ['httpd', 'postgresql']}, 'httpd']) => {
"msg": "Installing httpd on server2"
}
ok: [127.0.0.1] => (item=[{'name': 'server2', 'packages': ['httpd', 'postgresql']}, 'postgresql']) => {
"msg": "Installing postgresql on server2"
Pausing within a Loop
Sometimes, when performing actions in a loop, we might need to pause between iterations. This could be because we're waiting for a server to restart, for a file to become available, or for any number of reasons. You canloop_control
with pause
to add a delay between each loop iteration.
---
- name: Pausing within a loop using loop_control
hosts: localhost
gather_facts: no
tasks:
- name: Print number with pause
ansible.builtin.debug:
msg: "{{ item }}"
loop: [1, 2, 3]
loop_control:
pause: 3
loop: [1, 2, 3]
specifies that we want to loop over the numbers 1, 2, and 3.ansible.builtin.debug
module prints the current number.loop_control: pause: 3
pauses for 3 seconds after each task in the loop.
When you run this playbook, Ansible will print each number, pause for 3 seconds, and then proceed to the next iteration of the loop.
Closing Thoughts
So, there you have it. We've learned that Ansible loops are a handy tool that helps us automate repetitive tasks. Please let me know in the comments if you have any questions or feedback.