Module scrapli#
scrapli is a module that allows you to connect to network equipment using Telnet, SSH or NETCONF.
Just like netmiko, scrapli can use paramiko or telnetlib (and other modules) for the connection itself, but it provides the same interface for different types of connections and different equipment.
Installing scrapli:
pip install scrapli
Note
Book covers scrapli version 2021.1.30.
The three main components of scrapli are:
transport is a specific way to connect to equipment
channel - the next level above the transport, which is responsible for sending commands, receiving output and other interactions with equipment
driver is the interface for working with scrapli. There are both specific drivers, for example, IOSXEDriver, which understands how to interact with a specific type of equipment, and the basic Driver, which provides a minimal interface for working via SSH/Telnet.
Available transport options:
system - the built-in SSH client, it is assumed that the client is used on Linux/MacOS
paramiko - the paramiko module
ssh2 - the ssh2-python module (wrapper around the C library libssh2)
telnet - telnetlib
asyncssh - asyncssh module
asynctelnet - async telnet client
Most of the examples will be using the system transport. Since the module interface is the same for all synchronous transport options, to use a different transport, you just need to specify it (for telnet transport, you must also specify the port).
Note
Asynchronous transport options (asyncssh, asynctelnet) are covered in the Advanced Python for network engineers (russian)
Supported platforms:
Cisco IOS-XE
Cisco NX-OS
Juniper JunOS
Cisco IOS-XR
Arista EOS
In addition to these platforms, there are also scrapli community platforms. And one of the advantages of scrapli is that it is relatively easy to add new platforms.
There are two connection options in scrapli: using the general Scrapli class, which selects the required driver by the platform parameter, or a specific driver, for example, IOSXEDriver. The same parameters are passed to the specific driver and Scrapli.
Note
In addition to these options, there are also generic (base) drivers.
If the scrapli (or scrapli community) does not support the required platform, you can add the platform to the scrapli community or use generic drivers (not covered in the book):
Connection parameters#
Basic connection parameters:
host - IP address or hostname
auth_username - username for authentication
auth_password - password for authentication
auth_secondary - enable password
auth_strict_key - strict key checking (True by default)
platform - must be specified when using Scrapli
transport - which transport to use
transport_options - options for a specific transport
The connection process is slightly different depending on whether you are using a context manager or not. When connecting without a context manager, you first need to pass parameters to the driver or Scrapli, and then call the open method:
from scrapli import Scrapli
r1 = {
"host": "192.168.100.1",
"auth_username": "cisco",
"auth_password": "cisco",
"auth_secondary": "cisco",
"auth_strict_key": False,
"platform": "cisco_iosxe"
}
In [2]: ssh = Scrapli(**r1)
In [3]: ssh.open()
After that, you can send commands:
In [4]: ssh.get_prompt()
Out[4]: 'R1#'
In [5]: ssh.close()
When using a context manager, you don’t need to call open:
In [8]: with Scrapli(**r1_driver) as ssh:
...: print(ssh.get_prompt())
...:
R1#
Using the driver#
Available drivers
Network equipment |
Драйвер |
Параметр platform |
---|---|---|
Cisco IOS-XE |
IOSXEDriver |
cisco_iosxe |
Cisco NX-OS |
NXOSDriver |
cisco_nxos |
Cisco IOS-XR |
IOSXRDriver |
cisco_iosxr |
Arista EOS |
EOSDriver |
arista_eos |
Juniper JunOS |
JunosDriver |
juniper_junos |
Example of connection using the IOSXEDriver driver (connecting to Cisco IOS):
In [11]: from scrapli.driver.core import IOSXEDriver
In [12]: r1_driver = {
...: "host": "192.168.100.1",
...: "auth_username": "cisco",
...: "auth_password": "cisco",
...: "auth_secondary": "cisco",
...: "auth_strict_key": False,
...: }
In [13]: with IOSXEDriver(**r1_driver) as ssh:
...: print(ssh.get_prompt())
...:
R1#
Sending commands#
Scrapli has several methods for sending commands:
send_command
- send one show commandsend_commands
- send a list of show commandssend_commands_from_file
- send show commands from a filesend_config
- send one command in configuration modesend_configs
- send a list of commands in configuration modesend_configs_from_file
- send commands from file in configuration mode
All of these methods return a Response object, not the output of the command as a string.
Response object#
The send_command method and other methods for sending commands return a Response object (not the output of the command). Response allows you to get not only the output of the command, but also such things as the execution time of the command, whether there were errors during the execution of the command, structured output using textfsm, and so on.
In [15]: reply = ssh.send_command("sh clock")
In [16]: reply
Out[16]: Response <Success: True>
You can get the output of the command by accessing the result attribute:
In [17]: reply.result
Out[17]: '*17:31:54.232 UTC Wed Mar 31 2021'
The raw_result attribute contains a byte string with complete output:
In [18]: reply.raw_result
Out[18]: b'\n*17:31:54.232 UTC Wed Mar 31 2021\nR1#'
For commands that take longer than normal show, it may be necessary to know the command execution time:
In [18]: r = ssh.send_command("ping 10.1.1.1")
In [19]: r.result
Out[19]: 'Type escape sequence to abort.\nSending 5, 100-byte ICMP Echos to 10.1.1.1, timeout is 2 seconds:\n.....\nSuccess rate is 0 percent (0/5)'
In [20]: r.elapsed_time
Out[20]: 10.047594
In [21]: r.start_time
Out[21]: datetime.datetime(2021, 4, 1, 7, 10, 56, 63697)
In [22]: r.finish_time
Out[22]: datetime.datetime(2021, 4, 1, 7, 11, 6, 111291)
The channel_input attribute returns the command that was sent to the equipment:
In [23]: r.channel_input
Out[23]: 'ping 10.1.1.1'
send_command method#
The send_command
method allows you to send one command to a device.
In [14]: reply = ssh.send_command("sh clock")
Method parameters (all these parameters must be passed as keyword arguments):
strip_prompt
- remove a prompt from the output. Deleted by defaultfailed_when_contains
- if the output contains the specified line or one of the lines in the list, the command will be considered as completed with an errortimeout_ops
- maximum time to execute a command, by default it is 30 seconds for IOSXEDriver
An example of using the send_command
method:
In [15]: reply = ssh.send_command("sh clock")
In [16]: reply
Out[16]: Response <Success: True>
The timeout_ops parameter specifies how long to wait for the command to execute:
In [19]: ssh.send_command("ping 8.8.8.8", timeout_ops=20)
Out[19]: Response <Success: True>
If the command does not complete within the specified time, a ScrapliTimeout exception will be raised (output is truncated):
In [20]: ssh.send_command("ping 8.8.8.8", timeout_ops=2)
---------------------------------------------------------------------------
ScrapliTimeout Traceback (most recent call last)
<ipython-input-20-e062fb19f0e6> in <module>
----> 1 ssh.send_command("ping 8.8.8.8", timeout_ops=2)
In addition to receiving normal command output, scrapli also allows you to receive structured output, for example using the textfsm_parse_output method:
In [21]: reply = ssh.send_command("sh ip int br")
In [22]: reply.textfsm_parse_output()
Out[22]:
[{'intf': 'Ethernet0/0',
'ipaddr': '192.168.100.1',
'status': 'up',
'proto': 'up'},
{'intf': 'Ethernet0/1',
'ipaddr': '192.168.200.1',
'status': 'up',
'proto': 'up'},
{'intf': 'Ethernet0/2',
'ipaddr': 'unassigned',
'status': 'up',
'proto': 'up'},
{'intf': 'Ethernet0/3',
'ipaddr': '192.168.130.1',
'status': 'up',
'proto': 'up'}]
Note
What is TextFSM and how to work with it is covered in chapter 21. Scrapli uses ready-made templates in order to receive structured output and in basic cases does not require knowledge of TextFSM.
Error detection#
Methods for sending commands automatically check the output for errors. For each vendor/type of equipment, these are different errors, plus you can specify which lines in the output will be considered an error. By default, IOSXEDriver will consider the following lines as errors:
In [21]: ssh.failed_when_contains
Out[21]:
['% Ambiguous command',
'% Incomplete command',
'% Invalid input detected',
'% Unknown command']
The failed
attribute of the Response object returns False
if the command finished
without error and True
if it failed.
In [23]: reply = ssh.send_command("sh clck")
In [24]: reply.result
Out[24]: " ^\n% Invalid input detected at '^' marker."
In [25]: reply
Out[25]: Response <Success: False>
In [26]: reply.failed
Out[26]: True
send_config method#
The send_config
method allows you to send one configuration mode command.
Example:
In [33]: r = ssh.send_config("username user1 password password1")
Since scrapli removes the command from the output, by default, when using
send_config
, the result attribute will contain an empty string (if there was
no error while executing the command):
In [34]: r.result
Out[34]: ''
You can add the parameter strip_prompt=False
and then the prompt will
appear in the output:
In [37]: r = ssh.send_config("username user1 password password1", strip_prompt=False)
In [38]: r.result
Out[38]: 'R1(config)#'
send_commands, send_configs#
The send_commands, send_configs methods differ from send_command, send_config in that they can send several commands. In addition, these methods do not return a Response, but a MultiResponse object, which can generally be thought of as a list of Response objects, one for each command.
In [44]: reply = ssh.send_commands(["sh clock", "sh ip int br"])
In [45]: reply
Out[45]: MultiResponse <Success: True; Response Elements: 2>
In [46]: for r in reply:
...: print(r)
...: print(r.result)
...:
Response <Success: True>
*08:38:20.115 UTC Thu Apr 1 2021
Response <Success: True>
Interface IP-Address OK? Method Status Protocol
Ethernet0/0 192.168.100.1 YES NVRAM up up
Ethernet0/1 192.168.200.1 YES NVRAM up up
Ethernet0/2 unassigned YES NVRAM up up
Ethernet0/3 192.168.130.1 YES NVRAM up up
In [47]: reply.result
Out[47]: 'sh clock\n*08:38:20.115 UTC Thu Apr 1 2021sh ip int br\nInterface IP-Address OK? Method Status Protocol\nEthernet0/0 192.168.100.1 YES NVRAM up up\nEthernet0/1 192.168.200.1 YES NVRAM up up\nEthernet0/2 unassigned YES NVRAM up up\nEthernet0/3 192.168.130.1 YES NVRAM up up'
In [48]: reply[0]
Out[48]: Response <Success: True>
In [49]: reply[1]
Out[49]: Response <Success: True>
In [50]: reply[0].result
Out[50]: '*08:38:20.115 UTC Thu Apr 1 2021'
When sending multiple commands, it is also very convenient to use
the stop_on_failed
parameter. By default, it is False, so all commands
are executed, but if you specify stop_on_failed=True
, after an error
occurs in some command, the following commands will not be executed:
In [59]: reply = ssh.send_commands(["ping 192.168.100.2", "sh clck", "sh ip int br"], stop_on_failed=True)
In [60]: reply
Out[60]: MultiResponse <Success: False; Response Elements: 2>
In [61]: reply.result
Out[61]: "ping 192.168.100.2\nType escape sequence to abort.\nSending 5, 100-byte ICMP Echos to 192.168.100.2, timeout is 2 seconds:\n!!!!!\nSuccess rate is 100 percent (5/5), round-trip min/avg/max = 1/2/6 mssh clck\n ^\n% Invalid input detected at '^' marker."
In [62]: for r in reply:
...: print(r)
...: print(r.result)
...:
Response <Success: True>
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 192.168.100.2, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/2/6 ms
Response <Success: False>
^
% Invalid input detected at '^' marker.
Telnet connection#
To connect to equipment via Telnet, you must specify transport equal to telnet
and be sure to specify the port parameter equal to 23 (or the port that you use
to connect via Telnet):
from scrapli.driver.core import IOSXEDriver
from scrapli.exceptions import ScrapliException
import socket
r1 = {
"host": "192.168.100.1",
"auth_username": "cisco",
"auth_password": "cisco2",
"auth_secondary": "cisco",
"auth_strict_key": False,
"transport": "telnet",
"port": 23, # must be specified when connecting telnet
}
def send_show(device, show_command):
try:
with IOSXEDriver(**r1) as ssh:
reply = ssh.send_command(show_command)
return reply.result
except socket.timeout as error:
print(error)
except ScrapliException as error:
print(error, device["host"])
if __name__ == "__main__":
output = send_show(r1, "sh ip int br")
print(output)
Scrapli examples#
Basic example of sending show command:
from scrapli.driver.core import IOSXEDriver
from scrapli.exceptions import ScrapliException
r1 = {
"host": "192.168.100.1",
"auth_username": "cisco",
"auth_password": "cisco",
"auth_secondary": "cisco",
"auth_strict_key": False,
"timeout_socket": 5, # timeout for establishing socket/initial connection
"timeout_transport": 10, # timeout for ssh|telnet transport
}
def send_show(device, show_command):
try:
with IOSXEDriver(**r1) as ssh:
reply = ssh.send_command(show_command)
return reply.result
except ScrapliException as error:
print(error, device["host"])
if __name__ == "__main__":
output = send_show(r1, "sh ip int br")
print(output)
Basic example of sending config commands:
from pprint import pprint
from scrapli import Scrapli
r1 = {
"host": "192.168.100.1",
"auth_username": "cisco",
"auth_password": "cisco",
"auth_secondary": "cisco",
"auth_strict_key": False,
"platform": "cisco_iosxe",
}
def send_show(device, show_commands):
if type(show_commands) == str:
show_commands = [show_commands]
cmd_dict = {}
with Scrapli(**r1) as ssh:
for cmd in show_commands:
reply = ssh.send_command(cmd)
cmd_dict[cmd] = reply.result
return cmd_dict
if __name__ == "__main__":
print("show".center(20, "#"))
output = send_show(r1, ["sh ip int br", "sh ver | i uptime"])
pprint(output, width=120)
An example of sending configuration commands with error checking:
from pprint import pprint
from scrapli import Scrapli
r1 = {
"host": "192.168.100.1",
"auth_username": "cisco",
"auth_password": "cisco",
"auth_secondary": "cisco",
"auth_strict_key": False,
"platform": "cisco_iosxe",
}
def send_cfg(device, cfg_commands, strict=False):
output = ""
if type(cfg_commands) == str:
cfg_commands = [cfg_commands]
with Scrapli(**r1) as ssh:
reply = ssh.send_configs(cfg_commands, stop_on_failed=strict)
for cmd_reply in reply:
if cmd_reply.failed:
print(
f"An error occurred while executing the command::\n{reply.result}\n"
)
output = reply.result
return output
if __name__ == "__main__":
output_cfg = send_cfg(
r1, ["interfacelo11", "ip address 11.1.1.1 255.255.255.255"], strict=True
)
print(output_cfg)