After completing this section, you should be able to:
Implement iteration (loops) in plays to apply tasks to collections of items.
Build conditions in plays to control when tasks perform actions.
Customize how plays respond to error codes using conditions.
Combine conditions and loops to create tasks that are both powerful and flexible.
The when statement is used to run a task only when a condition is satisfied.
A simple condition is whether a Boolean variable is true or false.
The when statement in the example below causes the task to run only if the show_interfaces variable is true:
The indentation of the when statement matters.
The when statement is not a module variable, so it must be outside the module at the top level of the task.
A task is a YAML mapping, and the when statement is a key in the task, like the task's name and the module it uses.
A common convention places when statements after the task's name and the module (and module arguments).
---
- name: illustrating a simple condition
hosts: cs01
vars:
show_interfaces: True
tasks:
- name: conditionally issue command
vyos_command:
commands:
- show interfaces
register: command_output
when: show_interfaces == True
- name: show command output
debug:
msg: "{{ command_output }}"
when: show_interfaces == True
A single, compound when statement can evaluate multiple conditions.
Use the keywords and or or to form a compound when statement.
Multiple conditions that all need to be true (a logical 'and') can also be specified as a list, as in this example.
---
- name: a play for ios devices that uses ios_facts
hosts: ios
gather_facts: no
tasks:
- name: look at some facts
ios_facts:
gather_subset:
- hardware
- name: show results
debug:
msg:
- "model: {{ ansible_net_model }}"
- "version: {{ ansible_net_version }}"
when:
- ansible_net_model == 'CSR1000V'
- ansible_net_version is version('03.14', '>=')PLAY [a play for ios devices that uses ios_facts] ***
TASK [look at some facts] ***************************
ok: [cs01]
TASK [show results] *********************************
ok: [cs01] => {
"msg": [
"model: CSR1000V",
"version: 03.14.01.S"
]
}
PLAY RECAP ******************************************
cs01 : ok=2 changed=0 unreachable=0 failed=0Conditionals are a construct found in programming languages, and so it is tempting to use conditionals in Ansible in a similar way, as in the example below:
---
- hosts: spines
tasks:
- name: build list of interfaces
vyos_command:
commands: show interfaces | grep eth | cut -d' ' -f1
register: interfaces
- name: set eth0 description
vyos_config:
lines:
- set interface ethernet eth0 description 'Outside'
loop: "{{ interfaces.stdout_lines }}"
when: item == "eth0"
When you find yourself using the when statement, consider whether there might be a simpler alternative.
There might be a way to make use of host groups, for instance, to limit the scope of tasks.
The condition might not be necessary.
---
- name: configure interface descriptions on spine devices
hosts: spines
tasks:
- name: configure a spine device
vyos_config:
lines:
- set interface ethernet eth0 description 'Outside'
- set interface ethernet eth1 description 'Inside'Ansible provides many directives that serve to control and direct repeated application of a task to individual objects in the context of collections of objects. This is a sample.
| Directive | Description |
|---|---|
loop | Iterates a task over a list of items. List elements may be mappings. |
with_nested | Takes an outer list of two or more inner lists and runs
a loop that acts upon an array of items. The array
elements are referred to as item.0, item.1, and so
forth. |
with_sequence | Generates a sequence of items in increasing numerical order. Can take start and end arguments that have a decimal, octal, or hexadecimal integer value. |
with_dict | Takes a hash (a dictionary) and loops through its
elements, returning item.key and item.value. |
A simple loop uses the loop directive to iterate a task over a list of items.
- name: issue show interface command for each interface in list
hosts: cs01
gather_facts: no
vars:
interface_list: [ GigabitEthernet1, GigabitEthernet2 ]
tasks:
- name: show each interface
ios_command:
commands:
- show interface {{ item }}
register: results
loop: "{{ interface_list }}"
- name: show results
debug:
msg: "{{ results }}"
The with_nested directive causes the task to iterate over a list of lists.
This provides access within the body of the task to an array of items, one from each list.
---
- name: illustrating nested loops
hosts: spine01
gather_facts: no
vars:
source_interfaces: [ eth0, eth1 ]
dest_addresses: [ 172.25.250.254, 172.25.250.195 ]
tasks:
- name: with each destination, source ping from each interface
vyos_command:
commands:
- ping {{ item.0 }} interface {{ item.1 }} count 2
register: ping_output
with_nested:
- "{{ dest_addresses }}"
- "{{ source_interfaces }}"
- name: show ping_output
debug:
msg: "{{ ping_output }}"Display the target interface, but only if it is UP.
---
- name: >
show us the target interface
but only if it is UP
hosts: ios
gather_facts: no
vars:
target_interface: GigabitEthernet4
tasks:
- name: gather facts
ios_facts:
gather_subset: all
when: ansible_network_os == 'ios'
- name: display facts
# 'ansible_net_interfaces' fact is a dict of interfaces
# with interface name as key
debug:
msg: "{{ item }}"
with_dict: "{{ ansible_net_interfaces }}"
when:
# Multiple conditions that all need to be true
# (a logical 'and') can be specified as a list
- item.key == target_interface
- item.value['operstatus'] == 'up'PLAY [show us the target interface but only if it is UP] **************
TASK [debug] **********************************************************
ok: [cs01] => (item=None) => {
"msg": {
"key": "GigabitEthernet4",
"value": {
"bandwidth": 1000000,
"description": null,
"duplex": "Full",
"ipv4": [
{
"address": "172.16.10.1",
"subnet": "30"
}
],
"ipv6": [
{
"address": "FDFB:EDE0:BDF2:C094::1",
"subnet": "FDFB:EDE0:BDF2:C094::/64"
}
],
"lineprotocol": "up ",
"macaddress": "2cc2.600c.e636",
"mediatype": "RJ45",
"mtu": 1500,
"operstatus": "up",
"type": "CSR vNIC"
}
}
}
PLAY RECAP ************************************************************
cs01 : ok=2 changed=0 unreachable=0 failed=0A nonzero command exit code fails its task; it skips the remaining tasks for this host.
---
- name: >
an illustration of
normal task processing
hosts: ios
gather_facts: no
vars:
intf: Interface GigabitEthernet16
tasks:
- name: a failed task
# this task fails because
# no such interface exists
ios_config:
lines:
- shutdown
parents: "{{ intf }}"
- name: a good task
debug:
msg: "this is a good task"PLAY [an illustration of normal task processing] ***************************
TASK [a failed task] *******************************************************
An exception occurred during task execution. To see the full traceback, use -
vvv. The error was: cs01(config)#
fatal: [cs01]: FAILED! => {"changed": false, "module_stderr": "Traceback
(most recent call last):\n File \"/tmp/ansible_Rz9r3l/
ansible_module_ios_config.py\", line 583, in <module>\n main()\n
File \"/tmp/ansible_Rz9r3l/ansible_module_ios_config.py\", line 512, in
main\n load_config(module, commands)\n File \"/tmp/ansible_Rz9r3l/
ansible_modlib.zip/ansible/module_utils/network/ios/ios.py\", line 162, in
load_config\n File \"/tmp/ansible_Rz9r3l/ansible_modlib.zip/ansible/
module_utils/connection.py\", line 149, in
__rpc__\nansible.module_utils.connection.ConnectionError: Interface
GigabitEthernet16\r\n ^\r\n% Invalid input
detected at '^' marker.\r\n\r\ncs01(config)#\n", "module_stdout": "", "msg":
"MODULE FAILURE", "rc": 1}!
to retry, use: --limit @/home/student/tmp/chgxpl/ansible-for-networkautomation/
l/2/ios-normal-error-handling.retry
PLAY RECAP *****************************************************************
cs01 : ok=0 changed=0 unreachable=0 failed=1The rest of the tasks in the play are skipped for this host because the first task failed.
---
- name: >
an illustration of
ignoring errors on a task
hosts: ios
gather_facts: no
vars:
intf: Interface GigabitEthernet16
tasks:
- name: a failed task
# this task fails because
# no such interface exists
ios_config:
lines:
- shutdown
parents: "{{ intf }}"
ignore_errors: yes
- name: a good task
debug:
msg: "this is a good task"PLAY [an illustration of normal task processing] **************************** TASK[a failed task]******************************************************** An exception occurred during task execution. To see the full traceback, use - vvv. The error was: cs01(config)# fatal: [cs01]: FAILED! => {"changed": false, "module_stderr": "Traceback (most recent call last):\n File \"/tmp/ansible_02hpnL/ ansible_module_ios_config.py\", line 583, in <module>\n main()\n File \"/tmp/ansible_02hpnL/ansible_module_ios_config.py\", line 512, in main\n load_config(module, commands)\n File \"/tmp/ansible_02hpnL/ ansible_modlib.zip/ansible/module_utils/network/ios/ios.py\", line 162, in load_config\n File \"/tmp/ansible_02hpnL/ansible_modlib.zip/ansible/ module_utils/connection.py\", line 149, in __rpc__\nansible.module_utils.connection.ConnectionError: Interface GigabitEthernet16\r\n ^\r\n% Invalid input detected at '^' marker.\r\n\r\ncs01(config)#\n", "module_stdout": "", "msg": "MODULE FAILURE", "rc": 1}...ignoringTASK[a good task]********************************************************** ok: [cs01] => { "msg": "this is a good task" } PLAY RECAP ****************************************************************** cs01 : ok=2 changed=0 unreachable=0 failed=0
It does not matter if the play fails to shut down a nonexistent interface, so ignore the error.