Today I'm going to show you an inbuilt debugger tool that you can use in Ansible to help troubleshoot your tasks and playbooks. As per the Ansible docs, the debugger tool allows you to:

… fix errors during execution instead of editing your Playbook and running it again to see if your change worked. You have access to all of the features of the debugger in the context of the task …

In other words, it is similar to the Python PDB tool (minus a few features) in that you can step through your code at various points and see your variables, etc., each time.

Note: Our example will be based on the following Playbook, which has a simple mistake added!

---
- name: Create Interfaces
  hosts: spine
  gather_facts: false
  debugger: on_failed

  tasks:
    - name: Create L3 interfaces
      cisco.nxos.nxos_l3_interfaces:
        config:
        - name: "{{ item.name }}"
          ipv4:
            - address: "{{ item.add }}"
        state: "merged"
      loop: "{{ interfaces }}"

Enabling the Debugger

To use the debugger, we can enable it in a number of ways, such as via the Ansible config file, via an environment variable, or at a Play or Task level. In addition, you can also define when it will be invoked, i.e., always, on_failed, etc. (full details here).

For our short example, we will enable this at a Play level:

---
- name: Create Interfaces
  hosts: spine
  gather_facts: false
  debugger: on_failed 
  …

At the point we run our Playbook, we will be placed into the debugger shell:

$ ansible-playbook -i inventory.yaml playbooks/pb_create_interfaces.yaml

PLAY [Create Interfaces] *****************************************************************************************************************************

TASK [Create L3 interfaces] **************************************************************************************************************************

fatal: [spine1-nxos]: FAILED! => 
...
    The offending line appears to be:
      tasks:
        - name: Create L3 interfaces
          ^ here

[spine1-nxos] TASK: Create L3 interfaces (debug)> 

At this point, we have a number of options, such as printing the values, changing the values and also stepping to the next step in the Play. Let's look at some examples...

Printing Values

When it comes to print values, we have a few options; we can print the:

  • Task name via p task
  • Task input arguments via p task.args
  • Task variables via p task_vars.

Here's an example:

# Printing the task name.
[spine1-nxos] TASK: Create L3 interfaces (debug)> p task
TASK: Create L3 interfaces

# Printing the task arguments.
[spine1-nxos] TASK: Create L3 interfaces (debug)> p task.args
{'config': [{'ipv4': [{'address': '{{ item.add }}'}],
             'name': '{{ item.name }}'}],
 'state': 'merged'}

# Printing the variables
[spine1-nxos] TASK: Create L3 interfaces (debug)> p task_vars
...
 'groups': {'all': ['spine1-nxos',
                    'spine2-nxos',
                    'leaf1-ios',
                    'leaf2-ios',
                    'leaf3-junos',
                    'leaf4-junos',
                    'leaf5-eos',
                    'leaf6-eos'],
            'leaf': ['leaf1-ios',
                     'leaf2-ios',
                     'leaf3-junos',
                     'leaf4-junos',
                     'leaf5-eos',
                     'leaf6-eos'],
...
  'interfaces': [{'addr': '100.1.1.101/32', 'name': 'loopback111'},
                         {'addr': '100.1.1.102/32', 'name': 'loopback102'}],
          'inventory_dir': '/home/rick/development/ansible-debugging',
          'inventory_file': '/home/rick/development/ansible-debugging/inventory.yaml',
          'inventory_hostname': 'spine1-nxos',
          'inventory_hostname_short': 'spine1-nxos',
          'omit': '__omit_place_holder__f4887f2c17be2705a5b03e8aea973407629f6113',
          'play_hosts': ['spine1-nxos', 'spine2-nxos'],
          'playbook_dir': '/home/rick/development/ansible-debugging/playbooks',
          'role_names': []}}

# Printing a key from within the variables.
[spine1-nxos] TASK: Create L3 interfaces (debug)> p task_vars["interfaces"]
[{'addr': '100.1.1.101/32', 'name': 'loopback111'},
 {'addr': '100.1.1.102/32', 'name': 'loopback102'}]

From the output, we can see that our interface key should be addr instead of add within our task loop.

Changing Values

Both the variables and arguments can be changed inline. To do so, select the key and then provide the new value, i.e.: task.args[key] = value. This is exactly how you would set a key in a dictionary within Python, which makes sense since Ansible is just running Python under the hood.

Now, let's change the task input argument to the correct value:

# Change the input arguments.
spine1-nxos] TASK: Create L3 interfaces (debug)> task.args["config"][0]["ipv4"][0]["address"] = "{{ item.addr }}"

# Confirm the change.
[spine1-nxos] TASK: Create L3 interfaces (debug)> p task.args
{'config': [{'ipv4': [{'address': '{{ item.addr }}'}],
             'name': '{{ item.name }}'}],
 'state': 'merged'}

Stepping Through the Play

When it comes to stepping through the tasks, there are two main options. We can either:

  • run the task again via redo or r; or
  • continue and use the next task via continue or c.

If we return to our example and redo our task, we will see that spine1 is configured with the required interfaces as we have corrected the input argument. We've not done this for spine2, which is why when we then run continue, we see another error.

[spine1-nxos] TASK: Create L3 interfaces (debug)> redo
ok: [spine1-nxos] => (item={'name': 'loopback102', 'addr': '100.1.1.102/32'})
ok: [spine1-nxos] => (item={'name': 'loopback111', 'addr': '100.1.1.101/32'})
...

fatal: [spine2-nxos]: FAILED! => 
...
    The offending line appears to be:
      tasks:
        - name: Create L3 interfaces
          ^ here
          
[spine2-nxos] TASK: Create L3 interfaces (debug)> continue

PLAY RECAP *******************************************************************************************************************************************

spine1-nxos                : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
spine2-nxos                : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0 

That wraps up this post on the Ansible debugger. It's definitely a useful little tool when needing to debug your Ansible Playbooks, and it certainly warrants a place in my network automation toolbox!

Ready to Master Network Automation? Start Your Journey Today!
Our membership provides:
  • Full deep-dive course library (inc. Batfish, pyATS, Netmiko)
  • Code repositories inc. full course code, scripts and examples
  • 24x7 multi-vendor labs (Arista, Cisco, Juniper)
  • Private online community
  • Live monthly tech sessions
  • Access to tech session library

Join Now ➜