Preface

 

In this article I’d like to show you a way of using Python to script several Cisco routers.

 

Background

 

The story is based on a real scenario : how to do security hardening on more than 20.000 Cisco 800 series CPE routers ? The script needs to do a lot of things : update or create ACLs, set up SSHv2, configure AAA and so on. Given the router type, this discussion is not touching ACI or SDN but rather will focus on legacy, CLI based scripts.


Why use PrettyGoodTerminal ?



The question is, why would you use PGT to run Python scripts when you could run a script with Python itself ? What is the added value or benefit of using it ?


The obvious answer is that PGT is a free yet very powerful application when it comes to mass-configuration of routers.


As PGT was originally developed for router/switch mass configuration, it's scripting engine was specifically designed for scripting thousands of devices by mastering Telnet and SSH connections over multiple hops of jump servers and collecting the script results in a sophisticated way. As a .NET based application written in C#, PGT can actually host Python scripts using IronPython. This way you can consider PGT as Python script manager providing advanced services for scripts, such as:

 

  • Connection management:

PGT will manage all Telnet / SSH connection to devices, even if a device is reachable through multiple jump servers.

Script result handling:

The simple communication interface between PGT and the Python scripts make it very easy to capture and maintain the script results

 

  • Robust logging facility:

The built-in LogManager will provide simple and yet thread safe logging services for scripts, even if the same script is run in multiple instances at the same time

 

  • Lightweight debugging:

The built-in Python Interactive console will provide easy script debugging

 

  • Intelligent code editor:

The Python Interactive console provides auto code-completion and intelli-sense feature to help you writing or editing Python sctips

 

  • Script repository:

Python Scripts are assigned an alias name in the repository and PGT scripts can easily refer to them.

About scripting in PrettyGoodTerminal

 

In my previous post I explained how to use Visual Scripting, but at that time only C# was supported by PGT as the underlying script language. As Python scripting support has recently been introduced to PGT, it has become a real scripting platform and Visual Scripts can make full use of Python. The final script developed for the task above is quite complex and I do not want to go into its details but rather focus on simpler Python scripts. If you were interested in the script, its modified version is available within the download package of PGT and an article discussing debugging the script is using it as an example.

 

Now let's get back to the basics of Python scripting with PGT.

 

As explained earlier, PGT is is capable of hosting and managing Python scripts. This means that the Python scripts only need to focus on the configuration related part and PGT - as the hosting environment - will take care of all the connection management, even through multiple jump servers.

 

Said that, you won't need to tackle SSH or Telnet at all. All you need to do is to provide a list of the routers given by their management ip addresses or host names along with the connection protocol and optionally the jump server to be used. Something like this:

 

Jump server

Vendor

Device IP

Protocol

CustomActionID

10.10.10.2

cisco

10.10.20.1

Telnet

test

10.10.10.2

cisco

10.10.20.2

Telnet

test

10.10.10.2

cisco

10.10.20.3

Telnet

test

10.10.10.2

cisco

10.10.20.4

Telnet

test

10.10.10.2

cisco

10.10.20.5

Telnet

test

10.10.10.2

cisco

10.10.20.6

Telnet

test

 

This list is called a PGT script and can be saved as a CSV file. It is responsible for defining the list of devices we want to script and will also show the script result in the CommandResult columns (no shown here).

The interesting part of this PGT script is the last column : CustomActionID. For PGT this can be an arbitrary ID, identifying the action to be taken on the host. In our case, this will be the name of the Python script we want to run.

 

A Python script

 


To start with, let's create a simple script that queries the IOS version of the router. As I mentioned, PGT will manage the connection to the host and then will call the required script passing a couple of variables and objects to the script allowing it to interact with the router. For this example we will use the Session class which is representing the CLI command interface of the connected router and allows to send commands and get back the result.

1.png

Now simply call its ExecCommand() method :


commandResult = Session.ExecCommand("sh version")

 

In order to test the script, we will need to give an alias name to it. This alias name is called a "Custom Action ID" in PGT, and for interactive script debugging this can be set just below the script text. Any name will do, so call it "aaa".

Next we need a list of routers to connect to. Simly generate a script by pressing Ctrl-G or selecting the Generate Script menu item from the Action menu. Add the ip address of a test router to the device list and add a command by selecting the previously registered "aaa" Custom Action ID from the drop-down list. After clicking on the generate script button, we will have a simple script like below :

1.png

 

Lets start the script by pressing F5. After PGT logged in to the router it will transfer execution to our test Python script which is indicated in the script window title by the "connected to 10.10.10.2" text. Now press F10 to execute the current script line for the "show version" command.

 

When the command completed, the returned value is the command result text and our comandResult variable holds it :

1.png

 

Let's process the result text to get detailed version information. As we use regex expression to parse the version string, we will need to import the "re" Python module in the final script.

 

import re

_versions = [ n.strip(" ") for n in commandResult.splitlines() if n.startswith("Cisco") ]

CiscoVersionInfo = [ v.strip(" ")  for v in _versions[0].split(",")]

#Calculate IOS version numbers. This is more for demonstration, some scripts may need to check version numbers separately

_IOSVersion = CiscoVersionInfo[2]

_IOSVersionNumber = _IOSVersion.split(" ")[1]

_IOSSubVersionNumbers = re.findall(r"[\w']+", _IOSVersionNumber)

IOSMajorRelease = _IOSSubVersionNumbers[0]

IOSMinorRelease = _IOSSubVersionNumbers[1]

IOSMaintenanceNumber = _IOSSubVersionNumbers[2]

IOSTrainID = _IOSSubVersionNumbers[3]

# Calculate Router HW version

