Bookmark this page

Controlling Tasks with Loops and Conditions

Objectives

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.

Simple Conditions

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

Compound when Statements

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=0

Programmatic Style

Conditionals 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"

Ansible Style

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'

Applying Tasks to Objects

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.

DirectiveDescription
loopIterates a task over a list of items. List elements may be mappings.
with_nestedTakes 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_sequenceGenerates a sequence of items in increasing numerical order. Can take start and end arguments that have a decimal, octal, or hexadecimal integer value.
with_dictTakes a hash (a dictionary) and loops through its elements, returning item.key and item.value.

Simple Loops

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 }}"

Nested Loops

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 }}"

Looping Over Dict With Conditions

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=0

Normal Task Processing

A 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=1

The rest of the tasks in the play are skipped for this host because the first task failed.

Ignoring Errors on a Task

---
- 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}
...ignoring

TASK [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.

Revision: do457-2.5-4693601