Python Network Automation with Netmiko - Part 1

Netmiko a popular Python library used in Network Automation, providing a simple and easy-to-use interface for interacting with Networking Devices.

In this blog post, we will take an in-depth look at Netmiko and explore how it can be used to simplify your Network Automation tasks. We will cover the basic concepts of Netmiko, including how it works and what it can do. We will also go through some examples of how Netmiko can be used to automate tasks in real-world scenarios.

Bit of a background

I've been using Ansible for the last few years and it was reasonably easy to learn and implement. Anyone can look at an Ansible Playbook and understand exactly what the playbook does. Python on the other hand can be overwhelming if you are not familiar with it.

As a Network Engineer, I started my Python journey with an amazing book called 'Automate the Boring Stuff with Python' which I highly recommend for absolute beginners. The book teaches everything you need to know as an absolute beginner.

This blog post assumes that you are somewhat familiar with Python such as import module, list, dictionary and flow control.

What is Netmiko?

Netmiko is a Python library developed by Kirk Byers that makes it easier to work and interact with network devices. Netmiko is based on Paramiko which is the Python implementation of the SSH protocol. You can learn more about Netmiko here on the Github page.

GitHub - ktbyers/netmiko: Multi-vendor library to simplify Paramiko SSH connections to network devices
Multi-vendor library to simplify Paramiko SSH connections to network devices - GitHub - ktbyers/netmiko: Multi-vendor library to simplify Paramiko SSH connections to network devices

Installing Netmiko

Netmiko is not part of the standard Python library so, you will have to install it using pip as shown below. The process is similar for other operating systems such as macOS or Linux.

C:\Users\vsurr\Documents\Projects\Python-Udemy>pip install netmiko
Collecting netmiko
  Using cached netmiko-4.0.0-py3-none-any.whl (207 kB)
Requirement already satisfied: setuptools>=38.4.0 in 

*** TRUNCATED ***

  Using cached pycparser-2.21-py2.py3-none-any.whl (118 kB)
Installing collected packages: tenacity, six, future, textfsm, pycparser, cffi, pynacl, cryptography, bcrypt, paramiko, scp, ntc-templates, pyserial, netmiko
Successfully installed bcrypt-3.2.0 cffi-1.15.0 cryptography-36.0.2 future-0.18.2 netmiko-4.0.0 ntc-templates-3.0.0 paramiko-2.10.3 pycparser-2.21 pynacl-1.5.0 pyserial-3.5 scp-0.14.4 six-1.16.0 tenacity-8.0.1 textfsm-1.1.2
WARNING: You are using pip version 20.2.3; however, version 22.0.4 is available.
You should consider upgrading via the 'c:\users\vsurr\appdata\local\programs\python\python39\python.exe -m pip install --upgrade pip' command.

Connecting to a single device

Now that we installed the Netmiko library, let's try and connect to a Cisco router using a simple Python script.

💡
Please ensure that the cisco device is correctly configured for SSH access and that the Python workstation can successfully SSH into the device.
from netmiko import ConnectHandler

connection = ConnectHandler(host='10.10.20.17', port='22', username='cisco', password='cisco123', device_type='cisco_ios')

output = connection.send_command('show ip interface brief')
print(output)

print('Closing Connection')
connection.disconnect()
  • The first step is to import ConnectHandler class from the netmiko library.
  • The next step is to create the connection object by calling the ConnectHandler class constructor. Please note that the name connection is just an arbitrary name and you can use whatever you prefer.
  • I've defined the device parameters such as IP, credentials and device-type directly into the connection object (more on this later)
  • Use send_command() method to send commands to the router.
  • Ensure to disconnect the SSH session by using the disconnect() method

Let's run the script and see what happens? We should be able to see the show ip interface brief output on the terminal.

C:\Users\vsurr\Documents\Projects\Python-Udemy>python netmiko_1.py
Interface                  IP-Address      OK? Method Status                Protocol
GigabitEthernet0/0         10.10.20.17     YES manual up                    up
GigabitEthernet0/1         unassigned      YES unset  administratively down down
GigabitEthernet0/2         unassigned      YES unset  administratively down down
GigabitEthernet0/3         unassigned      YES unset  administratively down down
Closing Connection

Behind the scenes, Netmiko uses SSH to log in to the router and run the command specified and show us the output on the terminal.

💡
You should never save the passwords as a plain text file. We can use the getpass python library to prompt the user to enter the password during the run time which is explained later in this blog post.

Connect using a Python Dictionary

Ideally, we want to keep all the information about a device in a variable like a Python dictionary instead of defining directly in the connection object. One of the benefits of that is we can then use the variable elsewhere in our script. Like a list, a dictionary is a mutable collection of key-value pairs. Indexes for dictionaries are called keys, and a value is assigned to them. Dictionaries are written with curly brackets.

For example, in a dictionary"ip_address": "10.10.1.10", ip_address is the key and 10.10.1.10 is the value.

In this second example, we will define the device parameters as a Python dictionary called cisco-01 and pass it to ConnectHandler (In the previous example, we defined the parameters directly into ConnectHandler)

We can unpack the dictionary using **kwargs arguments unpacking. cisco_01 is called keyword argument of type dictionary and ** will unpack the dictionary.

from netmiko import ConnectHandler

cisco_01 = {
    "device_type": "cisco_ios",
    "host": "10.10.20.17",
    "username": "cisco",
    "password": "cisco123"
}

connection = ConnectHandler(**cisco_01) # unpacking the dictionary

output = connection.send_command('show interface desc')
print(output)

print('Closing Connection')
connection.disconnect()
Interface                      Status         Protocol Description
Gi0/0                          up             up       MANAGEMENT
Gi0/1                          admin down     down
Gi0/2                          admin down     down
Gi0/3                          admin down     down
Closing Connection

As you can see above, the result is the same but it looks more organised with a dictionary.

Enable / Privilege EXEC mode

Instead of show interface descriptioncommand, if we execute, show run command, the script will fail. The reason is that by default Netmiko logs in to the user EXEC shell which doesn't have permissions to run the show run command. We can verify which prompt is available by using the find_prompt() method (shown below)

We can instruct Netmiko to login into Privilege EXEC mode by using the enable() method. Of course, we also need to provide the enable password using the secret parameter as shown below.

from netmiko import ConnectHandler

cisco_01 = {
    "device_type": "cisco_ios",
    "host": "10.10.20.17",
    "username": "cisco",
    "password": "cisco123",
    "secret": "cisco123" # Enable password
}

connection = ConnectHandler(**cisco_01)
connection.enable() # Enable method

which_prompt = connection.find_prompt() # Find prompt method
print(which_prompt) # Print the prompt

output = connection.send_command('show run')
print(output)

print('Closing Connection')
connection.disconnect()
Containerlab - Creating Network Labs Can’t be Any Easier
What if I tell you that all you need is just a YAML file with just a bunch of lines to create a Network Lab that can run easily on your laptop? I’ll walk you through what Containerlab is

Global Configuration mode

So far, we've seen how to run show commands on the Cisco router. If we want to run configuration commands to the router then we will need to use the config_mode method.

from netmiko import ConnectHandler

cisco_01 = {
    "device_type": "cisco_ios",
    "host": "10.10.20.17",
    "username": "cisco",
    "password": "cisco123",
    "secret": "cisco123" # Enable password
}

connection = ConnectHandler(**cisco_01)

connection.enable() # Enable method
connection.config_mode() # Global config mode

connection.send_command('access-list 1 permit any')

print('Closing Connection')
connection.disconnect()

The above script will create the defined ACL (access-list 1 permit any) on the router. Please note that if you want to run any show commands after entering the Global config mode, you must use exit_config_mode() method to return to the privilege EXEC mode as shown below.

from netmiko import ConnectHandler

cisco_01 = {
    "device_type": "cisco_ios",
    "host": "10.10.20.17",
    "username": "cisco",
    "password": "cisco123",
    "secret": "cisco123" # Enable password
}

connection = ConnectHandler(**cisco_01)

connection.enable() # Enable method
connection.config_mode() # Global config mode

connection.send_command('access-list 1 permit any')

connection.exit_config_mode() # Exit global config mode

show_output = connection.send_command('show interface desc')
print(show_output)

print('Closing Connection')
connection.disconnect()

if statements with enable / global config mode

If you use TACACS+ for device administration, there is a good chance you will log in directly into the privilege EXEC mode. In a situation like this, we don't necessarily need to use the enable() method which is already available.

In a similar way, check_config_mode() returns either True or False Boolean value that can also be used with if statements. In the example below, I'm using two if statements to determine the current prompt and make decisions based on that.

