Python // Misc: Netmiko and TextFSM

In Netmiko 2.0.0, I added support for direct integration to TextFSM particularly for ntc-templates. 

I have talked about TextFSM previously here, but let’s review a bit.

What does TextFSM and ntc-templates allow you to do?

Simply stated, it allows you to take unstructured data and convert it to structured data. Or worded differently, it takes a block of text and converts it to lists and dictionaries (or some combination thereof).

And even better, if someone has already written the TextFSM template for you, then you might be able to just get structured data directly. See the ntc-templates index file which maps platform and show command to TextFSM templates.

So to recap, TextFSM is a complex, regular expression state-machine that converts from blocks of string to structured data (lists and dictionaries) and in the context of ntc-templates various people have already done the parsing for you (for some set of platforms and show commands).

Now this is all fine, but how do we use TextFSM and ntc-templates with Netmiko. First we have to get our environment setup correctly. Let’s look at this process.

Setting Up the Environment

Starting with a clean Python3 virtual environment:

$ virtualenv-3.6 -p /usr/bin/python36 netmiko_tfsm
Running virtualenv with interpreter /usr/bin/python36
Using base prefix '/usr'
New python executable in /home/kbyers/VENV/netmiko_tfsm/bin/python36
Also creating executable in /home/kbyers/VENV/netmiko_tfsm/bin/python
Installing setuptools, pip, wheel...done.

$ deactivate
$ source netmiko_tfsm/bin/activate
$ pip list
Package    Version
---------- -------
pip        10.0.1 
setuptools 39.2.0 
wheel      0.31.1 

Then install the latest version of Netmiko.

$ pip install netmiko

At this point, you should be able to successfully import the Netmiko library.

$ python
Python 3.6.5 (default, Apr 26 2018, 00:14:31) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-11)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import netmiko
>>> netmiko.__version__

Now you need to install ntc-templates using a ‘git clone’ action. The easiest way is to just install this into your home directory.

$ cd ~
$ git clone
Cloning into 'ntc-templates'...
remote: Counting objects: 3345, done.
remote: Compressing objects: 100% (36/36), done.
remote: Total 3345 (delta 24), reused 28 (delta 12), pack-reused 3297
Receiving objects: 100% (3345/3345), 713.69 KiB | 9.91 MiB/s, done.
Resolving deltas: 100% (1772/1772), done.

At this point, you should have the ntc-templates index in the following directory:

$ ls ~/ntc-templates/templates/index

I mentioned this earlier, but the index file is just a mapping between platform, command, and the corresponding TextFSM template to use. This includes possible abbreviated versions of the command (for example, ‘sh ip int br’ and ‘show ip interface brief’).

Now, Netmiko has been configured to automatically look in ~/ntc-template/templates/index for the ntc-templates index file. Alternatively, you can explicitly tell Netmiko where to look for the TextFSM template directory by setting the following environment variable (note, there must be an index file in this directory):

export NET_TEXTFSM=/path/to/ntc-templates/templates/

How to use Netmiko with TextFSM

Now that both Netmiko and ntc-templates have been installed, how do we tell Netmiko to use the TextFSM parsing capability. First, look at the ntc-templates index file and make sure your show command has a corresponding TextFSM template for your platform.

After you have done that, then you add the ‘use_textfsm=True’ argument to either the send_command() method or to the send_command_timing() method. Here are a couple of examples:

$ ipython
Python 3.6.5 (default, Apr 26 2018, 00:14:31) 
Type 'copyright', 'credits' or 'license' for more information
IPython 6.4.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from netmiko import Netmiko

In [2]: from getpass import getpass

In [3]: password = getpass()

In [4]: net_connect = Netmiko(host='', username='admin', password=password, device_type='cisco_ios')

In [5]: net_connect.find_prompt()
Out[5]: 'pynet-rtr1#'

In [6]: net_connect.send_command("show ip int brief", use_textfsm=True)
[{'intf': 'FastEthernet0',
  'ipaddr': 'unassigned',
  'status': 'down',
  'proto': 'down'},
 {'intf': 'FastEthernet1',
  'ipaddr': 'unassigned',
  'status': 'down',
  'proto': 'down'},
 {'intf': 'FastEthernet2',
  'ipaddr': 'unassigned',
  'status': 'down',
  'proto': 'down'},
 {'intf': 'FastEthernet3',
  'ipaddr': 'unassigned',
  'status': 'down',
  'proto': 'down'},
 {'intf': 'FastEthernet4',
  'ipaddr': '',
  'status': 'up',
  'proto': 'up'},
 {'intf': 'Vlan1', 'ipaddr': 'unassigned', 'status': 'down', 'proto': 'down'}]

As you can see I am getting structured data back.

Now what about the failure case? In other words, when the template lookup fails or when you do not have ntc-templates installed properly, in that situation, you will just get back unstructured data (i.e. a big string):

In [8]: output = net_connect.send_command("show arp", use_textfsm=True)

In [9]: print(output)
Protocol  Address Age (min)  Hardware Addr   Type   Interface
Internet   34   0062.ec29.70fe  ARPA   FastEthernet4
Internet   -   c89c.1dea.0eb6  ARPA   FastEthernet4
Internet 183   1c6a.7aaf.576c  ARPA   FastEthernet4
Internet 162   5254.aba8.9aea  ARPA   FastEthernet4
Internet 166   5254.abbe.5b7b  ARPA   FastEthernet4
Internet 163   5254.ab71.e119  ARPA   FastEthernet4
Internet 121   5254.abc7.26aa  ARPA   FastEthernet4
Internet  94   5254.ab3a.8d26  ARPA   FastEthernet4
Internet 130   5254.abfb.af12  ARPA   FastEthernet4
Internet  60   0001.00ff.0001  ARPA   FastEthernet4
Internet  46   0002.00ff.0001  ARPA   FastEthernet4
Internet  13   6464.9be8.08c8  ARPA   FastEthernet4

Note, ‘show ip arp’ is supported in ntc-templates, but ‘show arp’ isn’t (you can see this by looking at the ntc-templates index file).

How about one more working example:

In [14]: net_connect.send_command("show version", use_textfsm=True)
[{'version': '15.4(2)T1',
  'rommon': 'System',
  'hostname': 'pynet-rtr1',
  'uptime': '6 days, 4 hours, 59 minutes',
  'running_image': 'c880data-universalk9-mz.154-2.T1.bin',
  'hardware': ['881'],
  'serial': ['FTX0000001X'],
  'config_register': '0x2102'}]