Looping in Ansible with_items

There are many types of loop within Ansible, but by far the most common is the 'standard loop', or the with_items loop.

Curiously, the with_items loop doesn't look much like a loop to the untrained eye. Certainly, syntactically (what a word) it's far removed from for and foreach.

The syntax for a standard / with_items loop looks like this:

- name: Install common software requirements
  apt: pkg={{ item }} state=installed
     - git
     - htop
     - vim

When a Playbook is run, the above task would be interpreted as three separate steps - which is to say that whilst Ansible's output would show only one task having been run, there would be three changed outcomes, one per item installed.

Because we have used quite a simple loop here, Ansible would realise that the output of the entire loop could be inlined.

Perhaps easier to see than to explain:

* cut *

TASK: [Install common software requirements] ***
changed: [] => (item=git,htop,vim)

* cut *

As you'll see in the video, we can make use of the with_items style looping in more involved tasks, combining with other features we have already learned about - variables in this case.

The difference here is that Ansible usually outputs the results of a more complicated loop like this onto one line per item.

Again, perhaps easier to show than to explain - and you will see this in the video around the 4:00 minute mark:

* cut *

TASK: [Create home directory folder structure] ***
changed: [] => (item=src)
changed: [] => (item=backups)
changed: [] => (item=bin)

* cut *

Again, it's showing the same output, but this time one per line.

More Complex Loops

Whilst the with_items style loops are very handy, and very nice to have, you will likely need something more complex as you spend more time with Ansible.

Fortunately, there are many looping options to choose from.

To begin with, we can make our Standard Loop (with_items) more powerful by passing in a list of hashes. This gives us the ability to name our keys inside our hashes, and then reference those names inside our loop.

Again, that probably sounds more complex than it need be, so let's take a look at the example from the Ansible Docs:

- name: add several users
  user: name={{ item.name }} state=present groups={{ item.groups }}
    - { name: 'testuser1', groups: 'wheel' }
    - { name: 'testuser2', groups: 'root' }

We already know pretty much everything that's going on here. The only difference being that we now have access to named subkeys, instead of just having {{ item }}.

This really only scratches the surface of looping.

Nested Loops give you loops inside loops. A handy use for nested loops would be creating the home directory structure for multiple users. All you need to do is specify a list of users, and a list of directories, and let with_nested handle the rest.

There are more loops than I could cover here, and some really only apply to very specific situations.

I would strongly advise you read the docs and get a feel for all the looping options available:

  • with_dict
  • with_fileglob
  • with_subelements
  • with_sequence
  • with_random_choice

These are just a few of what's available, and you can create your own custom looping mechanics on the off-chance that Ansible doesn't provide what you need.

Looping With Dynamic Items

Perhaps a little more realistic / real-world is the situation where you need to pass in some variables from your host_vars/ or group_vars/ directory to the with_items loop.

Let's say I have a playbook that sets up a few website directories. I want to keep this step near the rest of my nginx playbook entries, so define an 'inline' set of tasks as follows:

# /playbook/nginx.yml

- hosts: nginx_servers
  sudo: True

    - name: "check for existence of website directory - {{ item.name }}"
      stat: path="/var/www/{{ item.directory }}"
      register: "website_directory_exists"
      with_items: "{{ website_directories }}"

    # - debug: var=website_directory_exists

    # - debug: msg="item.item={{item.item}}, item.changed={{item.changed}}"
    #  with_items: "{{website_directory_exists.results}}"

    - name: "create website directory - {{ item.item.name }}"
      file: dest="/var/www/{{ item.item.directory }}" 
            owner="{{ nginx_user }}" 
            group="{{ nginx_group }}" 
      when: item.stat.exists == False
      with_items: "{{ website_directory_exists.results }}"

And in my specific host_vars/ or group_vars/ directory, I might have e.g.:

# /host_vars/somehost.yml

website_domain_name: "api.mysite.com"

  - { name: "API", directory: "{{ website_domain_name }}" } 
  - { name: "Demo", directory: "some-demo-path" } 
  - { name: "Another", directory: "another-why-not" } 

What's nice about this setup is that I can pass in a simple list of object hashes (website_directories list), which in itself can contain entries that have variables (e.g. the API hash).

This list can be passed through to my playbook, which checks if the dir exists, and if not, goes about creating one and setting up some user, group, and permission settings.

I've left the debug statements in which helped me figure this problem out. I found those courtesy of Kashyap's stackoverflow answer.

The only part I don't like is the item.item.name, but I can't see a way around that at this stage. Ansible's internal naming for this seems strange. Still, it works well, just maybe worth leaving a comment that it's not a mistake, just for your future overly-eager-refactoring self.

Code For This Course

Get the code for this course.


# Title Duration
1 How To Install Ubuntu Server in Oracle VirtualBox 10:21
2 How to Rename our Ubuntu Server 02:00
3 Installing Ansible on Ubuntu Server 00:33
4 Safety First, Safety Second - Snapshots are like Ctrl+Z 00:11
5 Managing the Ansible Inventory Hosts File 02:16
6 Ansible Ad Hoc Commands 04:27
7 Introduction to Ansible Playbooks 02:14
8 Ansible Handlers 01:39
9 Ansible Variables 03:16
10 Git Your Deploy Just Right 05:35
11 Ansible Roles 05:51
12 Looping in Ansible with_items 04:35
13 Ansible Files For Beginners 06:16
14 Variable Precedence - Where To Put Your Role Vars? 04:13
15 Ansible Templates 05:51
16 Ansible Inventory With Our Own Hosts Files 06:57
17 How to Manage Users with Ansible 08:32
18 Ansible Vault Tutorial 03:48
19 Ansible Galaxy Tutorial 10:03
20 Real World Ansible - Common Role Walkthrough 06:20
21 Ansible MySQL Tutorial 13:44
22 Ansible Symfony and nginx 09:37