from netmiko import ConnectHandler

cisco_01 = {
    "device_type": "cisco_ios",
    "host": "10.10.20.17",
    "username": "cisco",
    "password": "cisco123",
    "secret": "cisco123" # Enable password
}

connection = ConnectHandler(**cisco_01)
which_prompt = connection.find_prompt()
print(which_prompt)

if '>' in which_prompt:
    connection.enable() # Go to Priv EXEC mode only 'if' '>' is present in the output

connection.config_mode() # Global config mode

connection.send_command('access-list 1 permit any')

if connection.check_config_mode:
    connection.exit_config_mode() # Exit global config mode 'if' the statement reurns 'True' value

show_output = connection.send_command('show interface desc')
print(show_output)

print('Closing Connection')
connection.disconnect()

If the current prompt is user EXEC mode then the router will display '>' next to the hostname (cisco-01>) so, the first if statement evaluates to True which in turn will run the if clause - connection.enable()

First if statement

The second if statement is used to exit the global configuration mode only if the check_config_mode() returns a True.

Second if statement

Sending multiple show commands

In this final example, we will learn how to send multiple 'show' commands using Netmiko. The process is very straightforward, we can use for loop to achieve this.

for loop in Python is used to iterate over a sequence such as a Python list. Loop continues until it reaches the last item on the list.

I created a Python List called show_commands and added three commands to it. for loop will then iterate over each item in the list as shown below.

from netmiko import ConnectHandler

cisco_01 = {
    "device_type": "cisco_ios",
    "host": "10.10.20.17",
    "username": "cisco",
    "password": "cisco123",
    "secret": "cisco123" # Enable password
}

connection = ConnectHandler(**cisco_01)
connection.enable() # Go to Priv EXEC mode

show_commands = ['show ip route', 'show interface desc', 'show clock'] # List of commands to send

for command in show_commands: # for loop
    print(f'\n *** Sending { command} *** \n')
    output = connection.send_command(command)
    print(output)

connection.disconnect()
Python/Python39/python.exe c:/Users/vsurr/Documents/Projects/python-learning/python-udemy/netmiko_muli_commands.py

 *** Sending show ip route ***

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 not set

      10.0.0.0/8 is variably subnetted, 2 subnets, 2 masks
C        10.10.0.0/16 is directly connected, GigabitEthernet0/0
L        10.10.20.17/32 is directly connected, GigabitEthernet0/0

 *** Sending show interface desc ***

Interface                      Status         Protocol Description
Gi0/0                          up             up       MANAGEMENT
Gi0/1                          admin down     down
Gi0/2                          admin down     down
Gi0/3                          admin down     down

 *** Sending show clock ***

*09:48:45.139 UTC Wed Apr 13 2022

Securing passwords with getpass

Passwords or secrets should never be saved as plain text files. Every automation tools provide a way to encrypt or hide the passwords, for example, we can use Ansible Vault with Ansible to encrypt sensitive information.

getpass library is a go-to option for Python, getpass prompts the user for a value, password in our case, without echoing what the user types to the terminal. getpass then reads input from the user and saves it as a string. In this example, the input is saved into a variable called passwd. The variable is then used wherever a password is required.

from netmiko import ConnectHandler
import getpass

passwd = getpass.getpass('Please enter the password: ') # Reads the output from the user and save it as a string

cisco_01 = {
    "device_type": "cisco_ios",
    "host": "10.10.20.17",
    "username": "cisco",
    "password": passwd, # Log in password from getpass
    "secret": passwd # Enable password from getpass
}

connection = ConnectHandler(**cisco_01)
connection.enable() # Go to Priv EXEC mode

output = connection.send_command('show interface desc')
print(output)

connection.disconnect()
Python/Python39/python.exe c:/Users/vsurr/Documents/Projects/python-learning/python-udemy/netmiko_muli_commands.py

Please enter the password: 
Interface                      Status         Protocol Description
Gi0/0                          up             up       MANAGEMENT
Gi0/1                          admin down     down
Gi0/2                          admin down     down
Gi0/3                          admin down     down

Woo, we've just learnt how to use Netmiko for Network Automation and how it is very flexible to work with. Please let me know in the comments if you have any questions or feedback.

Part 2 - https://www.packetswitch.co.uk/netmiko-par2/