Skip to content

Lab 18 - Refactoring Code with Functions

In the last lab, you learned how to use functions and even created a small function that connects to devices using Netmiko. You'll use that knowledge now to refactor and modularize the two scripts.

Task 1 - Modularize the Backup Script

Step 1-1

We will begin with a backup script that looks as follows (it's the final code from Lab 16!).

In your code editor, create a new file in /home/ntc/labs/python/ save it as backup-functions.py. Add to it the following code:

#! /usr/bin/env python

from netmiko import ConnectHandler

devices = ["csr1", "csr2" ,"csr3"]

for device in devices:
    print(f"Connecting to device | {device}")

    net_device = ConnectHandler(
        host=device, username="ntc", password="ntc123", device_type="cisco_ios"
    )

    print(f"Saving configuration | {device}")

    net_device.send_command("wr mem")

    print(f"Backing up configuration | {device}")

    net_device.send_command("term len 0")
    net_config = net_device.send_command("show run")

    print(f"Writing config to file | {device}\n")

    with open(f"/home/ntc/labs/python/configs/{device}.cfg", "w") as config_file:
        config_file.write(net_config)

Step 1-2

As you can see, there are logical distinctions to the actions actually being performed here. They are: Connecting to the device, Saving the configuration, Backing up the configuration, and writing the config to a file. This is a good way to start to thinking about functions. We'll also create a function called main() that'll act as the "beginning" of the program.

First let's start by creating main(). Simply put everything into main() and then call the function at the bottom of the script.

Note: Mind the additional indentation - any modern code editor should allow you to select text and press TAB to indent the selection. Typically, pressing Shift-TAB is used to unindent (that is remove one level of indentation).

IMPORTANT: Whatever you do, make sure you're only using 4 spaces per indentation level in Python!

It should look like this:

#! /usr/bin/env python

from netmiko import ConnectHandler


def main():
    devices = ["csr1", "csr2", "csr3"]

    for device in devices:
        print(f"Connecting to device | {device}")

        net_device = ConnectHandler(
            host=device, username="ntc", password="ntc123", device_type="cisco_ios"
        )

        print(f"Saving configuration | {device}")

        net_device.send_command("wr mem")

        print(f"Backing up configuration | {device}")

        net_device.send_command("term len 0")
        net_config = net_device.send_command("show run")

        print(f"Writing config to file | {device}\n")

        with open(f"/home/ntc/labs/python/configs/{device}.cfg", "w") as config_file:
            config_file.write(net_config)


main()

Step 1-3

Add a function called connect_to_device that looks like this:

def connect_to_device(hostname):
    print(f"Connecting to device | {hostname}")
    device = ConnectHandler(
        host=hostname, username="ntc", password="ntc123", device_type="cisco_ios"
    )

    return device

Notice that we'll receive the hostname of the device, use that to connect with netmiko, and return the actual "device object".

Note: a few variable names changed to ensure you understand which variables can be accessed inside and outside of the functions.

The updated script looks like this:

#! /usr/bin/env python

from netmiko import ConnectHandler


def connect_to_device(hostname):
    print(f"Connecting to device | {hostname}")
    device = ConnectHandler(
        host=hostname, username="ntc", password="ntc123", device_type="cisco_ios"
    )

    return device


def main():
    devices = ["csr1", "csr2", "csr3"]

    for device in devices:
        net_device = connect_to_device(device)

        print(f"Saving configuration | {device}")

        net_device.send_command("wr mem")

        print(f"Backing up configuration | {device}")

        net_device.send_command("term len 0")
        net_config = net_device.send_command("show run")

        print(f"Writing config to file | {device}\n")

        with open(f"/home/ntc/labs/python/configs/{device}.cfg", "w") as config_file:
            config_file.write(net_config)


main()

Step 1-4

Add another three functions called save_config, backup_config, write_to_file.

In this step, we'll just perform the work for save_config and use a pass statement (placeholder) in Python to allow us to prepare the ground work for the next two Steps beyond this one.

#! /usr/bin/env python

from netmiko import ConnectHandler


def connect_to_device(hostname):
    print(f"Connecting to device | {hostname}")
    device = ConnectHandler(
        host=hostname, username="ntc", password="ntc123", device_type="cisco_ios"
    )

    return device


def save_config(device, hostname):
    print(f"Saving configuration | {hostname}")
    device.send_command("wr mem")


def backup_config(device, hostname):
    pass


def write_to_file():
    pass


def main():
    devices = ["csr1", "csr2", "csr3"]

    for device in devices:
        net_device = connect_to_device(device)

        save_config(net_device, device)

        print(f"Backing up configuration | {device}")

        net_device.send_command("term len 0")
        net_config = net_device.send_command("show run")

        print(f"Writing config to file | {device}\n")

        with open(f"/home/ntc/labs/python/configs/{device}.cfg", "w") as config_file:
            config_file.write(net_config)


main()

Step 1-5

You'll perhaps notice variable names in the main() function are getting a bit confusing, so let's rename the loop variable device to device_hostname.

def main():
    devices = ["csr1", "csr2", "csr3"]

    for device_hostname in devices:
        net_device = connect_to_device(device_hostname)

        save_config(net_device, device_hostname)

        print(f"Backing up configuration | {device_hostname}")

        net_device.send_command("term len 0")
        net_config = net_device.send_command("show run")

        print(f"Writing config to file | {device_hostname}\n")

        with open(f"/home/ntc/labs/python/configs/{device_hostname}.cfg", "w") as config_file:
            config_file.write(net_config)

Note: Be careful if doing a search-and-replace operation so you don't end up with more changes than expected!

Step 1-6

Next, we need to add the code for the backup_config function.

def backup_config(device, hostname):
    print(f"Backing up configuration | {hostname}")
    device.send_command("term len 0")
    config = device.send_command("show run")

    return config

The associated main() function would need to be changed to the following too:

def main():
    devices = ["csr1", "csr2", "csr3"]

    for device_hostname in devices:
        net_device = connect_to_device(device_hostname)

        save_config(net_device, device_hostname)

        net_config = backup_config(net_device, device_hostname)

        print(f"Writing config to file | {device_hostname}\n")

        with open(
            f"/home/ntc/labs/python/configs/{device_hostname}.cfg", "w"
        ) as config_file:
            config_file.write(net_config)

Step 1-7

Update the write_to_file function so it includes the following:

def write_to_file(hostname, show_run):
    print(f"Writing config to file | {hostname}\n")
    backup_path = "/home/ntc/labs/python/configs"
    with open(f"{backup_path}/{hostname}.cfg", "w") as config_file:
        config_file.write(show_run)

The final script after updating main() should look like this:

#! /usr/bin/env python

from netmiko import ConnectHandler


def connect_to_device(hostname):
    print(f"Connecting to device | {hostname}")
    device = ConnectHandler(
        host=hostname, username="ntc", password="ntc123", device_type="cisco_ios"
    )

    return device


def save_config(device, hostname):
    print(f"Saving configuration | {hostname}")
    device.send_command("wr mem")


def backup_config(device, hostname):
    print(f"Backing up configuration | {hostname}")
    device.send_command("term len 0")
    config = device.send_command("show run")

    return config


def write_to_file(hostname, show_run):
    print(f"Writing config to file | {hostname}\n")
    backup_path = "/home/ntc/labs/python/configs"
    with open(f"{backup_path}/{hostname}.cfg", "w") as config_file:
        config_file.write(show_run)


def main():
    devices = ["csr1", "csr2", "csr3"]

    for device_hostname in devices:
        net_device = connect_to_device(device_hostname)

        save_config(net_device, device_hostname)

        net_config = backup_config(net_device, device_hostname)

        write_to_file(device_hostname, net_config)

        net_device.disconnect()


main()

Conclusion

As you can tell, this code is much more modular and you can now re-use these functions as you need to, for example, if you need to do another save or backup somewhere else in the script.