Skip to content

Commit

Permalink
Integrating textfsm support directly into Netmiko
Browse files Browse the repository at this point in the history
  • Loading branch information
ktbyers committed Dec 14, 2017
1 parent 06a5774 commit 6c01a3d
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 3 deletions.
11 changes: 9 additions & 2 deletions netmiko/base_connection.py
Expand Up @@ -21,7 +21,7 @@

from netmiko.netmiko_globals import MAX_BUFFER, BACKSPACE_CHAR
from netmiko.ssh_exception import NetMikoTimeoutException, NetMikoAuthenticationException
from netmiko.utilities import write_bytes, check_serial_port
from netmiko.utilities import write_bytes, check_serial_port, get_structured_data
from netmiko.py23_compat import string_types
from netmiko import log
import serial
Expand Down Expand Up @@ -869,7 +869,8 @@ def strip_prompt(self, a_string):

def send_command(self, command_string, expect_string=None,
delay_factor=1, max_loops=500, auto_find_prompt=True,
strip_prompt=True, strip_command=True, normalize=True):
strip_prompt=True, strip_command=True, normalize=True,
use_textfsm=False):
"""Execute command_string on the SSH channel using a pattern-based mechanism. Generally
used for show commands. By default this method will keep waiting to receive data until the
network device prompt is detected. The current network device prompt will be determined
Expand Down Expand Up @@ -897,6 +898,9 @@ def send_command(self, command_string, expect_string=None,
:param normalize: Ensure the proper enter is sent at end of command (default: True).
:type normalize: bool
:param use_textfsm: Process command output through TextFSM template (default: False).
:type normalize: bool
"""
# Time to delay in each read loop
loop_delay = .2
Expand Down Expand Up @@ -958,6 +962,9 @@ def send_command(self, command_string, expect_string=None,

output = self._sanitize_output(output, strip_command=strip_command,
command_string=command_string, strip_prompt=strip_prompt)
if use_textfsm:
output = get_structured_data(output, platform=self.device_type,
command=command_string.strip())
return output

def send_command_expect(self, *args, **kwargs):
Expand Down
53 changes: 53 additions & 0 deletions netmiko/utilities.py
Expand Up @@ -6,6 +6,9 @@
import io
import os
import serial.tools.list_ports
import clitable
from clitable import CliTableError


# Dictionary mapping 'show run' for vendors with different command
SHOW_RUN_MAPPER = {
Expand Down Expand Up @@ -175,3 +178,53 @@ def check_serial_port(name):
for p in ports:
msg += "{},".format(str(p))
raise ValueError(msg)


def get_template_dir():
"""Find and return the ntc-templates/templates dir."""
try:
template_dir = os.environ['NET_TEXTFSM']
index = os.path.join(template_dir, 'index')
if not os.path.isfile(index):
# Assume only base ./ntc-templates specified
template_dir = os.path.join(template_dir, 'templates')
except KeyError:
# Construct path ~/ntc-templates/templates
home_dir = os.path.expanduser("~")
template_dir = os.path.join(home_dir, 'ntc-templates', 'templates')

index = os.path.join(template_dir, 'index')
if not os.path.isdir(template_dir) or not os.path.isfile(index):
msg = """
Valid ntc-templates not found, please install https://github.com/networktocode/ntc-templates
and then set the NET_TEXTFSM environment variable to point to the ./ntc-templates/templates
directory."""
raise ValueError(msg)
return template_dir


def clitable_to_dict(cli_table):
"""Converts TextFSM cli_table object to list of dictionaries."""
objs = []
for row in cli_table:
temp_dict = {}
for index, element in enumerate(row):
temp_dict[cli_table.header[index].lower()] = element
objs.append(temp_dict)
return objs


def get_structured_data(raw_output, platform, command):
"""Convert raw CLI output to structured data using TextFSM template."""
template_dir = get_template_dir()
index_file = os.path.join(template_dir, 'index')
textfsm_obj = clitable.CliTable(index_file, template_dir)
attrs = {'Command': command, 'Platform': platform}
try:
# Parse output through template
textfsm_obj.ParseCmd(raw_output, attrs)
structured_data = clitable_to_dict(textfsm_obj)
output = raw_output if structured_data == [] else structured_data
return output
except CliTableError:
return raw_output
1 change: 1 addition & 0 deletions requirements.txt
Expand Up @@ -2,3 +2,4 @@ paramiko>=2.0.0
scp>=0.10.0
pyyaml
pyserial
textfsm
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -40,7 +40,7 @@ def find_version(*file_paths):
'Programming Language :: Python :: 3.6',
],
packages=find_packages(exclude=("test*", )),
install_requires=['paramiko>=2.0.0', 'scp>=0.10.0', 'pyyaml', 'pyserial'],
install_requires=['paramiko>=2.0.0', 'scp>=0.10.0', 'pyyaml', 'pyserial', 'textfsm'],
extras_require={
'test': ['pytest>=3.2.5', ]
},
Expand Down

0 comments on commit 6c01a3d

Please sign in to comment.