Leveraging NTC-Templates for Network Automation

Leveraging NTC-Templates for Network Automation

NTC-Templates is a powerful Python library that provides a collection of TextFSM templates designed specifically for parsing network device CLI outputs. It’s an essential tool in the network automation toolkit that helps bridge the gap between raw CLI output and structured data that automation scripts can easily process.

Why Use NTC-Templates?

  1. Standardized Output: NTC-Templates provides pre-built templates for common network device commands across multiple vendors, providing a consistent parsing methodology for vendor-specific CLI output. 
  2. Time-Saving: Instead of writing complex regex patterns or custom parsers for each command, you can use these battle-tested templates. 
  3. Vendor-Agnostic: Supports multiple network vendors including Cisco, Juniper, Arista, and more. 
  4. Community-Driven: Being open source, it benefits from community contributions (+283 contributors) and improvements. 
  5. Integration-Ready: Works seamlessly with popular network automation tools like Ansible, Netmiko, Scrapli, NAPALM, and custom Python scripts.

Who Is It For? 

NTC-Templates is ideal for: 

  • Network Engineers transitioning to automation
  • Network Automation Engineers
  • DevOps Engineers working with network infrastructure
  • Anyone who needs to parse device CLI output into structured data

When to Use NTC-Templates

Use NTC-Templates when: 

  1. You need to parse raw CLI output from network devices into structured data
  2. You want to standardize how you handle different vendor outputs
  3. You need to integrate CLI output parsing into a larger automation workflow
  4. You are working with devices that do not support modern APIs (NETCONF, gNMI, or REST)

How to Use NTC-Templates

1. Installation

# mkdir example_code
# python3 -m venv .venv 
# source .venv/bin/activate
# pip install NTC-Templates 

2. Basic Usage

Here is a simple example of parsing a Cisco device’s “show version” output: 

  • Below is an example of connecting to a Cisco IOS device and a portion of the “show version” output. 
  • For demonstration purposes, the raw_output content is specified in the script, but in a production network, the output of the command would be received from connecting to the device. 
  • Use device_type=“cisco_ios” and command=“show_version” to correctly parse the output. 

For a list of vendors and commands, take a look at https://github.com/networktocode/NTC-Templates/tree/master/ntc_templates/templates, where the first part of the filename is the vendor (cisco_ios, arista_eos) and the second part is the command.

from ntc_templates.parse import parse_output

# Raw CLI output from a Cisco device
raw_output = """
Cisco IOS Software, C3560 Software (C3560-IPSERVICESK9-M), Version 12.2(55)SE7, RELEASE SOFTWARE (fc1)
Technical Support: http://www.cisco.com/techsupport
Copyright (c) 1986-2013 by Cisco Systems, Inc.
Compiled Mon 28-Jan-13 10:10 by prod_rel_team
"""

# Parse the output using the appropriate template
parsed_output = parse_output(
    platform="cisco_ios",
    command="show version",
    data=raw_output
)

# The output is now structured data
print(parsed_output)

The output is structured JSON data:

# python show_version_example.py 
[{'software_image': 'C3560-IPSERVICESK9-M', 'version': '12.2(55)SE7', 'release': 'fc1', 'rommon': '', 'hostname': '', 'uptime': '', 'uptime_years': '', 'uptime_weeks': '', 'uptime_days': '', 'uptime_hours': '', 'uptime_minutes': '', 'reload_reason': '', 'running_image': '', 'hardware': [], 'serial': [], 'config_register': '', 'mac_address': [], 'restarted': ''}]

3. Parsing Interface Information

from ntc_templates.parse import parse_output

# Sample raw output from 'show interfaces' command from an IOS device
raw_interface_output = """
GigabitEthernet1/0/1 is up, line protocol is up
  Hardware is Gigabit Ethernet, address is 0012.43fd.2b01 (bia 0012.43fd.2b01)
  Description: UPLINK TO CORE
  Internet address is 10.1.1.1/24
  MTU 1500 bytes, BW 1000000 Kbit/sec, DLY 10 usec
  reliability 255/255, txload 1/255, rxload 1/255
  Encapsulation ARPA, loopback not set
  Keepalive set (10 sec)
  Full-duplex, 1000Mb/s, media type is 10/100/1000BaseTX
  input flow-control is off, output flow-control is unsupported
  ARP type: ARPA, ARP Timeout 04:00:00
  Last input 00:00:00, output 00:00:00, output hang never
  Last clearing of "show interface" counters never
  Input queue: 0/75/0/0 (size/max/drops/flushes); Total output drops: 0
  Queueing strategy: fifo
  Output queue: 0/40 (size/max)
  5 minute input rate 0 bits/sec, 0 packets/sec
  5 minute output rate 0 bits/sec, 0 packets/sec
     1234567 packets input, 123456789 bytes, 0 no buffer
     Received 1234567 broadcasts (0 IP multicasts)
     0 runts, 0 giants, 0 throttles
     0 input errors, 0 CRC, 0 frame, 0 overrun, 0 ignored
     0 watchdog, 0 multicast, 0 pause input
     0 input packets with dribble condition detected
     1234567 packets output, 123456789 bytes, 0 underruns
     0 output errors, 0 collisions, 0 interface resets
     0 unknown protocol drops
     0 babbles, 0 late collision, 0 deferred
     0 lost carrier, 0 no carrier, 0 pause output
     0 output buffer failures, 0 output buffers swapped out
"""

# Parse the output
interfaces = parse_output(
    platform="cisco_ios",
    command="show interfaces",
    data=raw_interface_output
)

# The parsed output will be a list of dictionaries with structured data
for interface in interfaces:
    print(f"Interface: {interface['interface']}")
    print(f"Status: {interface['link_status']}")
    print(f"Protocol: {interface['protocol_status']}")
    print(f"IP Address: {interface.get('ip_address', 'N/A')}")
    print(f"Description: {interface.get('description', 'N/A')}")
    print("---")

Here is the output:

# python show_interface_example.py 
Interface: GigabitEthernet1/0/1
Status: up
Protocol: up
IP Address: 10.1.1.1
Description: UPLINK TO CORE
---

4. Routing Table Output Example

from ntc_templates.parse import parse_output

raw_route_output = """
Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP
       D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area
       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
       E1 - OSPF external type 1, E2 - OSPF external type 2
       i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2
       ia - IS-IS inter area, * - candidate default, U - per-user static route
       o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP
       a - application route
       + - replicated route, % - next hop override

Gateway of last resort is 192.168.1.1 to network 0.0.0.0

S*    0.0.0.0/0 [1/0] via 192.168.1.1
      10.0.0.0/8 is variably subnetted, 2 subnets, 2 masks
C        10.1.1.0/24 is directly connected, GigabitEthernet1/0/1
L        10.1.1.1/32 is directly connected, GigabitEthernet1/0/1
      192.168.1.0/24 is variably subnetted, 2 subnets, 2 masks
C        192.168.1.0/24 is directly connected, GigabitEthernet1/0/2
L        192.168.1.2/32 is directly connected, GigabitEthernet1/0/2
"""

routes = parse_output(
    platform="cisco_ios",
    command="show ip route",
    data=raw_route_output
)

for route in routes:
    print(f"Network: {route['network']}")
    print(f"Protocol: {route['protocol']}")
    print(f"Next Hop: {route.get('next_hop', 'Directly Connected')}")
    print(f"Interface: {route.get('interface', 'N/A')}")
    print("---")

5. Available for a Diverse Set of Vendors and Commands

from ntc_templates.parse import parse_output

juniper_raw_output = """
Hostname: router1
Model: mx480
JUNOS Base OS boot [12.1R1.9]
JUNOS Base OS Software Suite [12.1R1.9]
JUNOS Kernel Software Suite [12.1R1.9]
JUNOS Crypto Software Suite [12.1R1.9]
JUNOS Routing Software Suite [12.1R1.9]
JUNOS Enterprise Software Suite [12.1R1.9]
JUNOS Online Documentation [12.1R1.9]
JUNOS Voice Services Container package [12.1R1.9]
"""

juniper_output = parse_output(
    platform="juniper_junos",
    command="show version",
    data=juniper_raw_output
)

for version in juniper_output:
    print(f"Hostname: {version.get('hostname', 'N/A')}")
    print(f"Model: {version.get('model', 'N/A')}")
    print(f"JUNOS Version: {version.get('version', 'N/A')}")
    print("---")

Here is an example of an Arista 7050 device:

from ntc_templates.parse import parse_output

arista_raw_output = """
Arista DCS-7050QX-32S
Hardware version:    01.00
Serial number:       JPE12345678
System MAC address:  001c.73ff.1234

Software image version: 4.20.10M
Architecture:           i386
Internal build version: 4.20.10M-1234567.42010M
Internal build ID:      1234567-42010M
Uptime:                 12 weeks, 4 days, 2 hours and 10 minutes
"""

arista_output = parse_output(
    platform="arista_eos",
    command="show version",
    data=arista_raw_output
)

for version in arista_output:
    print(f"Model: {version.get('model', 'N/A')}")
    print(f"Serial: {version.get('serial', 'N/A')}")
    print(f"Software Version: {version.get('version', 'N/A')}")
    print("---")

Pretty neat, huh? The previous example uses snippets of command output. Let’s see how we can integrate them with popular automation tools. 

Integration with NAPALM

NTC-Templates can be easily integrated with popular automation tools, such as NAPALM.

``` requirements.txt
napalm>=4.1.0
NTC-Templates>=3.5.0
```

This example focuses on connecting to an Arista cEOS device at 172.17.0.2. The username and password for this device are both set to ‘admin’. For setup details and additional context, please refer to the “Lab Setup” documentation, which can be found at Lab Setup from the “100 Days of Nautobot” project.

#!/usr/bin/env python3
"""
Example script demonstrating NAPALM integration with NTC-Templates for Arista EOS only
"""
from typing import Dict, Optional
from napalm import get_network_driver
from ntc_templates.parse import parse_output

def get_eos_device_info(hostname: str, username: str, password: str) -> Dict:
    """
    Get Arista EOS device information using NAPALM and parse with NTC-Templates
    Args:
        hostname: Device hostname or IP
        username: Device username
        password: Device password
    Returns:
        Dictionary containing parsed device information
    """
    try:
        network_driver = get_network_driver("eos")
        optional_args = {"port": 443, "protocol": "https"}
        platform = "arista_eos"

        with network_driver(
            hostname=hostname,
            username=username,
            password=password,
            optional_args=optional_args,
            timeout=10
        ) as device:
            # Get raw CLI output
            version_output = device.cli(['show version'])
            interface_output = device.cli(['show interfaces'])

            # Parse version information
            try:
                version_info = parse_output(
                    platform=platform,
                    command="show version",
                    data=version_output['show version']
                )
            except Exception as e:
                print(f"Warning: Could not parse 'show version': {e}")
                version_info = [{"raw_output": version_output['show version']}]

            # Parse interface information
            try:
                interface_info = parse_output(
                    platform=platform,
                    command="show interfaces",
                    data=interface_output['show interfaces']
                )
            except Exception as e:
                print(f"Warning: Could not parse 'show interfaces': {e}")
                interface_info = [{"raw_output": interface_output['show interfaces']}]

            return {
                'hostname': hostname,
                'version': version_info[0] if version_info else {},
                'interfaces': interface_info
            }

    except Exception as e:
        print(f"Error connecting to {hostname}: {str(e)}")
        return {}

def main() -> None:
    """
    Main function to demonstrate NAPALM and NTC-Templates integration for Arista EOS
    """
    # Device information (update as needed)
    device = {
        'hostname': '172.17.0.2',
        'username': 'admin',
        'password': 'admin',
    }

    device_info = get_eos_device_info(
        hostname=device['hostname'],
        username=device['username'],
        password=device['password']
    )

    if device_info:
        print(f"\nDevice: {device_info['hostname']}")
        print("=" * 50)

        # Display version information
        version = device_info['version']
        print("\nVersion Information:")
        print(f"  Version: {version.get('version', 'N/A')}")
        print(f"  Serial: {version.get('serial', 'N/A')}")
        print(f"  Model: {version.get('hardware', 'N/A')}")

        # Display interface information
        print("\nInterface Information:")
        for interface in device_info['interfaces']:
            print(f"\n  Interface: {interface.get('interface', 'N/A')}")
            print(f"    Status: {interface.get('link_status', 'N/A')}")
            print(f"    Protocol: {interface.get('protocol_status', 'N/A')}")
            print(f"    IP Address: {interface.get('ip_address', 'N/A')}")
            print(f"    Description: {interface.get('description', 'N/A')}")

if __name__ == "__main__":
    main()

This is the output:

Device: 172.17.0.2
==================================================

Version Information:
  Version: N/A
  Serial: N/A
  Model: N/A

Interface Information:

  Interface: Loopback0
    Status: up
    Protocol: up (connected)
    IP Address: 10.0.0.1/32
    Description: Loopback for Router ID

  Interface: Loopback100
    Status: up
    Protocol: up (connected)
    IP Address: 192.168.101.100/24
    Description: Mimic OSPF Area 0.0.0.2 loopback

  Interface: Management0
    Status: up
    Protocol: up (connected)
    IP Address: 172.17.0.2/16
    Description: 

Best Practices

1. Error Handling: Always implement proper error handling when parsing CLI output:

try:
    parsed_output = parse_output(
        platform="cisco_ios",
        command="show version",
        data=raw_output
    )
except Exception as e:
    print(f"Error parsing output: {e}

2. Template Selection: Use the correct platform and command combination. Please reference https://github.com/networktocode/NTC-Templates/tree/master/ntc_templates/templates for the most updated vendor and command list.

3. Output Validation: Always validate the parsed output before using it in your automation scripts.

Netmiko, Scrapli, and TextFSM 

In this blog post, Kirk demonstrated that TextFSM and NTC-Templates have been included in Netmiko installation by default since 2017. Let’s see an example:

1. We should install the Netmiko library in the virtual environment.

pip install netmiko

2. Let’s check the Netmiko version.

# python
Python 3.10.12 (main, Feb  4 2025, 14:57:36) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import netmiko
>>> netmiko.__version__
'4.5.0'
>>> 

3. We can try to connect to an Arista EOS device and parse the output from a “show interface” command. Note that in an earlier example we use Cisco IOS as an example; here, we are interacting with an Arista EOS device.

>>> from netmiko import ConnectHandler
>>> net_connect = ConnectHandler(host="172.17.0.2", username="admin", password="admin", device_type="arista_eos")
>>> net_connect.find_prompt()
'ceos-01>'
>>> net_connect.send_command("show ip int brief", use_textfsm=True)
[{'interface': 'Loopback0', 'ip_address': '10.0.0.1/32', 'status': 'up', 'protocol': 'up', 'mtu': '65535'}, {'interface': 'Loopback100', 'ip_address': '192.168.101.100/24', 'status': 'up', 'protocol': 'up', 'mtu': '65535'}, {'interface': 'Management0', 'ip_address': '172.17.0.2/16', 'status': 'up', 'protocol': 'up', 'mtu': '1500'}]

As Kirk wrote in the blog, “This should make you smile. We have structured data and someone else did the heavy lifting for us!”

Well said, Kirk!


Conclusion

NTC-Templates is an invaluable tool for network automation, providing a standardized way to parse CLI output from various network devices. Its ease of use, vendor support, and integration capabilities make it a must-have in any network automation toolkit.

One last note, please consider subscribing to the repository, contributing in any way you can, and giving feedback to improve the project!

Additional Resources

Slurpit TextFSM Template Builder based on NTC-Templates: https://slurpit.io/textfsm-template-builder/

– Eric Chou



Author