Expanding Netmiko's

Secure Copy Support

Author: Kirk Byers
Date: 2021-09-16

This article was originally written on March 3rd, 2018. It was updated and retested against the Netmiko 4.x code (develop branch) on September 16th, 2021.

Introduction

Netmiko has the capability to transfer files using Secure Copy. This code can be used for both OS upgrades and for transferring configuration files.

As of today, Netmiko supports the following platforms for Secure Copy:

  • arista_eos
  • ciena_saos
  • cisco_asa
  • cisco_ios
  • cisco_nxos
  • cisco_xe
  • cisco_xr
  • dell_os10
  • juniper_junos
  • linux
  • nokia_sros

This support is for both a Secure Copy 'get' and 'put'. It also includes methods that check whether the given file exists, that verify whether disk space is available, and that perform a verification on the transferred file (generally an MD5 verification).

The Netmiko driver generally requires an SCP server to be enabled on the remote network device.

The file_transfer() Function

Netmiko's main interface for using Secure Copy is the file_transfer() function.

The main arguments of the file_transfer function are the following:

def file_transfer(
    ssh_conn: "BaseConnection",
    source_file: str,
    dest_file: str,
    file_system: Optional[str] = None,
    direction: str = "put",
    verify_file: Optional[bool] = True,
    overwrite_file: bool = False,
    socket_timeout: float = 10.0,
): -> Dict[str, bool]: 

Note, there is a bit more nuance to the verify_file argument, but it essentially defaults to True. Also note, I intentionally removed some less frequently used arguments to simplify the discussion.

Looking at the arguments we can see that 'file_transfer' takes a Netmiko SSH connection (ssh_conn), a source file, and a destination file.

We can also see that we have an optional argument for specifying the file_system. There are default file systems included in the Netmiko driver code which are as follows:

Cisco IOS/IOS-XE/IOS-XR        Auto detects the file-system
Cisco NX-OS                    bootflash:
Arista EOS                     /mnt/flash
Juniper Junos                  /var/tmp
Linux                          /var/tmp
Nokia SR-OS                    None
Ciena SAOS                     None
Cisco ASA                      None
Dell OS10                      /home/admin

The default behavior of the file_transfer function is an idempotent file transfer.

In other words, Netmiko will first check if the file already exists, then calculate and compare the file's MD5. If the file already exists and has the correct MD5, then Netmiko will not transfer the file. The file verification method (MD5) will vary depending on the platform, but MD5 hashing is the general mechanism.

By default, 'file_transfer' has the overwrite_file argument set to False, so if the file already exists (and the MD5 doesn't match), then Netmiko won't overwrite the file. You can change this behavior by specifying overwrite_file=True.

You can also disable the file verification (generally an MD5 calculation). You could potentially do this because the MD5 calculation can be very slow on large files.

An Example

Let's look at an example:

#!/usr/bin/env python
import os
from netmiko import ConnectHandler, file_transfer

# Read password from environment variable
password = os.getenv("NETMIKO_PASSWORD")

cisco = {
    "device_type": "cisco_ios",
    "host": "cisco3.lasthop.io",
    "username": "pyclass",
    "password": password,
    "file_system": "flash:",
}
arista = {
    "device_type": "arista_eos",
    "host": "arista1.lasthop.io",
    "username": "pyclass",
    "password": password,
    "file_system": "/mnt/flash",
}
junos = {
    "device_type": "juniper_junos",
    "host": "vmx1.lasthop.io",
    "username": "pyclass",
    "password": password,
    "file_system": "/var/tmp",
}
nxos = {
    "device_type": "cisco_nxos",
    "host": "nxos1.lasthop.io",
    "username": "pyclass",
    "password": password,
    "file_system": "bootflash:",
}

source_file = "test1.txt"
dest_file = "testa.txt"
direction = "put"

for net_device in (cisco, arista, junos, nxos):
    file_system = net_device.pop("file_system")

    # Create the Netmiko SSH connection
    ssh_conn = ConnectHandler(**net_device)
    transfer_dict = file_transfer(
        ssh_conn,
        source_file=source_file,
        dest_file=dest_file,
        file_system=file_system,
        direction=direction,
        overwrite_file=True,
    )
    print(transfer_dict)
    pause = input("Hit enter to continue: ")

The 'file_transfer' function will return a dictionary with the following structure:

{
    'file_exists': True, 
    'file_transferred': True, 
    'file_verified': True
} 

The 'file_exists' key indicates the transferred file now exists. In the case of a 'put' operation this will mean the file is on the remote system. In the case of a 'get' operation this will mean the file is now on the local system (the system that is running the Python script). Note, file_exists should always return True or else an exception would have occurred.

The 'file_transferred' key indicates whether the file needed to be transferred or was already correct.

The 'file_verified' key indicates whether file verification occurred (generally MD5 hashing).

Here is the output of running this script. Note, I have added some comments and spacing for clarity:

$ python test_file_transfer.py 

# Cisco IOS-XE Device
{'file_exists': True, 'file_transferred': True, 'file_verified': True}
Hit enter to continue:

# Arista Device 
{'file_exists': True, 'file_transferred': True, 'file_verified': True}
Hit enter to continue: 

# Juniper Device
{'file_exists': True, 'file_transferred': True, 'file_verified': True}
Hit enter to continue:

# NX-OS Device 
{'file_exists': True, 'file_transferred': True, 'file_verified': True}
Hit enter to continue:

And here is the before and after on each one of these devices:

# Cisco IOS-XE Before 
cisco3#dir flash:/testa.txt 
%Error opening bootflash:/testa.txt (No such file or directory)

# Cisco IOS-XE After 
cisco3#dir flash:/testa.txt
Directory of bootflash:/testa.txt

  106  -rw-               25  Sep 16 2021 11:06:53 -07:00  testa.txt

2908606464 bytes total (1784090624 bytes free)
# Arista Before 
arista1#dir flash:/testa.txt
Directory of flash:/testa.txt

% Error listing directory flash:/testa.txt (No such file or directory)

# Arista After 
arista1#dir flash:/testa.txt
Directory of flash:/testa.txt

       -rwx          25           Sep 16 11:10  testa.txt

3957878784 bytes total (3294085120 bytes free)
# Juniper Before 
pyclass@vmx1> start shell sh 
$ cd /var/tmp

$ ls -al testa.txt
ls: testa.txt: No such file or directory

# Juniper After 
$ ls -al testa.txt
-rw-r--r--  1 pyclass  wheel  25 Sep 16 14:13 testa.txt
# NX-OS Before 
nxos1# dir bootflash:/testa.txt
No such file or directory

# NX-OS After 
nxos1# dir bootflash:/testa.txt
         25    Sep 16 18:16:35 2021  testa.txt

The file_transfer function is available in any reasonably recent version of Netmiko. It was originally created in Netmiko 2.1.0.

As with all things open-source, make sure you test and validate this function properly in your environment before using it.

Reference code for this article can be found here.

Note, there might be slight differences between the reference code and the code shown in the article.

Kirk Byers

@kirkbyers

You might also be interested in: