Enabling Network Automation using NTC-Ansible

A couple of months ago I wrote about Ansible and how it can enhance network automation. I also did a (very) little multi-vendor lab to show an example of what can be achieved with this tool.

Today I’m gonna write about an interesting project called ntc-ansible which exploits Ansible awesomeness into networks.


It’s an Open Source project launched by Jason Edelman (LinkedIn page, Blog) and his new-founded company Network to Code (it couldn’t have a better name 😀 ). You can find and download it here. It’s mainly composed by 2 module: ntc_show_commands and ntc_config_commands.


The problem is this: most of the network equipments, doesn’t matter the vendor who made them, don’t return structured data like XML or JSON back from show commands but just simple text. This can be painful if you want to automate some task, like gaining devices’ inventory information. A possible solution could be writing a Python module that would connect to your devices using SSH API, execute commands, retrieve output and, finally, parse the output using Regular Expressions. This is OK, but it’s a lenghty and not always simple procedure.

Ntc-ansible want to simplify all the above.

“ntc_show_commans is a multi-vendor module that can automate converting raw text from show commands into structured data, namely JSON.”

How? This module exploits another interesting tool called TextFSM which is a “Python module that implements a template based state machine for parsing semi-formatted text.” Basically, it takes two inputs, a raw file containing a show command output and a well defined template, and it returns a list of records that contains the data parsed from the text.

So, using ntc-ansible’s ntc_show_commands module we can write a simple Ansible playbook to obtain structured data from network devices in an easy way. Then, we can also use those data to do whatever we want (we’ll see an example of this later on this post).


- ntc_show_command:
    command:'show vlan'
    host:{{ inventory_hostname }}
    username:{{ username }}
    password:{{ password }}


This module enables us to write commands to devices that don’t have API. This can be done in two ways: specifying a list of commands or passing a file containing the commands to be executed. This is an useful module and later I’ll show an example of how this can help to do some auto-remediation.


# write vlan data
- ntc_config_command:
    connection: ssh
    platform: cisco_nxos
      - vlan 10
      - name vlan_10
      - end
    host: "{{ inventory_hostname }}"
    username: "{{ username }}"
    password: "{{ password }}"
    secret: "{{ secret }}"

# write config from file
- ntc_config_command:
    connection: ssh
    platform: cisco_nxos
    commands_file: "dynamically_created_config.txt"
    host: "{{ inventory_hostname }}"
    username: "{{ username }}"
    password: "{{ password }}"
    secret: "{{ secret }}"

Auto-Remediation with NTC-Ansible

The above introduction is far to be enough to let you understand how this tool really works and what it really does. So, in this section we’ll move together across all the steps needed to implement a simple auto-remediation.


A common routine task is to take a network node offline gracefully. This can be done prepending AS_PATH in BGP. Let’s imagine something happened into our network requiring us to drain traffic from a device (like a fan problem or something else). We’ll exploit ntc-ansible to write an Ansible playbook to achieve it (this can also be automatically triggered to be fully automated, but let’s focus on the playbook for now).

A common BGP prepending configuration looks like this (actually this is the one I have configured on my virtual lab):

route-map prepend permit 10
set as-path prepend last-as 3

router bgp 10
neighbor remote-as 20
neighbor route-map prepend out
neighbor remote-as 30
neighbor route-map prepend out

So, we’ll need to write a template extracting BGP neighbors information (from the show ip bgp summary command). We’ll achieve this using the ntc_show_commands module. Then, we’ll need to generate the proper configuration and to push it to the device using the ntc_config_commands module.

But first, we have to write a template for our show command.

TextFSM Template

The template file consists of two top level sections.

  • The Value definitions, which describe the columns of data to extract.
  • One or more State definitions, describing the various states of the engine whilst parsing data. A set of rules is defined in order to perform the parsing. The first and mandatory State is Start.

You can find a great guide on how to write one here.

My show ip bgp summary output looks like this:

