Bookmark this page

Writing and Running Playbooks

Objectives

  • Write a basic Ansible Playbook and run it using the automation content navigator.

Formatting an Ansible Playbook

A playbook is a text file written in YAML format, and is normally saved with a .yml extension.

Playbooks use space characters for indentation to indicate the structure of the data.

YAML does not place strict requirements on how many spaces are used for the indentation, but two basic rules apply:

  • Data elements at the same level in the hierarchy (such as items in the same list) must have the same indentation.

  • Items that are children of another item must be indented more than their parents.

Important

You can only use space characters for indentation; do not use tab characters.

YAML files contain lists and dictionaries.

Lists contains items that are all indented at the same level. An item in a YAML list starts with a single dash followed by a space:

- show version
- show interfaces
- show ip route

Dictionaries contain a set of key: value pairs indented at the same level. The colon must be followed by a space:

port: 22
delay: 60
timeout: 600

A playbook contains a list of plays. Each play is a dictionary of directives that perform operations such as naming the play, listing the hosts (managed nodes) operated on by the play, and so on. Plays also contain a tasks directive that consists of a list of tasks that the play runs.

The following playbook example contains one play with a single task.

---
- name: Network automation example
  hosts: ios
  gather_facts: false
  tasks:
    - name: Configure a Cisco IOS device
      cisco.ios.ios_config:
        lines:
            - service timestamps debug datetime msec
            - service timestamps log datetime msec localtime
            - service password-encryption
        save_when: changed

You can add blank lines in your playbooks for readability.

