Loops

Sequence

You can run a loop with a traditional start and end sequence without the use of a list by using the with_sequence command with the start and end of the loop defined using = assignment.

- name: Run sequence
  debug:
    msg: ""
  with_sequence: start=0 end=10

Loop Vars

You can rename the default name of items in an iterated list which is item by default using the loop_control and loop_var properties. In the example below the name item is changed to my_loop_var instead.

- name: Rename loop
  debug:
    msg: ""
  with_items: [1, 2, 3, 4, 5]
  loop_control:
    loop_var: my_loop_var

Ignore Loop Cases

You can ignore cases in a loop using the failed_when command. This works with a range of conditional operators, such as >, == etc. and will ignore any cases in iteration that meet the failed_when condition.

- name: Create list
  set_fact:
    my_list: [1, 2, 3, 4, 5]

# Prints every item apart from 2

- name: Ignore 2
  debug:
    msg: ""
  failed_when: item == 2

The output for this will be:

TASK [debug] ***************************************************************************************************************************************************************************
ok: [127.0.0.1] => {
    "msg": "my_var"
}

TASK [debug] **************************************
ok: [127.0.0.1] => {
    "msg": "1"
}

TASK [debug] **************************************
failed: [127.0.0.1] => {
    "msg": "2"
}

TASK [debug] **************************************
ok: [127.0.0.1] => {
    "msg": "3"
}

... etc.

fatal: [127.0.0.1]: FAILED! => {"msg": "All items completed"}

If you don’t want the entire playbook to fail once the loop completes you must append ignore_errors to these loop case command.

- name: Ignore 2
  debug:
    msg: ""
  failed_when: item == 2
  ignore_errors: yes

Nested Loops

You can take the cartesian product of two sets of lists (by nesting them) using the with_nested command. You can specify which component of the nested element you want to access by appending a . to the item variable. This specification is zero indexed. So items in the first list are indexed at .0 in the second list at .1 and so on.

- name: Nested list
  debug:
    msg: " : "
  with_nested:
    - [1, 2, 3]
    - ["a", "b", "c"]

# => "1 : a", "1 : b", "1 : c", "2 : a", "2 : b" etc...

You can create a nested loop using the Jinja filter product to generate a cartesian product of two lists in conjunction with the loop command, however this could just as easily be used in other places to generate an n-dimensional list. Importantly, the first list is passed via a | pipe into the product filter which takes a comma separated set of spread arguments of other lists to combine it with, from 1 to N.

- name: create list
  debug:
    msg: " : "
  loop: ""
  vars:
    x: [1, 2, 3]
    y: ["a", "b", "c"]
    z: ["*", "&", "^"

Traversing Lists inside Objects

You can traverse lists nested as properties inside objects (or dicts) by using the with_subelements command. This takes a list of objects as its first input (which must be using handlebars format) and then the name of the key to traverse through which should be a list. The loop will then output the original object and each element in the nested collection. The collection elements are available at the item.1 position and the original object at item.0.

- name: Define objects
  set_fact:
    objects:
      - { key_1: 0, key_2: ['a', 'b'] }
      - { key_1: 1, key_2: ['y', 'z'] }

- name: Iterate through property collections
  debug:
    msg: ""
  with_subelements:
    - "" # name of the collection of objects
    - key_2 # name of the key which is a collection inside the individual objects

# => a, b, y, z

Looping over a set of tasks

You can loop over a set of multiple tasks with a collection by placing the tasks you want to loop over in a separate file and using an include on them with the loop command. The items of the collection will be available inside the included file as `` and each item from the collection will be subbed into the included on each iteration of the loop. The outer run task would be:

- name: Create list
  set_fact:
    my_list:
      - a
      - b
      - c

- name: Run set of tasks
  include: tasks.yml
  loop: ""

And the corresponding included task would be:

# tasks.yml
- name: Do something with the list items
  some_command: ""

- name: Do something else with list items
  another_command: ""

- debug:
    msg: "List item is "

You can replace the loop command with the with_items command and the functionality will be identical.

You can also alias the names of items that you pass into the included playbook so that you don’t have to use item by including an assignment after the name of file that is to be included with the item property from the list assigned to a new name.

- name: Create list
  set_fact:
    my_list:
      - a
      - b
      - c

- name: Run set of tasks
  # assignment to a new name here
  include: tasks.yml letter=
  loop: ""

The corresponding include file (truncated for ease of reading):

# tasks.yml
- debug:
    msg: "List item is "

Looping over tightly coupled tasks using retries

You can loop over a group of tightly coupled tasks that may fail using retries delay by recursively calling an include on a task using the block and failure commands. The example below is taken from Jeff Martin’s blog. You can also define the max_retries, retry_delay and retry_count as facts outside of original recursive include.

- name: Group of tasks that are tightly coupled
  vars:
    max_retries: "5"
    retry_delay: "10"
  block:
  - name: Increment the retry count
    set_fact:
      retry_count: "0"

  - name: Some task
    setting_up:
        do_something: prerequisite action
  
    - name: Some other task
      setting_up:
        do_something: prerequisite action

  - name: Some task that might fail
    failing_task:
        some: setting

  rescue:
    - fail:
        msg: Maximum retries of grouped tasks reached
      when: retry_count | int == max_retries | int

    - debug:
        msg: "Task Group failed, let's give it another shot"

    - name: Sleep between retries
      wait_for:
        timeout: "" # seconds
      delegate_to: localhost
      become: false

    - include_tasks: coupled_task_group.yml