BGP router identifier, local AS number 10
BGP table version is 21, main routing table version 21
7 network entries using 819 bytes of memory
8 path entries using 416 bytes of memory
3/2 BGP path/bestpath attribute entries using 372 bytes of memory
1 BGP AS-PATH entries using 24 bytes of memory
0 BGP route-map cache entries using 0 bytes of memory
0 BGP filter-list cache entries using 0 bytes of memory
BGP using 1631 total bytes of memory
BGP activity 9/2 prefixes, 13/5 paths, scan interval 60 secs

Neighbor      V   AS   MsgRcvd   MsgSent   TblVer  InQ OutQ    Up/Down State/PfxRcd   4   20       297             435          21       0      0        06:48:44         3   4   30       409             414          21       0      0        06:45:40         0

I have highlighted the field I’m interested in. These are my Values. Now I need to define RegEx for them.

Value BGP_NEIGH (\d+\.\d+\.\d+\.\d+)
Value NEIGH_AS (\d+)

^${BGP_NEIGH}\s+\S+\s+${NEIGH_AS}.*\s\d+:\d+:\d+\s+${STATE_PFXRCD} -> Record

Regular Expressions on Value lines are used to define what we are interested in, while those inside the State are used to find the defined pattern inside each output line. We’ll save this new template inside the ./ntc_templates directory. This directory contains all the commands supported by the ntc_show_commands module as well as an index file with a list of them. Each line of this file is in this format:

cisco_ios_show_ip_bgp_summary.template, .*, cisco_ios, sh[[ow]] ip bgp sum[[mary]]

The highlited RegEx is used to select the rigth template based on the chosen command inside the playbook.


Using this template I’ll have a table with the selected values. Now I can make a first test with ntc_show_commands


  hosts: cisco_ios
  connection: local
  gather_facts: False

    - name: TEST TEMPLATE
        connection: ssh                   
        platform: cisco_ios               
        command: "show ip bgp summary"   
        host: "{{ inventory_hostname }}"  
        username: "{{ username }}"
        password: "{{ password }}"
      register: results

    - debug: var=results.response

On the same directory I have an inventory file looking like this:

[cisco_ios] username=cisco password=cisco secret=cisco

as = 10

If you are familiar with Ansible all the fields are self-explanatory. Running this we’ll obtain the following:


As we can see, the command has been correctly executed and data is well parsed in a JSON-like manner. Now we can decide to use these data to do whatever we want.

Our goal is to drain traffic from the device making it less prefered through BGP AS prepending. So, we need to configure AS prependig for every BGP neighbor. Thanks to ntc_show_commands now we know who BGP neighbors are and we can use this knowledge to build the proper configuration.

We write two other task in order to do so:

      lineinfile: dest=./drain.config line="router bgp {{ as }}\nneighbor {{ item['bgp_neigh'] }} route-map prepend out" state=present create=True
        - "{{ results.response }}"

    - name: ADDING ROUTE-MAP
      lineinfile: dest=./drain.config line="route-map prepend permit 10\nset as-path prepend last-as 3"

The first one builds the actual BGP configuration and store it inside the drain.config file. The state flagsetted to present, and the create flag, setted to True, let the playbook create a new file if it does not exist yet. Let’s run the expanded playbook now:

Senza titolo-7

If the playbook works fine, the drain.config file will appear like this:

router bgp 10
neighbor route-map prepend out
router bgp 10
neighbor route-map prepend out

route-map prepend permit 10
set as-path prepend last-as 3

At this point we write the ntc_config_commands task to configure our device:

        connection: ssh
        platform: cisco_ios
          - router bgp "{{ as }}"
        commands_file: "./drain.config"
        host: "{{ inventory_hostname }}"
        username: "{{ username }}"
        password: "{{ password }}"
        secret: "{{ secret }}"

..and now let’s run it again..

Senza titolo-9

As the screen shows, everything has been successful and we can verify it from a show ip bgp on a neighboring device (of course we could do this inside the playbook itself writing another task. Ignore BGP configuration, I’ve just built a sample lab to show ntc-ansible usage).

Senza titolo-4

..and inside red circles we can see the effect of our run.


This post wanted to be a practical introduction to ntc-ansible and a way to let me do some practice with its modules. I think this project to be really interesting and it’s also receiving lot of good feedbacks from people who are already using it in their environments.

Senza titolo-2

If you find interesting too, you can contribute writing more templates. I did it and I’m doing it right now 🙂