YAML comments begin with the number sign (#).

Playbook Syntax

A playbook usually begins with a line consisting of three dashes (---) to indicate the start of the document. It might end with three dots (...) to indicate the end of the document, although in practice this is often omitted.

Between those markers, the playbook is defined as a list of plays. Playbooks contain one or more plays. Plays must define, at a minimum, the managed nodes to target and at least one task to execute.

The first line of the previous example playbook starts with a dash and a space, to indicate the start of the list of plays, then the name of the first play is defined. The name line associates a string with the play as a label that identifies the play. Defining the name of a play is optional, but is recommended because it helps to document your playbook and makes playbook output easier to understand. This is especially useful when a playbook contains multiple plays.

---
- name: Network automation example

The next line of the play specifies the hosts on which to run the tasks that are defined in the play. The hosts line takes a host pattern as a value, such as the names of managed nodes or groups in the inventory.

  hosts: ios

After hosts, this play sets gather_facts: false to disable standard fact gathering. This is an example of a directive that affects how the play runs.

The next line contains the tasks keyword, which specifies the list of tasks that this play runs.

Tasks call modules, which perform your desired action. Modules have specific parameters that you can use to specify the action of the module.

This example play has a single task, which runs the cisco.ios.ios_config module with the lines and save_when parameters.

The lines parameter specifies the configuration lines you want to appear in the running configuration. The save_when parameter determines when to copy the running configuration to the startup configuration.

For more information on the available parameters for the cisco.ios.ios_config module, visit https://docs.ansible.com/ansible/latest/collections/cisco/ios/ios_config_module.html.

  tasks:
    - name: Configure a Cisco IOS device
      cisco.ios.ios_config:
        lines:
            - service timestamps debug datetime msec
            - service timestamps log datetime msec localtime
            - service password-encryption
        save_when: changed

The tasks line is the part of the play that lists, in order, the tasks to run against the managed nodes.

Important

The order in which the plays and tasks are listed in a playbook is important, because Ansible runs plays in order from top to bottom.

If a parameter is set to its default value, you do not have to specify it in the play. Although, you can still specify a parameter for documentation purposes and to clarify the desired outcome of the play for anyone else who reads the playbook.

For example, the following play specifies the match and replace parameters, even though their value is the default value.

  tasks:
    - name: Configure a Cisco IOS device
      cisco.ios.ios_config:
        lines:
            - service timestamps debug datetime msec
            - service timestamps log datetime msec localtime
            - service password-encryption
        match: line
        replace: line
        save_when: changed

Network Automation Modules

Several types of network automation modules are available.

Facts modules

Ansible facts are variables that are discovered by Ansible on a managed node by using facts modules. Ansible uses the setup module as the default facts module. You enable it by specifying gather_facts: true (or gather_facts: yes).

Facts modules use an _facts suffix. For example, the following are facts modules:

  • cisco.ios.ios_facts

  • cisco.nxos.nxos_facts

  • junipernetworks.junos.junos_facts

  • arista.eos.eos_facts

Important

When creating playbooks for network automation, always specify gather_facts: false (or gather_facts: no) and use the vendor-specific _facts modules if the managed node OS supports a specific _facts module.

Command modules

Command modules allow you to send commands to network devices. You might use these modules to create playbooks that automate the gathering of statistics, resetting interfaces, or troubleshooting a commonly occurring issue.

Command modules use an _command suffix. For example, the following are command modules:

  • cisco.ios.ios_command

  • cisco.nxos.nxos_command

  • junipernetworks.junos.junos_command

  • arista.eos.eos_command

Configuration modules

Configuration modules send configuration commands to network devices.

Configuration modules use the _config suffix, such as the cisco.ios.ios_config module.

Important

Configuration modules change the running configuration, but do not necessarily affect the startup configuration. If you want your changes to be persistent, then you must check documentation for the module that you are using.

Most Cisco configuration modules include the save_when option with never as the default value. With this value, the module never copies the running configuration to the startup configuration. Set a different value for the save_when option if you want the module to copy the running configuration to the startup configuration.

Like Cisco configuration modules, the Arista configuration module includes the save_when option with never as the default value.

The junipernetworks.junos.junos_config module uses the confirm option with 0 as the default value. With this value, the module immediately commits the changes so that the changes are persistent.

Resource modules

Network resource modules simplify and standardize the management of network devices across vendors.

Network resource modules enable reading and configuring specific configuration sections, such as VLANs, interfaces, Simple Network Management Protocol (SNMP), Open Shortest Path First (OSPF), ACLs, and others. Network resource modules provide a consistent experience across different network devices.

For more information on network resource modules, visit https://docs.ansible.com/ansible/latest/network/user_guide/network_resource_modules.html

For example, you can use the following network resource modules to configure Layer 3 interfaces:

  • cisco.ios.ios_l3_interfaces

  • arista.eos.eos_l3_interfaces

  • junipernetworks.junos.junos_l3_interfaces

And the following are network resource modules you can use to configure ACLs:

  • cisco.ios.ios_acls

  • arista.eos.eos_acls

  • junipernetworks.junos.junos_acls

Important

As with their associated configuration modules, Cisco and Arista resource modules change the running configuration, but do not change the startup configuration. You might use handlers to make persistent changes.

Using Handlers to Make Persistent Changes to Managed Network Nodes

Handlers are tasks that respond to a notification triggered by other tasks. Tasks only notify their handlers when the status of a task evaluates as changed. Each handler is triggered by its name.

If no task notifies the handler by name, then the handler does not run. If one or more tasks notify the handler, then the handler only runs a single time after all other tasks in the play have completed. Because handlers are tasks, you can use the same modules in handlers that you use for any other task.

For Linux hosts, you normally use handlers to reboot hosts and restart services. For managed network nodes, you can use handlers to save the running configuration to the startup configuration.

Important

Use unique names for your handlers. When multiple handlers are defined with the same name, only the last handler defined with the shared name runs.

Handlers can be considered as inactive tasks that only get triggered when explicitly invoked using a notify statement. The following playbook demonstrates saving the running configuration to the startup configuration for Cisco managed nodes. The ios_save_changes handler is only triggered if the cisco.ios.ios_banner makes a change.

---
- name: Configure login banners
  hosts: ios
  gather_facts: false
  tasks:
    - name: Configure banner on IOS devices 1
      cisco.ios.ios_banner:
        banner: login
        text: Managed by Ansible
        state: present
      notify: ios_save_changes 2

  handlers: 3
    - name: ios_save_changes 4
      cisco.ios.ios_config: 5
        save_when: always 6

1

The task that notifies the handler.

2

The notify statement indicates that the task needs to trigger a handler.

3

The handlers keyword indicates the start of the list of handler tasks.

4

The name of the handler. This name must match the name invoked by tasks.

5

The module to use for the handler.

6

By using the always value for the save_when option, if this handler is triggered, then the module always copies the running configuration to the startup configuration.

Important

Because handlers run after the completion of all other tasks in the play, any playbook error can stop a handler from running. Even after you fix a playbook error, you might find that a handler does not run. A task that made a change in a previous playbook run might not change, and therefor not trigger a handler, on additional playbook runs.

One way to force a task to trigger a handler is to temporarily add the changed_when: true line to a task, such as in the following example:

- name: Configure banner on IOS devices
  cisco.ios.ios_banner:
    banner: login
    text: Managed by Ansible
    state: present
  changed_when: true
  notify: ios_save_changes

After making the temporary change, run the playbook and verify that the handler runs. If successful, then remove the changed_when: true line from the previously modified task.

Finding Modules for Tasks

Modules are the tools that plays use to perform tasks. Hundreds of modules have been written that perform different tasks.. You can usually find a tested, special-purpose module that does what you need, often as part of the default automation execution environment.

Ansible Core 2.11 and later package the modules that you use for tasks in sets called Ansible Content Collections. Each Ansible Content Collection contains a selection of related Ansible content, including modules and documentation.

The ansible-core package provides a single Ansible Content Collection named ansible.builtin. These modules are always available. Visit https://docs.ansible.com/ansible/latest/collections/ansible/builtin/ for a list of modules contained in the ansible.builtin collection.

In addition, the default automation execution environment used by ansible-navigator in Red Hat Ansible Automation Platform 2.3, ee-rhel8-supported, includes a number of other Ansible Content Collections. You can browse these collections by running the ansible-navigator collections command.

In the ansible-navigator interactive text-based user interface (TUI), you can type a colon (:) followed by the word collections to list the available collections.

Figure 2.9:

The ansible-navigator TUI menu

Type a colon (:) followed by the line number of a collection to get more information about it, including the list of modules and other Ansible content that it provides. You can do the same thing with the line number of a module to get documentation about that module. Press Esc to go back to the preceding list.

Figure 2.10:

The ansible-navigator TUI collections menu

Note

The following list of resources provides additional Ansible Content Collections:

Your organization might also install and configure a private automation hub to provide more collections.

These collections can be installed in the collections directory of your Ansible project.

Red Hat does not provide formal support for community Ansible Content Collections; it only supports Red Hat Certified Ansible Content Collections.

Modules are named using a fully qualified collection name (FQCN). This allows the same name to be used for different modules in two Ansible Content Collections without causing conflicts.

For example, the copy module provided by the ansible.builtin Ansible Content Collection has ansible.builtin.copy as its FQCN.

Important

To avoid errors, it is a good practice to always use FQCNs in your playbooks.

Most modules are idempotent, which means that they can be run safely multiple times, and if the system is already in the correct state, they do nothing.

For example, if you run the play from the preceding example a second time, it should report no changes.

Running Playbooks

When you run a playbook, output is generated to show the play and tasks being executed. The output also reports the results of each executed task.

The following example shows the content of the netautomate.yml playbook, the command to run that playbook, and the result of running it.

[user@host networkautomation]$ cat netautomate.yml
---
- name: Network automation example
  hosts: ios
  gather_facts: false
  tasks:
    - name: Configure a Cisco IOS device
      cisco.ios.ios_config:
        lines:
            - service timestamps debug datetime msec
            - service timestamps log datetime msec localtime
            - service password-encryption
        save_when: changed
[user@host networkautomation]$ ansible-navigator run \
netautomate.yml -m stdout

PLAY [Network automation example] **********************************************

TASK [Configure a Cisco IOS device] ********************************************
[WARNING]: To ensure idempotency and correct diff the input configuration lines
should be similar to how they appear if present in the running configuration on
device
changed: [switch1.lab.example.com]
changed: [switch2.lab.example.com]

PLAY RECAP *********************************************************************
switch1.lab.example.com     : ok=0    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
switch2.lab.example.com     : ok=0    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

The name for each play and task is displayed when the playbook is run.

Notice also that the status of the Configure a Cisco IOS device task is changed for each managed node. This means that the task changed something on that host to ensure that its specification was met.

Increasing Output Verbosity

The default output provided by the ansible-navigator run command does not provide detailed task execution information. The -v option provides additional information, with up to four levels.

Table 2.1. Configuring the Output Verbosity of Playbook Execution

OptionDescription
-v Displays task results.
-vv Displays task results and task configuration.
-vvv Displays extra information about connections to managed nodes.
-vvvv Adds extra verbosity options to the connection plug-ins, including users who executed the scripts on the managed nodes, and what scripts have been executed.

Syntax Verification

Before executing a playbook, it is good practice to validate its syntax. You can use the ansible-navigator run --syntax-check command to validate the syntax of a playbook.

The following example shows the successful syntax validation of a playbook.

[user@host networkautomation]$ ansible-navigator run \
netautomate.yml -m stdout --syntax-check
playbook: /home/user/networkautomation/netautomate.yml

When syntax validation fails, a syntax error is reported. The output also includes the approximate location of the syntax issue in the playbook. The following example shows the failed syntax validation of a playbook where the space separator is missing after the name attribute for the play.

[user@host networkautomation]$ ansible-navigator run \
netautomate.yml -m stdout --syntax-check
ERROR! We were unable to read either as JSON nor YAML, these are the errors we got from each:
JSON: Expecting value: line 1 column 1 (char 0)

Syntax Error while loading YAML.
  mapping values are not allowed in this context

The error appears to be in '/home/user/networkautomation/netautomate.yml': line 6, column 27, but may
be elsewhere in the file depending on the exact syntax problem.

The offending line appears to be:

    - name:Configure a Cisco IOS device
      cisco.ios.ios_config:
                          ^ here
Please review the log for errors.

Executing a Dry Run

You can use the --check option to run a playbook in check mode, which performs a "dry run" of the playbook. This causes Ansible to report what changes would have occurred if the playbook were executed, but does not make any actual changes to managed nodes.

The following example shows the dry run of the example playbook. In this case, the dry run reports that the task would make a change on the managed host.

[user@host networkautomation]$ ansible-navigator run \
netautomate.yml -m stdout --check

PLAY [Network automation example] **********************************************

TASK [Configure a Cisco IOS device] ********************************************
[WARNING]: Skipping command copy running-config startup-config due to
check_mode.  Configuration not copied to non-volatile storage
[WARNING]: To ensure idempotency and correct diff the input configuration lines
should be similar to how they appear if present in the running configuration on
device
changed: [switch1.lab.example.com]
changed: [switch2.lab.example.com]

PLAY RECAP *********************************************************************
switch1.lab.example.com     : ok=0    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
switch2.lab.example.com     : ok=0    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Idempotency

In general, tasks in Ansible Playbooks are idempotent, and it is safe to run a playbook multiple times. If the targeted managed nodes are already in the correct state, no changes should be made.

For example, assume that the playbook from the previous example is run again:

PLAY [Network automation example] **********************************************

TASK [Configure a Cisco IOS device] ********************************************
ok: [switch1.lab.example.com]
ok: [switch2.lab.example.com]

PLAY RECAP *********************************************************************
switch1.lab.example.com     : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
switch2.lab.example.com     : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

This time, all tasks passed with status ok and no changes were reported.

Revision: do457-2.3-7cfa22a