Now that we’ve covered how TextFSM work and how it can be used to record useful information from device outputs, it’s time to move our focus on how we use TextFSM templates in Ansible.

Using NTC-Ansible’s ntc_show_command module, we’re able to _“get structured data from devices that don’t have an API”. _As I’ve touched on in previous posts, it does this through the use of TextFSM templates which can be found here. I’ll cover how to write your own templates in a future post but for now I’ll focus on how to use the existing templates.

Core Module

Ansible has Core modules which support a wide variety of network vendors and their product lines. The problem is that if the vendors’ product(s) do not return the data in a structured manner then all that is returned is a block of text which you need to parse through a script.

Let’s take a look at the playbook below which uses Ansible’s ios_command module. (See my Ansible Playbook Structure post for information on where I store my variables and hostnames.)

- hosts: gns3
  gather_facts: no
  connection: local

  tasks:
  - name: DEFINE PROVIDER
	set_fact:
	  provider:
		host: ""
		username: ""
		password: ""

  - name: RUN 'show cdp neighbors'
	ios_command:
	  provider: ""
	  commands:
		- show cdp neighbors
	register: neighbors

  - name: DEBUG 'show cdp neighbors'
	debug: var=neighbors

And now let’s see what output is produced when we run this playbook:

will@ubuntu:~/all/Ansible$ ansible-playbook -i hosts.ini show_version.yml 

PLAY [gns3] ********************************************************************

TASK [DEFINE PROVIDER] *********************************************************
ok: [10.255.0.254]

TASK [RUN 'sho cdp neighbors'] *************************************************
ok: [10.255.0.254]

TASK [DEBUG 'show cdp neighbors'] **********************************************
ok: [10.255.0.254] => {
    "neighbors": {
        "changed": false, 
        "stdout": [
            "Capability Codes: R - Router, T - Trans Bridge, B - Source Route Bridge
            S - Switch, H - Host, I - IGMP, r - Repeater
            Device ID        Local Intrfce     Holdtme    Capability  Platform  Port ID
            R3.lab           Fas 1/0            162         R S I     3745      Fas 0/1
            R2.lab           Fas 0/1            162         R S I     3745      Fas 0/0"
        ], 
        "stdout_lines": [
            [
                "Capability Codes: R - Router, T - Trans Bridge, B - Source Route Bridge", 
                "                  S - Switch, H - Host, I - IGMP, r - Repeater", 
                "", 
                "Device ID        Local Intrfce     Holdtme    Capability  Platform  Port ID", 
                "R3.lab           Fas 1/0            162         R S I     3745      Fas 0/1", 
                "R2.lab           Fas 0/1            162         R S I     3745      Fas 0/0"
            ]
        ], 
        "warnings": []
    }
}

PLAY RECAP *********************************************************************
10.255.0.254               : ok=3    changed=0    unreachable=0    failed=0

As mentioned above, the problem is that the important information (e.g the neighbor’s name, local interface and remote interface) is output as a block of text. If we wanted to use this information elsewhere in our playbook (e.g to update the router’s interface descriptions to contain the neighbor’s hostname), we’d need to write a script to extract the information and store it as a variable which would be quite time consuming. Thankfully NTC-Ansible solves this issue.

ntc_show_command Module

Let’s now modify the above playbook so that instead of using the ios_command module, we use the ntc_show_command module which will in turn use the cisco_ios_show_cdp_neighbors TextFSM template:

- hosts: gns3
  gather_facts: no
  connection: local

  tasks:
  - name: NTC_SHOW 'show cdp neighbors'
    ntc_show_command:
      connection: ssh
      platform: cisco_ios
      command: "show cdp neighbors"
      host: ""
      username: ""
      password: ""
    register: ntc_neighbors

  - name: DEBUG NTC_SHOW 'show cdp neighbors'
    debug: var=ntc_neighbors

And now let’s see what output is produced when we run this playbook:

will@ubuntu:~/all/Ansible$ ansible-playbook -i hosts.ini show_version.yml 

PLAY [gns3] ********************************************************************

TASK [NTC_SHOW 'show cdp neighbors'] *******************************************
ok: [10.255.0.254]

TASK [DEBUG NTC_SHOW 'show cdp neighbors'] *************************************
ok: [10.255.0.254] => {
    "ntc_neighbors": {
        "changed": false, 
        "response": [
            {
                "local_interface": "Fas 1/0", 
                "neighbor": "R3.lab", 
                "neighbor_interface": "Fas 0/1"
            }, 
            {
                "local_interface": "Fas 0/1", 
                "neighbor": "R2.lab", 
                "neighbor_interface": "Fas 0/0"
            }
        ], 
        "response_list": []
    }
}

PLAY RECAP *********************************************************************
10.255.0.254               : ok=2    changed=0    unreachable=0    failed=0

Here we see that ntc_show_command has output the important pieces of information in a structured fashion so that we can now use them as variables in our playbook.

As a basic example, let’s add the following lines to the bottom of our playbook:

  - name: Display debug message
    debug: msg="Devices  and  are connected to "

Don’t worry if you find the ntc_neighbors.response[0].neighbor  and ntc_neighbors.response[1].neighbor  variables confusing, I will cover them in a future post.

And now let’s rerun our playbook:

will@ubuntu:~/all/Ansible$ ansible-playbook -i hosts.ini show_version.yml 

PLAY [gns3] ********************************************************************

TASK [NTC_SHOW 'show cdp neighbors'] *******************************************
ok: [10.255.0.254]

TASK [DEBUG NTC_SHOW 'show cdp neighbors'] *************************************
ok: [10.255.0.254] => {
    "ntc_neighbors": {
        "changed": false, 
        "response": [
            {
                "local_interface": "Fas 1/0", 
                "neighbor": "R3.lab", 
                "neighbor_interface": "Fas 0/1"
            }, 
            {
                "local_interface": "Fas 0/1", 
                "neighbor": "R2.lab", 
                "neighbor_interface": "Fas 0/0"
            }
        ], 
        "response_list": []
    }
}

TASK [Display debug message] ***************************************************
ok: [10.255.0.254] => {
    "msg": "Devices R3.lab and R2.lab are connected to 10.255.0.254"
}

PLAY RECAP *********************************************************************
10.255.0.254               : ok=3    changed=0    unreachable=0    failed=0

While this debug message isn’t particularly useful and a loop should have really ben used to extract the data, I used this basic example as it is the simplest way to demonstrate the power of the ntc_show_command module. I will provide more useful examples in future posts, but in the meantime please take a look at Gabriele’s fantastic post on using the module in a real life situation.


As always, if you have any questions or have a topic that you would like me to discuss, please feel free to post a comment at the bottom of this blog entry, e-mail at will@oznetnerd.com, or drop me a message on Reddit (OzNetNerd).

Note: The opinions expressed in this blog are my own and not those of my employer.

Leave a comment