HWVersionInfo = [ v for v in _versions[1].strip(" ").split(" ")]

HWPlatform = HWVersionInfo[0] + " " + HWVersionInfo[1]

 

To restart the script press F5 again. Below the script the Local variables window will display our variables now holding version information. Among them there is the CiscoVersionInfo list. In order to see the details of this variable, right click it and select "Add item to inspector". The inspector view will then reveal the contents of the list :

1.png

 

Now let’s go a step further and extend our script to get the host name and the ip address of the VLAN1 interface. To do this define 3 functions that returns the required values as a string:

 

import re

def GetVersion():

  commandResult = Session.ExecCommand("sh version")

  _versions = [ n.strip(" ") for n in commandResult.splitlines() if n.startswith("Cisco") ]

  CiscoVersionInfo = [ v.strip(" ")  for v in _versions[0].split(",")]

 

  #Calculate IOS version numbers. This is more for demonstration, some scripts may need to check version numbers separately

  _IOSVersion = CiscoVersionInfo[2]

  _IOSVersionNumber = _IOSVersion.split(" ")[1]

  _IOSSubVersionNumbers = re.findall(r"[\w']+", _IOSVersionNumber)

  IOSMajorRelease = _IOSSubVersionNumbers[0]

  IOSMinorRelease = _IOSSubVersionNumbers[1]

  IOSMaintenanceNumber = _IOSSubVersionNumbers[2]

  IOSTrainID = _IOSSubVersionNumbers[3]

  # Calculate Router HW version

  HWVersionInfo = [ v for v in _versions[1].strip(" ").split(" ")]

  HWPlatform = HWVersionInfo[0] + " " + HWVersionInfo[1]

  return "IOS Version:" + IOSMajorRelease + "." + IOSMinorRelease + "." + IOSMaintenanceNumber + "." +  IOSTrainID + ";HW:" + HWPlatform

 

def GetGW():

  commandResult = Session.ExecCommand("sh run int vlan1")

  IpAddress = ""

  SubnetMask = ""

  #Get VLAN1 interface details

  for thisLine in commandResult.splitlines():

    if thisLine.strip(" ").startswith("ip address"):

      words = thisLine.strip(" ").split(" ")

      if len(words) > 3 :

        IpAddress = words[2]

        SubnetMask = words[3]

        break

 

  return ";Gateway IP:" + IpAddress + "/" + SubnetMask

 

def GetHostName():

  return  "Hostname: " + Session.GetHostName("#")



Now the script is almost complete, put the "import re" statement at the beginning, concatenate the result of the functions and return the result to PGT. This is where a some global variables comes into scope. I'll show you the complete code first, then will explain it:


import re

def GetVersion():

  commandResult = Session.ExecCommand("sh version")

  _versions = [ n.strip(" ") for n in commandResult.splitlines() if n.startswith("Cisco") ]

  CiscoVersionInfo = [ v.strip(" ")  for v in _versions[0].split(",")]

 

  #Calculate IOS version numbers. This is more for demonstration, some scripts may need to check version numbers separately

  _IOSVersion = CiscoVersionInfo[2]

  _IOSVersionNumber = _IOSVersion.split(" ")[1]

  _IOSSubVersionNumbers = re.findall(r"[\w']+", _IOSVersionNumber)

  IOSMajorRelease = _IOSSubVersionNumbers[0]

  IOSMinorRelease = _IOSSubVersionNumbers[1]

  IOSMaintenanceNumber = _IOSSubVersionNumbers[2]

  IOSTrainID = _IOSSubVersionNumbers[3]

  # Calculate Router HW version

  HWVersionInfo = [ v for v in _versions[1].strip(" ").split(" ")]

  HWPlatform = HWVersionInfo[0] + " " + HWVersionInfo[1]

  return "IOS Version:" + IOSMajorRelease + "." + IOSMinorRelease + "." + IOSMaintenanceNumber + "." +  IOSTrainID + ";HW:" + HWPlatform

 

def GetGW():

  commandResult = Session.ExecCommand("sh run int vlan1")

  IpAddress = ""

  SubnetMask = ""

  #Get VLAN1 interface details

  for thisLine in commandResult.splitlines():

    if thisLine.strip(" ").startswith("ip address"):

      words = thisLine.strip(" ").split(" ")

      if len(words) > 3 :

        IpAddress = words[2]

        SubnetMask = words[3]

        break

 

  return ";Gateway IP:" + IpAddress + "/" + SubnetMask

 

def GetHostName():

  return  "Hostname: " + Session.GetHostName("#")

#Perform any action only when Terminal is connected to a device

if Session.IsConnected:

  ScriptSuccess = False

  ActionResult = "Script error"  

  ActionResult = GetHostName() + ";" + GetVersion() + ";" +GetGW()

  ScriptSuccess = True


At the end of the script we make use of ActionResult and ScriptSuccess global variables. These variables are actually optional, but PGT searches for them and if present will use their values. ActionResult is a string and its value will be copied to the CommandResult column of the originally constructed PGT script. The lines of the PGT script will be colored red or green depending on the value the ScripSuccess variable.


I also published this article on PGT's website with a lot of screenshots and explanation on how to manage and run Python scripts from within PGT.

Conclusion

 

I use PGT for my work almost every day and I believe it is a very powerful scripting application and spare a lot of time when one has to deal with numerous switches or routers. Either making queries for simple configuration parameters or building a complex configuration script, it is made simple and fast. There are also some powerful plug-ins included to create Visio drawing based on Cisco CDP protocol information or store scripting results in SQL using the Scripting  Project Manager.

 

And all of this functionality for FREE.