Three Python Script Improvements

I continue to pick automation routines on the network from Huawei switches. This time, the research that made it possible to reduce the code by 3 times, namely: hosts and commands are moved to separate files, the password and username are no longer stored in clear text. There is demonstration running the script.

I perceive things better visually, so I presented the task of optimizing the script in a graphical form (see below) – namely, I reflected three main tasks:

1. Apply an inventory file that lists the ip addresses of all network devices, rather than creating a dictionary for each of them.

2. Move the configuration commands to a separate file and, just like the inventory file, call it using a function.

3. Get rid of storing the password and username in the script.

I also removed the verification commands (Of course, you can leave them, and they will work from the configuration file, but they are not needed for this task).

In a comment to the previous post “My friend Netmiko” there was a question from the user anders_i about how much the script can be scaled:

Then I used 4 Huawei virtual CloudEngine in eNSP. This time I tried to expand their number as much as the power of Huawei MateBook X Pro 2021 is enough.

And it was possible to expand to 8. The 9th one refused to start, the occupation of 16GB of RAM was 80%.

The updated topology looks like this:

1.I create a configuration file

I prefer the Nano editor (some people prefer Vim):

nano switch_file_config

In the file, I write all the commands from the previous example (the commands can be any, depending on the needs) without spaces / indents. Important specify command return last line. As I understand it, it needs to be written manually only when using the configuration file – when writing commands in the violin itself, the Netmiko module automatically applies it and terminates the SSH connection. Without return the script won’t work.

List of commands moved to a separate file:

Now all the previous code:

    for n in range (300,302):
        print ("Creating VLAN " + str(n))
        config_commands = [
                          'vlan ' + str(n),
                          'desc NETMIKO_VLAN ' + str(n),
                          'Commit'
        ]
        output = ssh_connect.send_config_set(config_commands)

    output = ssh_connect.send_config_set(
        [
        'interface range GE 1/0/9 GE 1/0/10',
        'port trunk allow-pass vlan 300 301',
        'commit'
        ]
    )

I shorten it to:

with open('switch_file_config') as f:
    config_lines = f.read().splitlines()
print (config_lines)

and change the line:

    output = ssh_connect.send_config_set(config_lines)

python function open opens the specified file, reads it, splitting on lines (formatting) f.read().splitlines() and displays print(config_lines). Netmiko module send_config_set will now refer to this file (config_lines)by applying commands from it.

2.I create an inventory file with the IP addresses of the switches:

I start by creating the same file in the Nano editor:

nano myswitches

This file will contain a list of switch IP addresses:

The main thing here is to make sure that there are no gaps left.

I save with Ctrl+X.

For a little test, I created a python file called openfile.py.

I wrote a simple script in it that will open a file with IP addresses and display them on the screen:

f = open ('myswitches')

for IP in f:
    print (IP)

Launched it:

As you can see, the first line was left empty, which means for Python it will look like [‘ ’,]. When trying to run such a file, Python will give an error that it could not contact the destination address. Therefore, it is better not to allow empty lines.

I configured an additional 4 CE switches in the eNSP by giving them IP addresses and setting up an SSH connection:

Now I’m shortening all the previous code:

CE_1_BORDER = {
    'device_type': 'huawei',
    'ip':   '7.7.7.1',
    'username': 'vasyo1',
    'password': '@ghjcnjnF358986'
}

CE_2 = {
    'device_type': 'huawei',
    'ip':   '7.7.7.2',
    'username': 'vasyo1',
    'password': '@ghjcnjnF358986'
}

CE_3 = {
    'device_type': 'huawei',
    'ip':   '7.7.7.3',
    'username': 'vasyo1',
    'password': '@ghjcnjnF358986'        
}

CE_4 = {
    'device_type': 'huawei',
    'ip':   '7.7.7.4',
    'username': 'vasyo1',
    'password': '@ghjcnjnF358986'
}

all_devices = [CE_1_BORDER, CE_2, CE_3, CE_4]

for device in all_devices:
    ssh_connect = ConnectHandler(**device)

Before:

with open('myswitches') as f:
    ip_lines = f.read().splitlines()
print (ip_lines)

for device in ip_lines:
    ip_address_of_device = device
    CE = {
        'device_type': 'huawei',
        'ip':   ip_address_of_device,
        'username': username,
        'password': password
    }

    ssh_connect = ConnectHandler(**CE)
    output = ssh_connect.send_config_set(config_lines)

With the syntax for opening a file, everything is clear.

I no longer write the IP address in the Netmiko dictionary. Instead, now the code will access the variable for IP addresses ip_address_of_devicewhich is equal to the variable devicewhich is part of a loop (loop) forwho reads ip_lineswhich is a variable containing the IP addresses of the devices in the file myswitches.

3. I remove the password and username from the script

It is not safe to store the username and password in what is called clear text:

    CE = {
        'device_type': 'huawei',
        'ip':   ip_address_of_device,
        'username': 'vasyo1',
        'password': '@ghjcnjnF358986'
    }

It is unlikely that this is acceptable in a production network.

Therefore, I will remove them from the script, replacing them with variables: I will rewrite the script in such a way that the script displays a message on the need to enter a username, and then saves the entered values ​​in a variable. For this I use the function input(). Function input() for Python 2.x version is called raw_input().

Function getpass() I use to enter a password.

username = input('Enter your SSH username: ')
password = getpass()

In the dictionary, I replace the username and password with variables:

    CE = {
        'device_type': 'huawei',
        'ip':   ip_address_of_device,
        'username': username,
        'password': password
    }

As a result, I received an improved code that was reduced from 63 lines to 28 and became more adapted for use in real production practice. It looks like this (you can watch the launch of the script on the video On the page Huawei ICT Club Forum):

from getpass import getpass
from netmiko import ConnectHandler

username = input('Enter your SSH username: ')
password = getpass()

with open('switch_file_config') as f:
    config_lines = f.read().splitlines()
print (config_lines)

with open('myswitches') as f:
    ip_lines = f.read().splitlines()
print (ip_lines)

for device in ip_lines:
    ip_address_of_device = device
    CE = {
        'device_type': 'huawei',
        'ip':   ip_address_of_device,
        'username': username,
        'password': password
    }

    ssh_connect = ConnectHandler(**CE)
    output = ssh_connect.send_config_set(config_lines)
    print(f"nn-------------- CE_{CE['ip']} --------------")
    print(output)
    print("-------------------- End -------------------")

Literature:

https://stackoverflow.com/questions/5563089/raw-input-function-in-python

https://pynet.twb-tech.com/blog/automation/netmiko.html

https://pyneng.readthedocs.io/en/latest/book/18_ssh_telnet/netmiko.html

https://github.com/ktbyers/netmiko

https://github.com/ktbyers/netmiko/blob/master/netmiko/ssh_dispatcher.py

Udemy.com – Python Network Programming for Network Engineers (Python 3) (David Bombal)

https://www.pythoncentral.io/pythons-range-function-explained

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *