Ansible 101: #010 - Loops 1

Hello everyone! In this part of the Ansible tutorial, we're going to look at loops. Since there is a lot to say about this topic, I will split it into (at least) two parts. In this part we will start with the basics and simple use of loops, in the following parts I will show you some more details and special features.

We use a loop whenever we want to repeat a certain task several times. A classic example would be setting file permissions on a list of files and directories. With ten different paths, we would need ten different tasks, which is quite a lot of code. Loops allow us to do this in a single task definition. Loops also allow us to execute tasks on lists that we create at runtime.

Let's have a look at a task defined with a simple loop:

- hosts: all
  tasks:
    - name: task with loop
      ansible.builtin.debug: 
        msg: "I am: {{ item }}"
      loop:
        - "Loop-Item 1"
        - "Loop-Item 2"
        - "Item 3"
        - "Another Item"

There are two things of particular importance here. Firstly, the new task level parameter "loop". Here we can either specify a list of values directly (as in the example) or use a variable. More on this in a moment. Secondly, we use the variable "{{ item }}" in the message. This is always automatically available when we define a loop for our task and then contains the appropriate value from the list for each run.

The output of the above example would then look something like this:

PLAY [localhost] ********************************************************************************************************************************************************

TASK [Gathering Facts] **************************************************************************************************************************************************
ok: [localhost]

TASK [task with loop] ****************************************************************************************************************************************************
ok: [localhost] => (item=Loop-Item 1) => {
    "msg": "I am: Loop-Item 1"
}
ok: [localhost] => (item=Loop-Item 2) => {
    "msg": "I am: Loop-Item 2"
}
ok: [localhost] => (item=Item 3) => {
    "msg": "I am: Item 3"
}
ok: [localhost] => (item=Noch ein Item) => {
    "msg": "I am: Another Item"
}

PLAY RECAP **************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

So we have defined a task that runs once per item under "loop". And each time with the appropriate value from the loop in the 'item' variable.

Let's look at this with a practical example. Let's say we want to create several directories on our web servers:

/var/www/html/my_icons
/var/www/html/my_documents
/var/www/html/my_other_stuff

In this example, we do not want to define the 3 values directly under the "loop" parameter, but rather define them in a variable beforehand. This allows us to vary the content using the host and group variables, for example.

So we first create a variable in the groupvars for "webservers":

---
dirs_to_create: 
  - "/var/www/html/my_icons"
  - "/var/www/html/my_documents"
  - "/var/www/html/my_other_stuff"

~/ansible-guide/group_vars/webservers/main.yml

The 'dirs_to_create' variable is of type 'list' and is now available to all hosts in the 'webservers' group. Now let's create an appropriate playbook:

---
- hosts: webservers
  tasks:
    - name: create directories
      ansible.builtin.file:
        path: "{{ item }}"
        state: directory
      loop: "{{ dirs_to_create }}"
      become: true

~/ansible-guide/loops1.yml

So we create a task with the "file" module. We can use this to manage files and directories. In this case, instead of specifying the list directly under "loop", we specify the variable "dirs_to_create". Ansible recognises that this is a list and applies "loop" to it. The list contains all the paths we want to create. So we specify the loop variable "item" in the module parameter "path" and use "state: directory" to specify that Ansible should create a directory.

Then we run the playbook:

ansible-playbook -i inventory.txt loops1.yml
PLAY [webservers] ********************************************************************************************************************************************************

TASK [Gathering Facts] **************************************************************************************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]

TASK [create directories] ***********************************************************************************************************************************************
changed: [ansible-guide-1] => (item=/var/www/html/my_icons)
changed: [ansible-guide-1] => (item=/var/www/html/my_documents)
changed: [ansible-guide-1] => (item=/var/www/html/my_other_stuff)
changed: [ansible-guide-2] => (item=/var/www/html/my_icons)
changed: [ansible-guide-2] => (item=/var/www/html/my_documents)
changed: [ansible-guide-2] => (item=/var/www/html/my_other_stuff)

PLAY RECAP **************************************************************************************************************************************************************
ansible-guide-1                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
ansible-guide-2                 : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

For the sake of completeness, we check that the directories were actually created on the server:

ssh ansible-guide-1
ls -rtl /var/www/html
mow@notebook:~/blog$ ls -rtl /var/www/html/
insgesamt 16
-rw-r--r-- 1 root root   22 Feb 21 10:33 index.html
drwxr-xr-x 2 root root 4096 Mai  4 17:02 my_icons
drwxr-xr-x 2 root root 4096 Mai  4 17:02 my_documents
drwxr-xr-x 2 root root 4096 Mai  4 17:02 my_other_stuff

Looks good! Now you have the basis for further work with loops. Of course, you can use them not only in the "file" and "debug" modules, but with almost any module!

That's it for this part. In the next post, I'll show you some special features and advanced uses for loops.

As always, I look forward to any kind of constructive feedback!

Mow