ONLY FOR SELF STUDY, NO COMMERCIAL USAGE!!!
Chapter 4. Implementing Task Control
Writing Loops and Conditional Tasks
Task Iteration with Loops
Using loops makes it possible to avoid writing multiple tasks that use the same module.
To iterate a task over a set of items, you can use the loop
keyword. You can configure loops to repeat a task using each item in a list, the contents of each of the files in a list, a generated sequence of numbers, or using more complicated structures.
Simple Loops
A simple loop iterates a task over a list of items. The loop
keyword is added to the task, and takes as a value the list of items over which the task should be iterated. The loop variable item
holds the value used during each iteration.
Consider the following snippet that uses the ansible.builtin.service
module twice to ensure that two network services are running:
- name: Postfix is running
ansible.builtin.service:
name: postfix
state: started
- name: Dovecot is running
ansible.builtin.service:
name: dovecot
state: started
These two tasks can be rewritten to use a simple loop so that only one task is needed to ensure that both services are running:
- name: Postfix and Dovecot are running
ansible.builtin.service:
name: "{
{ item }}"
state: started
loop:
- postfix
- dovecot
The loop can use a list provided by a variable.
In the following example, the mail_services
variable contains the list of services that need to be running.
vars:
mail_services:
- postfix
- dovecot
tasks:
- name: Postfix and Dovecot are running
ansible.builtin.service:
name: "{
{ item }}"
state: started
loop: "{
{ mail_services }}"
Loops over a List of Dictionaries
The loop list does not need to be a list of simple values.
In the following example, each item in the list is actually a dictionary. Each dictionary in the example has two keys, name
and groups
, and the value of each key in the current item
loop variable can be retrieved with the item['name']
and item['groups']
variables, respectively.
- name: Users exist and are in the correct groups
user:
name: "{
{ item['name'] }}"
state: present
groups: "{
{ item['groups'] }}"
loop:
- name: jane
groups: wheel
- name: joe
groups: root
The outcome of the preceding task is that the user jane
is present and a member of the group wheel
, and that the user joe
is present and a member of the group root
.
Earlier-style Loop Keywords
Before Ansible 2.5, most playbooks used a different syntax for loops. Multiple loop keywords were provided, which used the with_
prefix, followed by the name of an Ansible look-up plug-in (an advanced feature not covered in detail in this course). This syntax for looping is very common in existing playbooks, but will probably be deprecated at some point in the future.
Some examples are listed in the following table:
Table 4.1. Earlier-style Ansible Loops
Loop keyword | Description |
---|---|
with_items |
Behaves the same as the loop keyword for simple lists, such as a list of strings or a list of dictionaries. Unlike loop , if lists of lists are provided to with_items , they are flattened into a single-level list. The item loop variable holds the list item used during each iteration. |
with_file |
Requires a list of control node file names. The item loop variable holds the content of a corresponding file from the file list during each iteration. |
with_sequence |
Requires parameters to generate a list of values based on a numeric sequence. The item loop variable holds the value of one of the generated items in the generated sequence during each iteration. |
The following playbook shows an example of the with_items
keyword:
vars:
data:
- user0
- user1
- user2
tasks:
- name: "with_items"
ansible.builtin.debug:
msg: "{
{ item }}"
with_items: "{
{ data }}"
Important note:
Since Ansible 2.5, the recommended way to write loops is to use the loop
keyword.
However, you should still understand the earlier syntax, especially with_items
, because it is widely used in existing playbooks. You are likely to encounter playbooks and roles that continue to use with_*
keywords for looping.
The Ansible documentation contains a good reference on how to convert the earlier loops to the new syntax, as well as examples of how to loop over items that are not simple lists. See the “Migrating from with_X to loop” section of the Ansible User Guide.
Using Register Variables with Loops
The register
keyword can also capture the output of a task that loops. The following snippet shows the structure of the register
variable from a task that loops:
[student@workstation loopdemo]$ cat loop_register.yml
---
- name: Loop Register Test
gather_facts: false
hosts: localhost
tasks:
- name: Looping Echo Task
ansible.builtin.shell: "echo This is my item: {
{ item }}"
loop:
- one
- two
register: echo_results
- name: Show echo_results variable
ansible.builtin.debug:
var: echo_results
Running the preceding playbook yields the following output:
[student@workstation loopdemo]$ ansible-navigator run -m stdout loop_register.yml
PLAY [Loop Register Test] ******************************************************
TASK [Looping Echo Task] *******************************************************
changed: [localhost] => (item=one)
changed: [localhost] => (item=two)
TASK [Show echo_results variable] **********************************************
ok: [localhost] => {
"echo_results": {
"changed": true,
"msg": "All items completed",
"results": [
{
"ansible_loop_var": "item",
"changed": true,
"cmd": "echo This is my item: one",
"delta": "0:00:00.004519",
"end": "2022-06-29 17:32:54.065165",
"failed": false,
...output omitted...
"item": "one",
"msg": "",
"rc": 0,
"start": "2022-06-29 17:32:54.060646",
"stderr": "",
"stderr_lines": [],
"stdout": "This is my item: one",
"stdout_lines": [
"This is my item: one"
]
},
{
"ansible_loop_var": "item",
"changed": true,
"cmd": "echo This is my item: two",
"delta": "0:00:00.004175",
"end": "2022-06-29 17:32:54.296940",
"failed": false,
...output omitted...
"item": "two",
"msg": "",
"rc": 0,
"start": "2022-06-29 17:32:54.292765",
"stderr": "",
"stderr_lines": [],
"stdout": "This is my item: two",
"stdout_lines": [
"This is my item: two"
]
}
],
"skipped": false
}
}
...output omitted...
In the preceding example, the results
key contains a list. In the next example, the playbook is modified so that the second task iterates over this list:
# new_loop_register.yml
---
- name: Loop Register Test
gather_facts: false
hosts: localhost
tasks:
- name: Looping Echo Task
ansible.builtin.shell: "echo This is my item: {
{ item }}"
loop:
- one
- two
register: echo_results
- name: Show stdout from the previous task.
ansible.builtin.debug:
msg: "STDOUT from previous task: {
{ item['stdout'] }}"
loop: "{
{ echo_results['results'] }}"
After running the preceding playbook, you see the following output:
PLAY [Loop Register Test] ******************************************************
TASK [Looping Echo Task] *******************************************************
changed: [localhost] => (item=one)
changed: [localhost] => (item=two)
TASK [Show stdout from the previous task.] *************************************
ok: [localhost] => (item={'changed': True, 'stdout': 'This is my item: one', 'stderr': '', 'rc': 0, 'cmd': 'echo This is my item: one', 'start': '2022-06-29 17:41:15.558529', 'end': '2022-06-29 17:41:15.563615', 'delta': '0:00:00.005086', 'msg': '', 'invocation': {'module_args': {'_raw_params': 'echo This is my item: one', '_uses_shell': True, 'warn': False, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': ['This is my item: one'], 'stderr_lines': [], 'failed': False, 'item': 'one', 'ansible_loop_var': 'item'}) => {
"msg": "STDOUT from previous task: This is my item: one"
}
ok: [localhost] => (item={'changed': True, 'stdout': 'This is my item: two', 'stderr': '', 'rc': 0, 'cmd': 'echo This is my item: two', 'start': '2022-06-29 17:41:15.810566', 'end': '2022-06-29 17:41:15.814932', 'delta': '0:00:00.004366', 'msg': '', 'invocation': {'module_args': {'_raw_params': 'echo This is my item: two', '_uses_shell': True, 'warn': False, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': ['This is my item: two'], 'stderr_lines': [], 'failed': False, 'item': 'two', 'ansible_loop_var': 'item'}) => {
"msg": "STDOUT from previous task: This is my item: two"
}
...output omitted...
Running Tasks Conditionally
Ansible can use conditionals to run tasks or plays when certain conditions are met.
The following scenarios illustrate the use of conditionals in Ansible.
- Define a hard limit in a variable (for example,
min_memory
) and compare it against the available memory on a managed host. - Capture the output of a command and evaluate it to determine whether a task completed before taking further action. For example, if a program fails, then a batch is skipped.
- Use Ansible facts to determine the managed host network configuration and decide which template file to send (for example, network bonding or trunking).
- Evaluate the number of CPUs to determine how to properly tune a web server.
- Compare a registered variable with a predefined variable to determine if a service changed. For example, test the MD5 checksum of a service configuration file to see if the service is changed.
Conditional Task Syntax
The when
statement is used to run a task conditionally. It takes as a value the condition to test. If the condition is met, the task runs. If the condition is not met, the task is skipped.
One of the simplest conditions that can be tested is whether a Boolean variable is true or false. The when
statement in the following example causes the task to run only if run_my_task
is true.
---
- name: Simple Boolean Task Demo
hosts: all
vars:
run_my_task: