diff --git a/.github/workflows/commit.yaml b/.github/workflows/commit.yaml
index c561d970..49c79df2 100644
--- a/.github/workflows/commit.yaml
+++ b/.github/workflows/commit.yaml
@@ -9,7 +9,7 @@ jobs:
max-parallel: 6
matrix:
os: [ubuntu-latest, macos-latest]
- python-version: [3.7, 3.8]
+ python-version: [3.6, 3.7, 3.8]
steps:
- uses: actions/checkout@v1
- name: set up python ${{ matrix.python-version }}
diff --git a/.github/workflows/weekly.yaml b/.github/workflows/weekly.yaml
index 92651114..41120e8e 100644
--- a/.github/workflows/weekly.yaml
+++ b/.github/workflows/weekly.yaml
@@ -12,7 +12,7 @@ jobs:
max-parallel: 6
matrix:
os: [ubuntu-latest, macos-latest]
- python-version: [3.7, 3.8]
+ python-version: [3.6, 3.7, 3.8]
steps:
- uses: actions/checkout@v1
- name: set up python ${{ matrix.python-version }}
diff --git a/Makefile b/Makefile
index 9aebac49..798f8002 100644
--- a/Makefile
+++ b/Makefile
@@ -6,26 +6,26 @@ lint:
python -m black .
python -m pylama .
python -m pydocstyle .
- python -m mypy --strict nssh/
- find nssh -type f \( -iname "*.py" ! -iname "ptyprocess.py" \) | xargs darglint
+ python -m mypy --strict scrapli/
+ find scrapli -type f \( -iname "*.py" ! -iname "ptyprocess.py" \) | xargs darglint -x
cov:
python -m pytest \
- --cov=nssh \
+ --cov=scrapli \
--cov-report html \
--cov-report term \
tests/
cov_unit:
python -m pytest \
- --cov=nssh \
+ --cov=scrapli \
--cov-report html \
--cov-report term \
tests/unit/
cov_functional:
python -m pytest \
- --cov=nssh \
+ --cov=scrapli \
--cov-report html \
--cov-report term \
tests/functional/
@@ -42,34 +42,34 @@ test_functional:
test_iosxe:
python -m pytest -v \
tests/unit \
- tests/functional/cisco_iosxe
+ tests/functional/driver/core/cisco_iosxe
test_nxos:
python -m pytest -v \
tests/unit \
- tests/functional/cisco_nxos
+ tests/functional/driver/core/cisco_nxos
test_iosxr:
python -m pytest -v \
tests/unit \
- tests/functional/cisco_iosxr
+ tests/functional/driver/core/cisco_iosxr
test_eos:
python -m pytest -v \
tests/unit \
- tests/functional/arista_eos
+ tests/functional/driver/core/arista_eos
test_junos:
python -m pytest -v \
tests/unit \
- tests/functional/juniper_junos
+ tests/functional/driver/core/juniper_junos
.PHONY: docs
docs:
python -m pdoc \
--html \
--output-dir docs \
- nssh \
+ scrapli \
--force
start_dev_env:
diff --git a/README.md b/README.md
index cec276b3..e8e9b656 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,19 @@
-![](https://github.com/carlmontanari/nssh/workflows/Weekly%20Build/badge.svg)
-[![PyPI version](https://badge.fury.io/py/nssh.svg)](https://badge.fury.io/py/nssh)
+![](https://github.com/carlmontanari/scrapli/workflows/Weekly%20Build/badge.svg)
+[![PyPI version](https://badge.fury.io/py/scrapli.svg)](https://badge.fury.io/py/scrapli)
[![Python 3.7](https://img.shields.io/badge/python-3.7-blue.svg)](https://www.python.org/downloads/release/python-370/)
[![Python 3.8](https://img.shields.io/badge/python-3.8-blue.svg)](https://www.python.org/downloads/release/python-380/)
[![Code Style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)
-nssh
+scrapli
=======
-nssh is a python library focused on connecting to devices, specifically network devices (routers/switches/firewalls
-/etc.) via SSH. nssh's goal is to be as fast and flexible as possible, while providing a well typed, well documented
-, simple API.
+scrapli -- scrap(e) (c)li -- is a python library focused on connecting to devices, specifically network devices
+ (routers/switches/firewalls/etc.) via SSH or Telnet. The name scrapli -- is just "scrape cli" (as in screen scrape
+ ) squished together! scrapli's goal is to be as fast and flexible as possible, while providing a well typed, well
+ documented, simple API.
-nssh is built primarily in two parts: transport and channel. The transport layer is responsible for providing a file
+scrapli is built primarily in two parts: transport and channel. The transport layer is responsible for providing a file
-like interface to the target SSH server. The channel layer is responsible for reading and writing to the provided
file-like interface.
@@ -25,25 +26,25 @@ There are three available "drivers" for the transport layer -- all of which inhe
A good question to ask at this point is probably "why?". Why multiple transport options? Why not just use paramiko
like most folks do? Historically the reason for moving away from paramiko was simply speed. ssh2-python is a wrapper
- around the libssh2 C library, and as such is very very fast. In a prior project ([ssh2net]()), of which nssh is the
- successor/evolution, ssh2-python was used with great success, however, it is a bit feature-limited, and devlopment
+ around the libssh2 C library, and as such is very very fast. In a prior project ([ssh2net]()), of which scrapli is the
+ successor/evolution, ssh2-python was used with great success, however, it is a bit feature-limited, and development
seems to have stalled.
This led to moving back to paramiko, which of course is a fantastic project with tons and tons of feature support
. Paramiko, however, does not "direct" OpenSSH support, and I don't believe it provides 100% full OpenSSH support
- either (ex: ControlPersist). Fully supporting an OpenSSH config file would be an ideal end goal for nssh, something
+ either (ex: ControlPersist). Fully supporting an OpenSSH config file would be an ideal end goal for scrapli, something
that may not be possible with Paramiko - ControlPersist in particular is very interesting to me.
With the goal of supporting all of the OpenSSH configuration options the final transport driver option is simply
native system local SSH (almost certainly this won't work on Windows, but I don't have a Windows box to test on, or
any particular interest in doing so). The implementation of using system SSH is of course a little bit messy
- , however nssh takes care of that for you so you don't need to care about it! The payoff of using system SSH is of
- course that OpenSSH config files simply "work" -- no passing it to nssh, no selective support, no need to set
+ , however scrapli takes care of that for you so you don't need to care about it! The payoff of using system SSH is of
+ course that OpenSSH config files simply "work" -- no passing it to scrapli, no selective support, no need to set
username or ports or any of the other config items that may reside in your SSH config file. The "system"
transport driver is still a bit of a work in progress, but in testing has been reliable thus far.
-The final piece of nssh is the actual "driver" -- or the component that binds the transport and channel together and
- deals with instantiation of an nssh object. There is a "base" driver object -- `NSSH` -- which provides essentially
+The final piece of scrapli is the actual "driver" -- or the component that binds the transport and channel together and
+ deals with instantiation of an scrapli object. There is a "base" driver object -- `Scrape` -- which provides essentially
a "raw" SSH connection with read and write methods (provided by the channel object), and not much else. More
specific "drivers" can inherit from this class to extend functionality of the driver to make it more friendly for
network devices.
@@ -59,7 +60,7 @@ The final piece of nssh is the actual "driver" -- or the component that binds th
- [Native and Platform Drivers Examples](#native-and-platform-drivers-examples)
- [Platform Regex](#platform-regex)
- [Basic Operations -- Sending and Receiving](#basic-operations----sending-and-receiving)
- - [Result Objects](#result-objects)
+ - [Response Objects](#response-objects)
- [Handling Prompts](#handling-prompts)
- [Driver Privilege Levels](#driver-privilege-levels)
- [Sending Configurations](#sending-configurations)
@@ -78,8 +79,8 @@ The final piece of nssh is the actual "driver" -- or the component that binds th
Documentation is auto-generated [using pdoc3](https://github.com/pdoc3/pdoc). Documentation is linted (see Linting and
Testing section) via [pydocstyle](https://github.com/PyCQA/pydocstyle/) and [darglint](https://github.com/terrencepreilly/darglint).
-Documentation is hosted via GitHub Pages and can be found [here.](https://carlmontanari.github.io/nssh/docs/nssh/index.html).
- You can also view the readme as a web page [here.](https://carlmontanari.github.io/nssh/)
+Documentation is hosted via GitHub Pages and can be found [here.](https://carlmontanari.github.io/scrapli/docs/scrapli/index.html).
+ You can also view the readme as a web page [here.](https://carlmontanari.github.io/scrapli/)
To regenerate documentation locally, use the following make command:
@@ -90,9 +91,9 @@ make docs
# Supported Platforms
-nssh "core" drivers cover basically the [NAPALM](https://github.com/napalm-automation/napalm) platforms -- Cisco IOS-XE,
- IOS-XR, NX-OS, Arista EOS, and Juniper JunOS. These drivers provide an interface tailored to network device "screen
- -scraping" rather than just a generic SSH connection/channel.
+scrapli "core" drivers cover basically the [NAPALM](https://github.com/napalm-automation/napalm) platforms -- Cisco
+ IOS-XE, IOS-XR, NX-OS, Arista EOS, and Juniper JunOS. These drivers provide an interface tailored to network device
+ "screen-scraping" rather than just a generic SSH connection/channel.
At the moment there are five "core" drivers representing the most common networking platforms (outlined below)
, however in the future it would be possible for folks to contribute additional "community" drivers. It is unlikely
@@ -110,7 +111,7 @@ This "driver" pattern is pretty much exactly like the implementation in NAPALM.
for use with TextFSM.
All of this is focused on network device type SSH cli interfaces, but should work on pretty much any SSH connection
- (though there are almost certainly better options for non-network type devices!). This "base" (`NSSH`) connection does
+ (though there are almost certainly better options for non-network type devices!). This "base" (`Scrape`) connection does
not handle any kind of device-specific operations such as privilege escalation or saving configurations, it is simply
intended to be a bare bones connection that can interact with nearly any device/platform if you are willing to
send/parse inputs/outputs manually.
@@ -124,31 +125,45 @@ The goal for all "core" devices will be to include functional tests that can run
You should be able to pip install it "normally":
```
-pip install nssh
+pip install scrapli
```
To install from this repositories master branch:
```
-pip install git+https://github.com/carlmontanari/nssh
+pip install git+https://github.com/carlmontanari/scrapli
```
To install from source:
```
-git clone https://github.com/carlmontanari/nssh
-cd nssh
+git clone https://github.com/carlmontanari/scrapli
+cd scrapli
python setup.py install
```
-As for platforms to *run* nssh on -- it has and will be tested on MacOS and Ubuntu regularly and should work on any
+scrapli has made an effort to have as few dependencies as possible. The "core" of scrapli can run with nothing other
+ than standard library! If you wish to use paramiko or ssh2-python as a driver, however, you of course need to install
+ those. This can be done with pip:
+
+```
+pip install scrapli[paramiko]
+```
+
+The available optional installation options are:
+
+- paramiko
+- ssh2 (ssh2-python)
+- textfsm (textfsm and ntc-templates)
+
+As for platforms to *run* scrapli on -- it has and will be tested on MacOS and Ubuntu regularly and should work on any
POSIX system.
# Examples Links
-- [Basic "native" NSSH operations](/examples/basic_usage/nssh_driver.py)
-- [Basic "driver" NSSH operations](/examples/basic_usage/iosxe_driver.py)
+- [Basic "native" Scrape operations](/examples/basic_usage/scrapli_driver.py)
+- [Basic "driver" Scrape operations](/examples/basic_usage/iosxe_driver.py)
- [Setting up basic logging](/examples/logging/basic_logging.py)
- [Using SSH Key for authentication](/examples/ssh_keys/ssh_keys.py)
- [Using SSH config file](/)
@@ -158,13 +173,13 @@ As for platforms to *run* nssh on -- it has and will be tested on MacOS and Ubun
## Native and Platform Drivers Examples
-Example NSSH "native/base" connection:
+Example Scrape "native/base" connection:
```python
-from nssh import NSSH
+from scrapli import Scrape
my_device = {"host": "172.18.0.11", "auth_username": "vrnetlab", "auth_password": "VR-netlab9"}
-conn = NSSH(**my_device)
+conn = Scrape(**my_device)
conn.open()
# do stuff!
```
@@ -173,7 +188,7 @@ Example IOS-XE driver setup. This also shows using context manager which is also
using the context manager there is no need to call the "open_shell" method:
```python
-from nssh.driver.core import IOSXEDriver
+from scrapli.driver.core import IOSXEDriver
my_device = {"host": "172.18.0.11", "auth_username": "vrnetlab", "auth_password": "VR-netlab9"}
with IOSXEDriver(**my_device) as conn:
@@ -186,16 +201,16 @@ with IOSXEDriver(**my_device) as conn:
Due to the nature of SSH there is no good way to know when a command has completed execution. Put another way, when
sending any command, data is returned over a socket, that socket doesn't ever tell us when it is "done" sending the
output from the command that was executed. In order to know when the session is "back at the base prompt/starting
- point" nssh uses a regular expression pattern to find that base prompt.
+ point" scrapli uses a regular expression pattern to find that base prompt.
This pattern is contained in the `comms_prompt_pattern` setting, and is perhaps the most important argument to getting
- nssh working.
+ scrapli working.
The "base" (default, but changeable) pattern is:
`"^[a-z0-9.\-@()/:]{1,20}[#>$]$"`
-*NOTE* all `comms_prompt_pattern` should use the start and end of line anchors as all regex searches in nssh are
+*NOTE* all `comms_prompt_pattern` should use the start and end of line anchors as all regex searches in scrapli are
multline (this is an important piece to making this all work!). While you don't *need* to use the line anchors its
probably a really good idea!
@@ -208,23 +223,23 @@ If you do not wish to match Cisco "config" level prompts you could use a `comms_
If you use a platform driver, the base prompt is set in the driver so you don't really need to worry about this!
-The `comms_prompt_pattern` pattern can be changed at any time at or after instantiation of an nssh object. Changing
+The `comms_prompt_pattern` pattern can be changed at any time at or after instantiation of an scrapli object. Changing
this *can* break things though, so be careful!
## Basic Operations -- Sending and Receiving
-Sending inputs and receiving outputs is done through the base NSSH object or your selected driver object. The inputs
- /outputs all are processed (sent/read) via the channel object. If using the base `NSSH` object you must use the
+Sending inputs and receiving outputs is done through the base Scrape object or your selected driver object. The inputs
+ /outputs all are processed (sent/read) via the channel object. If using the base `Scrape` object you must use the
`channel.send_inputs` method -- the `NetworkDriver` and platform specific drivers have a `send_commands` method as
outlined below. The following example shows sending a "show version" command as a string. Also shown: `send_inputs
` accepts a list/tuple of commands.
```python
-from nssh import NSSH
+from scrapli import Scrape
my_device = {"host": "172.18.0.11", "auth_username": "vrnetlab", "auth_password": "VR-netlab9"}
-with NSSH(**my_device) as conn:
+with Scrape(**my_device) as conn:
results = conn.channel.send_inputs("show version")
results = conn.channel.send_inputs(("show version", "show run"))
```
@@ -235,7 +250,7 @@ When using a network "driver", it is more desirable to use the `send_commands` m
(`default_desired_priv` attribute of the specific driver, see [Driver Privilege Levels](#driver-privilege-levels)).
```python
-from nssh.driver.core import IOSXEDriver
+from scrapli.driver.core import IOSXEDriver
my_device = {"host": "172.18.0.11", "auth_username": "vrnetlab", "auth_password": "VR-netlab9"}
with IOSXEDriver(**my_device) as conn:
@@ -244,13 +259,13 @@ with IOSXEDriver(**my_device) as conn:
```
-## Result Objects
+## Response Objects
-All read operations result in a `Result` object being created. The `Result` object contains attributes for the command
+All read operations result in a `Response` object being created. The `Response` object contains attributes for the command
sent (`channel_input`), start/end/elapsed time, and of course the result of the command sent.
```python
-from nssh.driver.core import IOSXEDriver
+from scrapli.driver.core import IOSXEDriver
my_device = {"host": "172.18.0.11", "auth_username": "vrnetlab", "auth_password": "VR-netlab9"}
with IOSXEDriver(**my_device) as conn:
@@ -262,31 +277,47 @@ with IOSXEDriver(**my_device) as conn:
## Handling Prompts
-In some cases you may need to run an "interactive" command on your device. The `send_inputs_interact` method can be
- used to handle these situations. This method accepts a tuple containing the initial input (command) to send, the
- expected prompt after the initial send, the response to that prompt, and the final expected prompt -- basically
- telling nssh when it is done with the interactive command. In the below example the expectation is that the
- current/base prompt is the final expected prompt, so we can simply call the `get_prompt` method to snag that
- directly off the router.
+In some cases you may need to run an "interactive" command on your device. The `channel.send_inputs_interact` method
+ can be used to handle these situations if using the base `Scrape` driver -- if using the `NetworkBase` or any of the
+ platform drivers, the `send_interactive` method can be used to accomplish this -- `send_interactive` is just a thin
+ wrapper around the `channel.send_inputs_interact` method for convenience (not having to call a channel method
+ basically). This method accepts a tuple containing the initial input (command) to send, the expected prompt after
+ the initial send, the response to that prompt, and the final expected prompt -- basically telling scrapli when it
+ is done with the interactive command. In the below example the expectation is that the current/base prompt is
+ the final expected prompt, so we can simply call the `get_prompt` method to snag that directly off the router.
```python
-from nssh.driver.core import IOSXEDriver
+from scrapli import Scrape
my_device = {"host": "172.18.0.11", "auth_username": "vrnetlab", "auth_password": "VR-netlab9"}
interact = ["clear logging", "Clear logging buffer [confirm]", "\n"]
-with IOSXEDriver(**my_device) as conn:
+with Scrape(**my_device) as conn:
interactive = conn.channel.send_inputs_interact(
("clear logging", "Clear logging buffer [confirm]", "\n", conn.get_prompt())
)
```
+Or with the `NetworkDriver` or any platform driver:
+
+```python
+from scrapli.driver import NetworkDriver
+
+my_device = {"host": "172.18.0.11", "auth_username": "vrnetlab", "auth_password": "VR-netlab9"}
+interact = ["clear logging", "Clear logging buffer [confirm]", "\n"]
+
+with NetworkDriver(**my_device) as conn:
+ interactive = conn.send_interactive(
+ ("clear logging", "Clear logging buffer [confirm]", "\n", conn.get_prompt())
+ )
+```
+
## Driver Privilege Levels
The "core" drivers understand the basic privilege levels of their respective device types. As mentioned previously
, the drivers will automatically attain the "privilege_exec" (or equivalent) privilege level prior to executing "show
-" commands. If you don't want this "auto-magic" you can use the base driver (nssh). The privileges for each device
+" commands. If you don't want this "auto-magic" you can use the base driver (Scrape). The privileges for each device
are outlined in named tuples in the platforms `driver.py` file.
As an example, the following privilege levels are supported by the IOSXEDriver:
@@ -313,7 +344,7 @@ If you wish to manually enter a privilege level you can use the `acquire_priv` m
should handle much of this for you.
```python
-from nssh.driver.core import IOSXEDriver
+from scrapli.driver.core import IOSXEDriver
my_device = {"host": "172.18.0.11", "auth_username": "vrnetlab", "auth_password": "VR-netlab9"}
@@ -324,7 +355,7 @@ with IOSXEDriver(**my_device) as conn:
## Sending Configurations
-When using the native mode (`NSSH` object), sending configurations is no different than sending commands and is done via
+When using the native mode (`Scrape` object), sending configurations is no different than sending commands and is done via
the `send_inputs` method. You must manually ensure you are in the correct privilege/mode.
When using any of the core drivers or the base `NetworkDriver`, you can send configurations via the `send_configs` method
@@ -332,27 +363,27 @@ When using any of the core drivers or the base `NetworkDriver`, you can send con
send a single string or a list/tuple of strings.
```python
-from nssh.driver.core import IOSXEDriver
+from scrapli.driver.core import IOSXEDriver
my_device = {"host": "172.18.0.11", "auth_username": "vrnetlab", "auth_password": "VR-netlab9"}
with IOSXEDriver(**my_device) as conn:
- conn.send_configs(("interface loopback123", "description configured by nssh"))
+ conn.send_configs(("interface loopback123", "description configured by scrapli"))
```
## TextFSM/NTC-Templates Integration
-nssh supports parsing output with TextFSM. This of course requires installing TextFSM and having ntc-templates
+scrapli supports parsing output with TextFSM. This of course requires installing TextFSM and having ntc-templates
somewhere on your system. When using a driver you can pass `textfsm=True` to the `send_commands` method to
- automatically try to parse all output. Parsed/structured output is stored in the `Result` object in the
+ automatically try to parse all output. Parsed/structured output is stored in the `Response` object in the
`structured_result` attribute. Alternatively you can use the `textfsm_parse_output` method of the driver to parse
output in a more manual fashion. This method accepts the string command (channel_input) and the text result and
returns structured data; the driver is already configured with the ntc-templates device type to find the correct
template.
```python
-from nssh.driver.core import IOSXEDriver
+from scrapli.driver.core import IOSXEDriver
my_device = {"host": "172.18.0.11", "auth_username": "vrnetlab", "auth_password": "VR-netlab9"}
@@ -364,14 +395,14 @@ with IOSXEDriver(**my_device) as conn:
structured_output = conn.textfsm_parse_output("show version", results[0].result)
```
-nssh also supports passing in templates manually (meaning not using the pip installed ntc-templates directory to
- find templates) if desired. The `nssh.helper.textfsm_parse` function accepts a string or loaded (TextIOWrapper
+scrapli also supports passing in templates manually (meaning not using the pip installed ntc-templates directory to
+ find templates) if desired. The `scrapli.helper.textfsm_parse` function accepts a string or loaded (TextIOWrapper
) template and output to parse. This can be useful if you have custom or one off templates or don't want to pip
install ntc-templates.
```python
-from nssh.driver.core import IOSXEDriver
-from nssh.helper import textfsm_parse
+from scrapli.driver.core import IOSXEDriver
+from scrapli.helper import textfsm_parse
my_device = {"host": "172.18.0.11", "auth_username": "vrnetlab", "auth_password": "VR-netlab9"}
@@ -385,7 +416,7 @@ with IOSXEDriver(**my_device) as conn:
## Timeouts
-nssh supports several timeout options. The simplest is the `timeout_socket` which controls the timeout for... setting
+scrapli supports several timeout options. The simplest is the `timeout_socket` which controls the timeout for... setting
up the underlying socket in seconds. Value should be a positive, non-zero number, however ssh2 and paramiko
transport options support floats.
@@ -398,16 +429,16 @@ Finally, `timeout_ops` sets a timeout value for individual operations -- or put
## Disabling Paging
-nssh native driver attempts to send `terminal length 0` to disable paging by default. In the future this will
+scrapli native driver attempts to send `terminal length 0` to disable paging by default. In the future this will
likely be removed and relegated to the device drivers only. For all drivers, there is a standard disable paging
string already configured for you, however this is of course user configurable. In addition to passing a string to
- send to disable paging, nssh supports passing a callable. This callable should accept the drivers reference to
+ send to disable paging, scrapli supports passing a callable. This callable should accept the drivers reference to
self as the only argument. This allows for users to create a custom function to disable paging however they like
. This callable option is supported on the native driver as well. In general it is probably a better idea to
handle this by simply passing a string, but the goal is to be flexible so the callable is supported.
```python
-from nssh.driver.core import IOSXEDriver
+from scrapli.driver.core import IOSXEDriver
def iosxe_disable_paging(cls):
cls.send_commands("term length 0")
@@ -422,23 +453,23 @@ with IOSXEDriver(**my_device) as conn:
## Login Handlers
Some devices have additional prompts or banners at login. This generally causes issues for SSH screen scraping
- automation. nssh supports -- just like disable paging -- passing a string to send or a callable to execute after
+ automation. scrapli supports -- just like disable paging -- passing a string to send or a callable to execute after
successful SSH connection but before disabling paging occurs. By default this is an empty string which does nothing.
## SSH Config Support
-nssh supports using OpenSSH configuration files in a few ways. For "system" SSH driver, passing a path to a config
- file will simply make nssh "point" to that file, and therefore use that configuration files attributes (because it
- is just exec'ing system SSH!). Soon SSH support that exists in ssh2net will be ported over to nssh for ssh2-python
+scrapli supports using OpenSSH configuration files in a few ways. For "system" SSH driver, passing a path to a config
+ file will simply make scrapli "point" to that file, and therefore use that configuration files attributes (because it
+ is just exec'ing system SSH!). Soon SSH support that exists in ssh2net will be ported over to scrapli for ssh2-python
and paramiko transport drivers.
-*NOTE* -- when using the system (default) SSH transport driver nssh does NOT disable strict host checking by default
+*NOTE* -- when using the system (default) SSH transport driver scrapli does NOT disable strict host checking by default
. Obviously this is the "smart" behavior, but it can be overridden on a per host basis in your SSH config file, or by
passing `False` to the "auth_strict_key" argument on object instantiation.
```python
-from nssh.driver.core import IOSXEDriver
+from scrapli.driver.core import IOSXEDriver
my_device = {"host": "172.18.0.11", "ssh_config_file": "~/mysshconfig", "auth_strict_key": False, "auth_password": "VR-netlab9"}
@@ -450,10 +481,10 @@ with IOSXEDriver(**my_device) as conn:
# FAQ
- Question: Why build this? Netmiko exists, Paramiko exists, Ansible exists, etc...?
- - Answer: I built ssh2net to learn -- to have a goal/target for writing some code. nssh is an evolution of the
+ - Answer: I built ssh2net to learn -- to have a goal/target for writing some code. scrapli is an evolution of the
lessons learned building ssh2net. About mid-way through building ssh2net I realized it may actually be kinda good
- at doing... stuff. So, sure there are other tools out there, but I think nssh its pretty snazzy and fills in some
- of the gaps in other tools. For example nssh is 100% compliant with strict mypy type checking, very uniformly
+ at doing... stuff. So, sure there are other tools out there, but I think scrapli its pretty snazzy and fills in some
+ of the gaps in other tools. For example scrapli is 100% compliant with strict mypy type checking, very uniformly
documented/linted, contains a results object for every operation, is very very fast, is very flexible, and in
general pretty awesome! Finally, while I think in general that SSH "screen scraping" is not "sexy" or even
"good", it is the lowest common denominator for automation in the networking world. So I figured I could try
@@ -467,7 +498,7 @@ with IOSXEDriver(**my_device) as conn:
" for you like Netmiko does for example, so its a lot more like Paramiko in that regard. That said you can use one
of the available drivers to have a more Netmiko-like experience -OR- write your own driver as this has been built
with the thought of being easily extended.
-- Why do I get a "conn (or your object name here) has no attribute channel" exception when using the base `NSSH` or
+- Why do I get a "conn (or your object name here) has no attribute channel" exception when using the base `Scrape` or
`NetworkDriver` objects?
- Answer: Those objects do not "auto open", and the channel attribute is not assigned until opening the connection
. Call `conn.open()` (or your object name in place of conn) to open the session and assign the channel attribute.
@@ -491,7 +522,7 @@ with IOSXEDriver(**my_device) as conn:
# Linting and Testing
-*NOTE* Currently there are no unit/functional tests for IOSXR/NXOS/EOS/Junos, however as this part of nssh is largely
+*NOTE* Currently there are no unit/functional tests for IOSXR/NXOS/EOS/Junos, however as this part of scrapli is largely
a port of ssh2net, they should work :)
## Linting
@@ -547,22 +578,23 @@ Executing the functional tests is a bit more complicated! First, thank you to Kr
Basic functional tests exist for all "core" platform types (IOSXE, NXOS, IOSXR, EOS, Junos). Vrnetlab currently only supports the older emulation style NX-OS devices, and *not* the newer VM image n9kv. I have made some very minor tweaks to vrnetlab locally in order to get the n9kv image running -- I have raised a PR to add this to vrnetlab proper. Minus the n9kv tweaks, getting going with vrnetlab is fairly straightforward -- simply follow Kristian's great readme docs. For the Arista EOS image -- prior to creating the container you should boot the device and enter the `zerotouch disable` command. This allows for the config to actually be saved and prevents the interfaces from cycling through interface types in the container (I'm not clear why it does that but executing this command before building the container "fixes" this!). After creating the image(s) that you wish to test, rename the image to the following format:
```
-nssh[PLATFORM]
+scrapli[PLATFORM]
```
The docker-compose file here will be looking for the container images matching this pattern, so this is an important bit! The container image names should be:
```
-nsshciscoiosxe
-nsshcisconxos
-nsshciscoiosxr
-nsshciscojunos
+scrapliciscoiosxe
+scraplicisconxos
+scrapliciscoiosxr
+scrapliaristaeos
+scraplijuniperjunos
```
You can tag the image names on creation (following the vrnetlab readme docs), or create a new tag once the image is built:
```
-docker tag [TAG OF IMAGE CREATED] nssh[VENDOR][OS]
+docker tag [TAG OF IMAGE CREATED] scrapli[VENDOR][OS]
```
*NOTE* I have added vty lines 5-98 on the CSR image -- I think the connections opening/closing so quickly during
diff --git a/docker-compose.yaml b/docker-compose.yaml
index af93c0cc..9d87ffba 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -5,7 +5,7 @@ services:
iosxe:
hostname: cisco_iosxe
privileged: true
- image: nsshciscoiosxe
+ image: scrapliciscoiosxe
networks:
net1:
ipv4_address: 172.18.0.11
@@ -13,7 +13,7 @@ services:
nxos:
hostname: cisco_nxos
privileged: true
- image: nsshcisconxos
+ image: scraplicisconxos
networks:
net1:
ipv4_address: 172.18.0.12
@@ -21,7 +21,7 @@ services:
iosxr:
hostname: cisco_iosxr
privileged: true
- image: nsshciscoiosxr
+ image: scrapliciscoiosxr
networks:
net1:
ipv4_address: 172.18.0.13
@@ -29,7 +29,7 @@ services:
eos:
hostname: arista_eos
privileged: true
- image: nssharistaeos
+ image: scrapliaristaeos
networks:
net1:
ipv4_address: 172.18.0.14
@@ -37,7 +37,7 @@ services:
junos:
hostname: juniper_junos
privileged: true
- image: nsshjuniperjunos
+ image: scraplijuniperjunos
networks:
net1:
ipv4_address: 172.18.0.15
diff --git a/docs/nssh/driver/core/index.html b/docs/nssh/driver/core/index.html
deleted file mode 100644
index 3eaa8e8e..00000000
--- a/docs/nssh/driver/core/index.html
+++ /dev/null
@@ -1,487 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/docs/nssh/channel/channel.html b/docs/scrapli/channel/channel.html
similarity index 75%
rename from docs/nssh/channel/channel.html
rename to docs/scrapli/channel/channel.html
index cbdc9670..428515b7 100644
--- a/docs/nssh/channel/channel.html
+++ b/docs/scrapli/channel/channel.html
@@ -4,8 +4,8 @@
-nssh.channel.channel API documentation
-
+scrapli.channel.channel API documentation
+
@@ -17,23 +17,23 @@
-
Module nssh.channel.channel
+
Module scrapli.channel.channel
-
nssh.channel.channel
+
scrapli.channel.channel
Expand source code
-
"""nssh.channel.channel"""
+
"""scrapli.channel.channel"""
import re
from logging import getLogger
from typing import List, Tuple, Union
-from nssh.decorators import operation_timeout
-from nssh.helper import get_prompt_pattern, normalize_lines, strip_ansi
-from nssh.result import Result
-from nssh.transport.transport import Transport
+from scrapli.decorators import operation_timeout
+from scrapli.helper import get_prompt_pattern, normalize_lines, strip_ansi
+from scrapli.response import Response
+from scrapli.transport.transport import Transport
LOG = getLogger("channel")
@@ -64,15 +64,16 @@
Module nssh.channel.channel
comms_prompt_pattern: raw string regex pattern -- use `^` and `$` for multi-line!
comms_return_char: character to use to send returns to host
comms_ansi: True/False strip comms_ansi characters from output
+ timeout_ops: timeout in seconds for channel operations (reads/writes)
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ N/A
"""
self.transport: Transport = transport
@@ -86,34 +87,34 @@
def send_inputs_interact(
self, inputs: Union[List[str], Tuple[str, str, str, str]], hidden_response: bool = False,
- ) -> List[Result]:
+ ) -> List[Response]:
"""
- Send inputs in an interactive fashion; used to handle prompts
-
- accepts inputs and looks for expected prompt;
- sends the appropriate response, then waits for the "finale"
- returns the results of the interaction
-
- could be "chained" together to respond to more than a "single" staged prompt
+ Send inputs in an interactive fashion, used to handle prompts that occur after an input.
Args:
- inputs: list or tuple containing strings representing:
- initial input
- expectation (what should nssh expect after input)
- response (response to expectation)
- finale (what should nssh expect when "done")
+ inputs: list or tuple containing strings representing
+ channel_input - initial input to send
+ expected_prompt - prompt to expect after initial input
+ response - response to prompt
+ final_prompt - final prompt to expect
hidden_response: True/False response is hidden (i.e. password input)
Returns:
- N/A # noqa
+ responses: list of scrapli Response objects
Raises:
- N/A # noqa
+ TypeError: if inputs is not tuple or list
"""
if not isinstance(inputs, (list, tuple)):
raise TypeError(f"send_inputs_interact expects a List or Tuple, got {type(inputs)}")
input_stages = (inputs,)
- results = []
- for channel_input, expectation, response, finale in input_stages:
- result = Result(self.transport.host, channel_input, expectation, response, finale)
+ responses = []
+ for channel_input, expectation, channel_response, finale in input_stages:
+ response = Response(
+ self.transport.host, channel_input, expectation, channel_response, finale
+ )
raw_result, processed_result = self._send_input_interact(
- channel_input, expectation, response, finale, hidden_response
+ channel_input, expectation, channel_response, finale, hidden_response
)
- result.raw_result = raw_result.decode()
- result.record_result(processed_result.decode().strip())
- results.append(result)
- return results
+ response.raw_result = raw_result.decode()
+ response.record_response(processed_result.decode().strip())
+ responses.append(response)
+ return responses
@operation_timeout("timeout_ops")
def _send_input_interact(
self,
channel_input: str,
expectation: str,
- response: str,
+ channel_response: str,
finale: str,
hidden_response: bool = False,
) -> Tuple[bytes, bytes]:
"""
- Respond to a single "staged" prompt and return results
+ Respond to a single "staged" prompt and return results.
Args:
channel_input: string input to write to channel
expectation: string of what to expect from channel
- response: string what to respond to the "expectation"
- finale: string of prompt to look for to know when "done"
+ channel_response: string what to respond to the `expectation`, or empty string to send
+ return character only
+ finale: string of prompt to look for to know when `done`
hidden_response: True/False response is hidden (i.e. password input)
Returns:
output: output read from the channel
Raises:
- N/A # noqa
+ N/A
"""
bytes_channel_input = channel_input.encode()
self.transport.session_lock.acquire()
- self.transport.flush()
LOG.debug(
f"Attempting to send input interact: {channel_input}; "
f"\texpecting: {expectation};"
- f"\tresponding: {response};"
+ f"\tresponding: {channel_response};"
f"\twith a finale: {finale};"
f"\thidden_response: {hidden_response}"
)
@@ -380,11 +375,11 @@
Module nssh.channel.channel
output = self._read_until_prompt(prompt=expectation)
# if response is simply a return; add that so it shows in output likewise if response is
# "hidden" (i.e. password input), add return, otherwise, skip
- if not response:
+ if not channel_response:
output += self.comms_return_char.encode()
elif hidden_response is True:
output += self.comms_return_char.encode()
- self.transport.write(response)
+ self.transport.write(channel_response)
LOG.debug(f"Write: {repr(channel_input)}")
self._send_return()
LOG.debug(f"Write (sending return character): {repr(self.comms_return_char)}")
@@ -398,16 +393,15 @@
class Channel(transport, comms_prompt_pattern='^[a-z0-9.\\-@()/:]{1,32}[#>$]$', comms_return_char='\n', comms_ansi=False, timeout_ops=10)
@@ -437,20 +431,20 @@
Args
character to use to send returns to host
comms_ansi
True/False strip comms_ansi characters from output
+
timeout_ops
+
timeout in seconds for channel operations (reads/writes)
Args
-
N/A
-# noqa
+
N/A
Returns
N/A
-# noqa
+# noqa: DAR202
Raises
-
N/A
-# noqa
+
N/A
@@ -474,15 +468,16 @@
Raises
comms_prompt_pattern: raw string regex pattern -- use `^` and `$` for multi-line!
comms_return_char: character to use to send returns to host
comms_ansi: True/False strip comms_ansi characters from output
+ timeout_ops: timeout in seconds for channel operations (reads/writes)
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ N/A
"""
self.transport: Transport = transport
@@ -496,34 +491,34 @@
def send_inputs_interact(
self, inputs: Union[List[str], Tuple[str, str, str, str]], hidden_response: bool = False,
- ) -> List[Result]:
+ ) -> List[Response]:
"""
- Send inputs in an interactive fashion; used to handle prompts
-
- accepts inputs and looks for expected prompt;
- sends the appropriate response, then waits for the "finale"
- returns the results of the interaction
-
- could be "chained" together to respond to more than a "single" staged prompt
+ Send inputs in an interactive fashion, used to handle prompts that occur after an input.
Args:
- inputs: list or tuple containing strings representing:
- initial input
- expectation (what should nssh expect after input)
- response (response to expectation)
- finale (what should nssh expect when "done")
+ inputs: list or tuple containing strings representing
+ channel_input - initial input to send
+ expected_prompt - prompt to expect after initial input
+ response - response to prompt
+ final_prompt - final prompt to expect
hidden_response: True/False response is hidden (i.e. password input)
Returns:
- N/A # noqa
+ responses: list of scrapli Response objects
Raises:
- N/A # noqa
+ TypeError: if inputs is not tuple or list
"""
if not isinstance(inputs, (list, tuple)):
raise TypeError(f"send_inputs_interact expects a List or Tuple, got {type(inputs)}")
input_stages = (inputs,)
- results = []
- for channel_input, expectation, response, finale in input_stages:
- result = Result(self.transport.host, channel_input, expectation, response, finale)
+ responses = []
+ for channel_input, expectation, channel_response, finale in input_stages:
+ response = Response(
+ self.transport.host, channel_input, expectation, channel_response, finale
+ )
raw_result, processed_result = self._send_input_interact(
- channel_input, expectation, response, finale, hidden_response
+ channel_input, expectation, channel_response, finale, hidden_response
)
- result.raw_result = raw_result.decode()
- result.record_result(processed_result.decode().strip())
- results.append(result)
- return results
+ response.raw_result = raw_result.decode()
+ response.record_response(processed_result.decode().strip())
+ responses.append(response)
+ return responses
@operation_timeout("timeout_ops")
def _send_input_interact(
self,
channel_input: str,
expectation: str,
- response: str,
+ channel_response: str,
finale: str,
hidden_response: bool = False,
) -> Tuple[bytes, bytes]:
"""
- Respond to a single "staged" prompt and return results
+ Respond to a single "staged" prompt and return results.
Args:
channel_input: string input to write to channel
expectation: string of what to expect from channel
- response: string what to respond to the "expectation"
- finale: string of prompt to look for to know when "done"
+ channel_response: string what to respond to the `expectation`, or empty string to send
+ return character only
+ finale: string of prompt to look for to know when `done`
hidden_response: True/False response is hidden (i.e. password input)
Returns:
output: output read from the channel
Raises:
- N/A # noqa
+ N/A
"""
bytes_channel_input = channel_input.encode()
self.transport.session_lock.acquire()
- self.transport.flush()
LOG.debug(
f"Attempting to send input interact: {channel_input}; "
f"\texpecting: {expectation};"
- f"\tresponding: {response};"
+ f"\tresponding: {channel_response};"
f"\twith a finale: {finale};"
f"\thidden_response: {hidden_response}"
)
@@ -790,11 +779,11 @@
Raises
output = self._read_until_prompt(prompt=expectation)
# if response is simply a return; add that so it shows in output likewise if response is
# "hidden" (i.e. password input), add return, otherwise, skip
- if not response:
+ if not channel_response:
output += self.comms_return_char.encode()
elif hidden_response is True:
output += self.comms_return_char.encode()
- self.transport.write(response)
+ self.transport.write(channel_response)
LOG.debug(f"Write: {repr(channel_input)}")
self._send_return()
LOG.debug(f"Write (sending return character): {repr(self.comms_return_char)}")
@@ -808,22 +797,21 @@
Send inputs in an interactive fashion; used to handle prompts
-
accepts inputs and looks for expected prompt;
-sends the appropriate response, then waits for the "finale"
-returns the results of the interaction
-
could be "chained" together to respond to more than a "single" staged prompt
+
Send inputs in an interactive fashion, used to handle prompts that occur after an input.
Args
inputs
-
list or tuple containing strings representing:
-initial input
-expectation (what should nssh expect after input)
-response (response to expectation)
-finale (what should nssh expect when "done")
+
list or tuple containing strings representing
+channel_input - initial input to send
+expected_prompt - prompt to expect after initial input
+response - response to prompt
+final_prompt - final prompt to expect
hidden_response
True/False response is hidden (i.e. password input)
Returns
-
N/A
-# noqa
-
+
responses
+
list of scrapli Response objects
Raises
-
N/A
-# noqa
-
+
TypeError
+
if inputs is not tuple or list
@@ -944,44 +925,40 @@
Raises
def send_inputs_interact(
self, inputs: Union[List[str], Tuple[str, str, str, str]], hidden_response: bool = False,
-) -> List[Result]:
+) -> List[Response]:
"""
- Send inputs in an interactive fashion; used to handle prompts
-
- accepts inputs and looks for expected prompt;
- sends the appropriate response, then waits for the "finale"
- returns the results of the interaction
-
- could be "chained" together to respond to more than a "single" staged prompt
+ Send inputs in an interactive fashion, used to handle prompts that occur after an input.
Args:
- inputs: list or tuple containing strings representing:
- initial input
- expectation (what should nssh expect after input)
- response (response to expectation)
- finale (what should nssh expect when "done")
+ inputs: list or tuple containing strings representing
+ channel_input - initial input to send
+ expected_prompt - prompt to expect after initial input
+ response - response to prompt
+ final_prompt - final prompt to expect
hidden_response: True/False response is hidden (i.e. password input)
Returns:
- N/A # noqa
+ responses: list of scrapli Response objects
Raises:
- N/A # noqa
+ TypeError: if inputs is not tuple or list
"""
if not isinstance(inputs, (list, tuple)):
raise TypeError(f"send_inputs_interact expects a List or Tuple, got {type(inputs)}")
input_stages = (inputs,)
- results = []
- for channel_input, expectation, response, finale in input_stages:
- result = Result(self.transport.host, channel_input, expectation, response, finale)
+ responses = []
+ for channel_input, expectation, channel_response, finale in input_stages:
+ response = Response(
+ self.transport.host, channel_input, expectation, channel_response, finale
+ )
raw_result, processed_result = self._send_input_interact(
- channel_input, expectation, response, finale, hidden_response
+ channel_input, expectation, channel_response, finale, hidden_response
)
- result.raw_result = raw_result.decode()
- result.record_result(processed_result.decode().strip())
- results.append(result)
- return results
class Channel(transport, comms_prompt_pattern='^[a-z0-9.\\-@()/:]{1,32}[#>$]$', comms_return_char='\n', comms_ansi=False, timeout_ops=10)
@@ -63,20 +63,20 @@
Args
character to use to send returns to host
comms_ansi
True/False strip comms_ansi characters from output
+
timeout_ops
+
timeout in seconds for channel operations (reads/writes)
Args
-
N/A
-# noqa
+
N/A
Returns
N/A
-# noqa
+# noqa: DAR202
Raises
-
N/A
-# noqa
+
N/A
@@ -100,15 +100,16 @@
Raises
comms_prompt_pattern: raw string regex pattern -- use `^` and `$` for multi-line!
comms_return_char: character to use to send returns to host
comms_ansi: True/False strip comms_ansi characters from output
+ timeout_ops: timeout in seconds for channel operations (reads/writes)
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ N/A
"""
self.transport: Transport = transport
@@ -122,34 +123,34 @@
def send_inputs_interact(
self, inputs: Union[List[str], Tuple[str, str, str, str]], hidden_response: bool = False,
- ) -> List[Result]:
+ ) -> List[Response]:
"""
- Send inputs in an interactive fashion; used to handle prompts
-
- accepts inputs and looks for expected prompt;
- sends the appropriate response, then waits for the "finale"
- returns the results of the interaction
-
- could be "chained" together to respond to more than a "single" staged prompt
+ Send inputs in an interactive fashion, used to handle prompts that occur after an input.
Args:
- inputs: list or tuple containing strings representing:
- initial input
- expectation (what should nssh expect after input)
- response (response to expectation)
- finale (what should nssh expect when "done")
+ inputs: list or tuple containing strings representing
+ channel_input - initial input to send
+ expected_prompt - prompt to expect after initial input
+ response - response to prompt
+ final_prompt - final prompt to expect
hidden_response: True/False response is hidden (i.e. password input)
Returns:
- N/A # noqa
+ responses: list of scrapli Response objects
Raises:
- N/A # noqa
+ TypeError: if inputs is not tuple or list
"""
if not isinstance(inputs, (list, tuple)):
raise TypeError(f"send_inputs_interact expects a List or Tuple, got {type(inputs)}")
input_stages = (inputs,)
- results = []
- for channel_input, expectation, response, finale in input_stages:
- result = Result(self.transport.host, channel_input, expectation, response, finale)
+ responses = []
+ for channel_input, expectation, channel_response, finale in input_stages:
+ response = Response(
+ self.transport.host, channel_input, expectation, channel_response, finale
+ )
raw_result, processed_result = self._send_input_interact(
- channel_input, expectation, response, finale, hidden_response
+ channel_input, expectation, channel_response, finale, hidden_response
)
- result.raw_result = raw_result.decode()
- result.record_result(processed_result.decode().strip())
- results.append(result)
- return results
+ response.raw_result = raw_result.decode()
+ response.record_response(processed_result.decode().strip())
+ responses.append(response)
+ return responses
@operation_timeout("timeout_ops")
def _send_input_interact(
self,
channel_input: str,
expectation: str,
- response: str,
+ channel_response: str,
finale: str,
hidden_response: bool = False,
) -> Tuple[bytes, bytes]:
"""
- Respond to a single "staged" prompt and return results
+ Respond to a single "staged" prompt and return results.
Args:
channel_input: string input to write to channel
expectation: string of what to expect from channel
- response: string what to respond to the "expectation"
- finale: string of prompt to look for to know when "done"
+ channel_response: string what to respond to the `expectation`, or empty string to send
+ return character only
+ finale: string of prompt to look for to know when `done`
hidden_response: True/False response is hidden (i.e. password input)
Returns:
output: output read from the channel
Raises:
- N/A # noqa
+ N/A
"""
bytes_channel_input = channel_input.encode()
self.transport.session_lock.acquire()
- self.transport.flush()
LOG.debug(
f"Attempting to send input interact: {channel_input}; "
f"\texpecting: {expectation};"
- f"\tresponding: {response};"
+ f"\tresponding: {channel_response};"
f"\twith a finale: {finale};"
f"\thidden_response: {hidden_response}"
)
@@ -416,11 +411,11 @@
Raises
output = self._read_until_prompt(prompt=expectation)
# if response is simply a return; add that so it shows in output likewise if response is
# "hidden" (i.e. password input), add return, otherwise, skip
- if not response:
+ if not channel_response:
output += self.comms_return_char.encode()
elif hidden_response is True:
output += self.comms_return_char.encode()
- self.transport.write(response)
+ self.transport.write(channel_response)
LOG.debug(f"Write: {repr(channel_input)}")
self._send_return()
LOG.debug(f"Write (sending return character): {repr(self.comms_return_char)}")
@@ -434,22 +429,21 @@
Send inputs in an interactive fashion; used to handle prompts
-
accepts inputs and looks for expected prompt;
-sends the appropriate response, then waits for the "finale"
-returns the results of the interaction
-
could be "chained" together to respond to more than a "single" staged prompt
+
Send inputs in an interactive fashion, used to handle prompts that occur after an input.
Args
inputs
-
list or tuple containing strings representing:
-initial input
-expectation (what should nssh expect after input)
-response (response to expectation)
-finale (what should nssh expect when "done")
+
list or tuple containing strings representing
+channel_input - initial input to send
+expected_prompt - prompt to expect after initial input
+response - response to prompt
+final_prompt - final prompt to expect
hidden_response
True/False response is hidden (i.e. password input)
Returns
-
N/A
-# noqa
-
+
responses
+
list of scrapli Response objects
Raises
-
N/A
-# noqa
-
+
TypeError
+
if inputs is not tuple or list
@@ -570,44 +557,40 @@
Raises
def send_inputs_interact(
self, inputs: Union[List[str], Tuple[str, str, str, str]], hidden_response: bool = False,
-) -> List[Result]:
+) -> List[Response]:
"""
- Send inputs in an interactive fashion; used to handle prompts
-
- accepts inputs and looks for expected prompt;
- sends the appropriate response, then waits for the "finale"
- returns the results of the interaction
-
- could be "chained" together to respond to more than a "single" staged prompt
+ Send inputs in an interactive fashion, used to handle prompts that occur after an input.
Args:
- inputs: list or tuple containing strings representing:
- initial input
- expectation (what should nssh expect after input)
- response (response to expectation)
- finale (what should nssh expect when "done")
+ inputs: list or tuple containing strings representing
+ channel_input - initial input to send
+ expected_prompt - prompt to expect after initial input
+ response - response to prompt
+ final_prompt - final prompt to expect
hidden_response: True/False response is hidden (i.e. password input)
Returns:
- N/A # noqa
+ responses: list of scrapli Response objects
Raises:
- N/A # noqa
+ TypeError: if inputs is not tuple or list
"""
if not isinstance(inputs, (list, tuple)):
raise TypeError(f"send_inputs_interact expects a List or Tuple, got {type(inputs)}")
input_stages = (inputs,)
- results = []
- for channel_input, expectation, response, finale in input_stages:
- result = Result(self.transport.host, channel_input, expectation, response, finale)
+ responses = []
+ for channel_input, expectation, channel_response, finale in input_stages:
+ response = Response(
+ self.transport.host, channel_input, expectation, channel_response, finale
+ )
raw_result, processed_result = self._send_input_interact(
- channel_input, expectation, response, finale, hidden_response
+ channel_input, expectation, channel_response, finale, hidden_response
)
- result.raw_result = raw_result.decode()
- result.record_result(processed_result.decode().strip())
- results.append(result)
- return results
session_pre_login_handler: Union[str, Callable[..., Any]] = "",
session_disable_paging: Union[str, Callable[..., Any]] = "terminal length 0",
ssh_config_file: Union[str, bool] = True,
- driver: str = "system",
+ transport: str = "system",
):
"""
- NSSH Object
+ Scrape Object
- NSSH is the base class for NetworkDriver, and subsequent platform specific drivers
- (i.e. IOSXEDriver). NSSH can be used on its own and offers a semi-pexpect like experience in
+ Scrape is the base class for NetworkDriver, and subsequent platform specific drivers (i.e.
+ IOSXEDriver). Scrape can be used on its own and offers a semi-pexpect like experience in
that it doesn't know or care about privilege levels, platform types, and things like that.
Args:
@@ -97,7 +101,7 @@
Module nssh.driver.driver
timeout_ops: timeout for ssh channel operations
comms_prompt_pattern: raw string regex pattern -- preferably use `^` and `$` anchors!
this is the single most important attribute here! if this does not match a prompt,
- nssh will not work!
+ scrapli will not work!
IMPORTANT: regex search uses multi-line + case insensitive flags. multi-line allows
for highly reliably matching for prompts after stripping trailing white space,
case insensitive is just a convenience factor so i can be lazy.
@@ -109,18 +113,19 @@
Module nssh.driver.driver
string to send to device to disable paging
ssh_config_file: string to path for ssh config file, True to use default ssh config file
or False to ignore default ssh config file
- driver: system|ssh2|paramiko -- type of ssh driver to use
+ transport: system|ssh2|paramiko|telnet -- type of transport to use
system uses system available ssh (/usr/bin/ssh)
ssh2 uses ssh2-python
paramiko uses... paramiko
+ telnet uses telnetlib
choice of driver depends on the features you need. in general system is easiest as
it will just "auto-magically" use your ssh config file (~/.ssh/config or
/etc/ssh/config_file). ssh2 is very very fast as it is a thin wrapper around libssh2
however it is slightly feature limited. paramiko is slower than ssh2, but has more
- features built in (though nssh does not expose/support them all).
+ features built in (though scrapli does not expose/support them all).
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
TypeError: if auth_strict_key is not a bool
@@ -156,10 +161,12 @@
Module nssh.driver.driver
raise TypeError(f"ssh_config_file should be str or bool, got {type(ssh_config_file)}")
self.ssh_config_file = ssh_config_file
- if driver not in ("ssh2", "paramiko", "system"):
- raise ValueError(f"transport should be one of ssh2|paramiko|system, got {driver}")
+ if transport not in ("ssh2", "paramiko", "system", "telnet"):
+ raise ValueError(
+ f"transport should be one of ssh2|paramiko|system|telnet, got {transport}"
+ )
self.transport: Transport
- self.transport_class, self.transport_args = self._transport_factory(driver)
+ self.transport_class, self.transport_args = self._transport_factory(transport)
self.channel: Channel
self.channel_args = {}
@@ -168,18 +175,18 @@
NSSH is the base class for NetworkDriver, and subsequent platform specific drivers
-(i.e. IOSXEDriver). NSSH can be used on its own and offers a semi-pexpect like experience in
+
Scrape Object
+
Scrape is the base class for NetworkDriver, and subsequent platform specific drivers (i.e.
+IOSXEDriver). Scrape can be used on its own and offers a semi-pexpect like experience in
that it doesn't know or care about privilege levels, platform types, and things like that.
Args
@@ -511,7 +518,7 @@
Args
comms_prompt_pattern
raw string regex pattern – preferably use ^ and $ anchors!
this is the single most important attribute here! if this does not match a prompt,
-nssh will not work!
+scrapli will not work!
IMPORTANT: regex search uses multi-line + case insensitive flags. multi-line allows
for highly reliably matching for prompts after stripping trailing white space,
case insensitive is just a convenience factor so i can be lazy.
@@ -528,21 +535,22 @@
Args
ssh_config_file
string to path for ssh config file, True to use default ssh config file
or False to ignore default ssh config file
-
driver
-
system|ssh2|paramiko – type of ssh driver to use
+
transport
+
system|ssh2|paramiko|telnet – type of transport to use
system uses system available ssh (/usr/bin/ssh)
ssh2 uses ssh2-python
paramiko uses… paramiko
+telnet uses telnetlib
choice of driver depends on the features you need. in general system is easiest as
it will just "auto-magically" use your ssh config file (~/.ssh/config or
/etc/ssh/config_file). ssh2 is very very fast as it is a thin wrapper around libssh2
however it is slightly feature limited. paramiko is slower than ssh2, but has more
-features built in (though nssh does not expose/support them all).
+features built in (though scrapli does not expose/support them all).
session_pre_login_handler: Union[str, Callable[..., Any]] = "",
session_disable_paging: Union[str, Callable[..., Any]] = "terminal length 0",
ssh_config_file: Union[str, bool] = True,
- driver: str = "system",
+ transport: str = "system",
):
"""
- NSSH Object
+ Scrape Object
- NSSH is the base class for NetworkDriver, and subsequent platform specific drivers
- (i.e. IOSXEDriver). NSSH can be used on its own and offers a semi-pexpect like experience in
+ Scrape is the base class for NetworkDriver, and subsequent platform specific drivers (i.e.
+ IOSXEDriver). Scrape can be used on its own and offers a semi-pexpect like experience in
that it doesn't know or care about privilege levels, platform types, and things like that.
Args:
@@ -595,7 +603,7 @@
Raises
timeout_ops: timeout for ssh channel operations
comms_prompt_pattern: raw string regex pattern -- preferably use `^` and `$` anchors!
this is the single most important attribute here! if this does not match a prompt,
- nssh will not work!
+ scrapli will not work!
IMPORTANT: regex search uses multi-line + case insensitive flags. multi-line allows
for highly reliably matching for prompts after stripping trailing white space,
case insensitive is just a convenience factor so i can be lazy.
@@ -607,18 +615,19 @@
Raises
string to send to device to disable paging
ssh_config_file: string to path for ssh config file, True to use default ssh config file
or False to ignore default ssh config file
- driver: system|ssh2|paramiko -- type of ssh driver to use
+ transport: system|ssh2|paramiko|telnet -- type of transport to use
system uses system available ssh (/usr/bin/ssh)
ssh2 uses ssh2-python
paramiko uses... paramiko
+ telnet uses telnetlib
choice of driver depends on the features you need. in general system is easiest as
it will just "auto-magically" use your ssh config file (~/.ssh/config or
/etc/ssh/config_file). ssh2 is very very fast as it is a thin wrapper around libssh2
however it is slightly feature limited. paramiko is slower than ssh2, but has more
- features built in (though nssh does not expose/support them all).
+ features built in (though scrapli does not expose/support them all).
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
TypeError: if auth_strict_key is not a bool
@@ -654,10 +663,12 @@
Raises
raise TypeError(f"ssh_config_file should be str or bool, got {type(ssh_config_file)}")
self.ssh_config_file = ssh_config_file
- if driver not in ("ssh2", "paramiko", "system"):
- raise ValueError(f"transport should be one of ssh2|paramiko|system, got {driver}")
+ if transport not in ("ssh2", "paramiko", "system", "telnet"):
+ raise ValueError(
+ f"transport should be one of ssh2|paramiko|system|telnet, got {transport}"
+ )
self.transport: Transport
- self.transport_class, self.transport_args = self._transport_factory(driver)
+ self.transport_class, self.transport_args = self._transport_factory(transport)
self.channel: Channel
self.channel_args = {}
@@ -666,18 +677,18 @@
NSSH is the base class for NetworkDriver, and subsequent platform specific drivers
-(i.e. IOSXEDriver). NSSH can be used on its own and offers a semi-pexpect like experience in
-that it doesn't know or care about privilege levels, platform types, and things like that.
+
BaseNetworkDriver Object
Args
-
host
-
host ip/name to connect to
-
port
-
port to connect to
-
auth_username
-
username for authentication
-
auth_public_key
-
path to public key for authentication
-
auth_password
-
password for authentication
-
auth_strict_key
-
strict host checking or not – applicable for system ssh driver only
-
timeout_socket
-
timeout for establishing socket in seconds
-
timeout_ssh
-
timeout for ssh transport in milliseconds
-
timeout_ops
-
timeout for ssh channel operations
-
comms_prompt_pattern
-
raw string regex pattern – preferably use ^ and $ anchors!
-this is the single most important attribute here! if this does not match a prompt,
-nssh will not work!
-IMPORTANT: regex search uses multi-line + case insensitive flags. multi-line allows
-for highly reliably matching for prompts after stripping trailing white space,
-case insensitive is just a convenience factor so i can be lazy.
-
comms_return_char
-
character to use to send returns to host
-
comms_ansi
-
True/False strip comms_ansi characters from output
-
session_pre_login_handler
-
callable or string that resolves to an importable function to
-handle pre-login (pre disable paging) operations
-
session_disable_paging
-
callable, string that resolves to an importable function, or
-string to send to device to disable paging
-
ssh_config_file
-
string to path for ssh config file, True to use default ssh config file
-or False to ignore default ssh config file
system|ssh2|paramiko – type of ssh driver to use
-system uses system available ssh (/usr/bin/ssh)
-ssh2 uses ssh2-python
-paramiko uses… paramiko
-choice of driver depends on the features you need. in general system is easiest as
-it will just "auto-magically" use your ssh config file (~/.ssh/config or
-/etc/ssh/config_file). ssh2 is very very fast as it is a thin wrapper around libssh2
-however it is slightly feature limited. paramiko is slower than ssh2, but has more
-features built in (though nssh does not expose/support them all).
+
auth_secondary
+
password to use for secondary authentication (enable)
Open Transport (socket/session) and establish channel
+
Send command(s)
Args
-
N/A
-# noqa
+
+
commands
+
string or list of strings to send to device in privilege exec mode
+
strip_prompt
+
True/False strip prompt from returned output
+
Returns
-
N/A
-# noqa
+
responses
+
list of Scrape Response objects
+
+
Raises
+
+
N/A
+
+
+
+Expand source code
+
+
def send_commands(
+ self, commands: Union[str, List[str]], strip_prompt: bool = True
+) -> List[Response]:
+ """
+ Send command(s)
+
+ Args:
+ commands: string or list of strings to send to device in privilege exec mode
+ strip_prompt: True/False strip prompt from returned output
+
+ Returns:
+ responses: list of Scrape Response objects
+
+ Raises:
+ N/A
+
+ """
+ self.acquire_priv(str(self.default_desired_priv))
+ responses = self.channel.send_inputs(commands, strip_prompt)
+
+ # update the response objects with textfsm platform; we do this here because the underlying
+ # channel doesn't know or care about platforms
+ for response in responses:
+ response.textfsm_platform = self.textfsm_platform
+
+ return responses
Send inputs in an interactive fashion; used to handle prompts
+
accepts inputs and looks for expected prompt;
+sends the appropriate response, then waits for the "finale"
+returns the results of the interaction
+
could be "chained" together to respond to more than a "single" staged prompt
+
Args
+
+
inputs
+
list or tuple containing strings representing
+channel_input - initial input to send
+expected_prompt - prompt to expect after initial input
+response - response to prompt
+final_prompt - final prompt to expect
+
hidden_response
+
True/False response is hidden (i.e. password input)
+
+
Returns
+
+
responses
+
List of Scrape Response objects
+
Raises
+
+
N/A
+
+
+
+
+Expand source code
+
+
def send_interactive(
+ self, inputs: Union[List[str], Tuple[str, str, str, str]], hidden_response: bool = False,
+) -> List[Response]:
+ """
+ Send inputs in an interactive fashion; used to handle prompts
+
+ accepts inputs and looks for expected prompt;
+ sends the appropriate response, then waits for the "finale"
+ returns the results of the interaction
+
+ could be "chained" together to respond to more than a "single" staged prompt
+
+ Args:
+ inputs: list or tuple containing strings representing
+ channel_input - initial input to send
+ expected_prompt - prompt to expect after initial input
+ response - response to prompt
+ final_prompt - final prompt to expect
+ hidden_response: True/False response is hidden (i.e. password input)
+
+ Returns:
+ responses: List of Scrape Response objects
+
+ Raises:
+ N/A
+
+ """
+ self.acquire_priv(str(self.default_desired_priv))
+ responses = self.channel.send_inputs_interact(inputs, hidden_response)
+ return responses
Scrape is the base class for NetworkDriver, and subsequent platform specific drivers (i.e.
+IOSXEDriver). Scrape can be used on its own and offers a semi-pexpect like experience in
+that it doesn't know or care about privilege levels, platform types, and things like that.
Args
-
auth_secondary
-
password to use for secondary authentication (enable)
-
**kwargs
-
keyword args to pass to inherited class(es)
+
host
+
host ip/name to connect to
+
port
+
port to connect to
+
auth_username
+
username for authentication
+
auth_public_key
+
path to public key for authentication
+
auth_password
+
password for authentication
+
auth_strict_key
+
strict host checking or not – applicable for system ssh driver only
+
timeout_socket
+
timeout for establishing socket in seconds
+
timeout_ssh
+
timeout for ssh transport in milliseconds
+
timeout_ops
+
timeout for ssh channel operations
+
comms_prompt_pattern
+
raw string regex pattern – preferably use ^ and $ anchors!
+this is the single most important attribute here! if this does not match a prompt,
+scrapli will not work!
+IMPORTANT: regex search uses multi-line + case insensitive flags. multi-line allows
+for highly reliably matching for prompts after stripping trailing white space,
+case insensitive is just a convenience factor so i can be lazy.
+
comms_return_char
+
character to use to send returns to host
+
comms_ansi
+
True/False strip comms_ansi characters from output
+
session_pre_login_handler
+
callable or string that resolves to an importable function to
+handle pre-login (pre disable paging) operations
+
session_disable_paging
+
callable, string that resolves to an importable function, or
+string to send to device to disable paging
+
ssh_config_file
+
string to path for ssh config file, True to use default ssh config file
+or False to ignore default ssh config file
+
transport
+
system|ssh2|paramiko|telnet – type of transport to use
+system uses system available ssh (/usr/bin/ssh)
+ssh2 uses ssh2-python
+paramiko uses… paramiko
+telnet uses telnetlib
+choice of driver depends on the features you need. in general system is easiest as
+it will just "auto-magically" use your ssh config file (~/.ssh/config or
+/etc/ssh/config_file). ssh2 is very very fast as it is a thin wrapper around libssh2
+however it is slightly feature limited. paramiko is slower than ssh2, but has more
+features built in (though scrapli does not expose/support them all).
def close(self) -> None:
"""
- Send command(s)
+ Close Transport (socket/session)
Args:
- commands: string or list of strings to send to device in privilege exec mode
- strip_prompt: True/False strip prompt from returned output
- textfsm: True/False try to parse each command with textfsm
+ N/A
Returns:
- results: list of SSH2NetResult objects
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ N/A
"""
- self.acquire_priv(str(self.default_desired_priv))
- results = self.channel.send_inputs(commands, strip_prompt)
- if not textfsm:
- return results
- for result in results:
- result.structured_result = self.textfsm_parse_output(
- result.channel_input, result.result
- )
- return results
Send inputs in an interactive fashion; used to handle prompts
-
accepts inputs and looks for expected prompt;
-sends the appropriate response, then waits for the "finale"
-returns the results of the interaction
-
could be "chained" together to respond to more than a "single" staged prompt
+
Open Transport (socket/session) and establish channel
Args
-
-
inputs
-
list or tuple containing strings representing:
-initial input
-expectation (what should nssh expect after input)
-response (response to expectation)
-finale (what should nssh expect when "done")
-
hidden_response
-
True/False response is hidden (i.e. password input)
-
+
N/A
Returns
N/A
-# noqa
-
-
-
Raises
-
-
N/A
-# noqa
+# noqa: DAR202
-
-
-
-Expand source code
-
-
def send_interactive(
- self, inputs: Union[List[str], Tuple[str, str, str, str]], hidden_response: bool = False,
-) -> List[Result]:
- """
- Send inputs in an interactive fashion; used to handle prompts
-
- accepts inputs and looks for expected prompt;
- sends the appropriate response, then waits for the "finale"
- returns the results of the interaction
-
- could be "chained" together to respond to more than a "single" staged prompt
-
- Args:
- inputs: list or tuple containing strings representing:
- initial input
- expectation (what should nssh expect after input)
- response (response to expectation)
- finale (what should nssh expect when "done")
- hidden_response: True/False response is hidden (i.e. password input)
-
- Returns:
- N/A # noqa
-
- Raises:
- N/A # noqa
-
- """
- self.acquire_priv(str(self.default_desired_priv))
- results = self.channel.send_inputs_interact(inputs, hidden_response)
- return results
Escalate to the next privilege level up
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ UnknownPrivLevel: if priv level cant be attained
+ TypeError: if invalid next prompt value
"""
current_priv = self._determine_current_priv(self.channel.get_prompt())
@@ -195,13 +166,13 @@
Module nssh.driver.network_driver
Deescalate to the next privilege level down
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ UnknownPrivLevel: if no default priv level set to deescalate to
"""
current_priv = self._determine_current_priv(self.channel.get_prompt())
@@ -220,10 +191,10 @@
Module nssh.driver.network_driver
Args:
desired_priv: string name of desired privilege level
- (see nssh.driver.<driver_category.device_type>.driver for levels)
+ (see scrapli.driver.<driver_category.device_type>.driver for levels)
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
CouldNotAcquirePrivLevel: if requested priv level not attained
@@ -245,36 +216,35 @@
Module nssh.driver.network_driver
priv_attempt_counter += 1
def send_commands(
- self, commands: Union[str, List[str]], strip_prompt: bool = True, textfsm: bool = False,
- ) -> List[Result]:
+ self, commands: Union[str, List[str]], strip_prompt: bool = True
+ ) -> List[Response]:
"""
Send command(s)
Args:
commands: string or list of strings to send to device in privilege exec mode
strip_prompt: True/False strip prompt from returned output
- textfsm: True/False try to parse each command with textfsm
Returns:
- results: list of SSH2NetResult objects
+ responses: list of Scrape Response objects
Raises:
- N/A # noqa
+ N/A
"""
self.acquire_priv(str(self.default_desired_priv))
- results = self.channel.send_inputs(commands, strip_prompt)
- if not textfsm:
- return results
- for result in results:
- result.structured_result = self.textfsm_parse_output(
- result.channel_input, result.result
- )
- return results
+ responses = self.channel.send_inputs(commands, strip_prompt)
+
+ # update the response objects with textfsm platform; we do this here because the underlying
+ # channel doesn't know or care about platforms
+ for response in responses:
+ response.textfsm_platform = self.textfsm_platform
+
+ return responses
def send_interactive(
self, inputs: Union[List[str], Tuple[str, str, str, str]], hidden_response: bool = False,
- ) -> List[Result]:
+ ) -> List[Response]:
"""
Send inputs in an interactive fashion; used to handle prompts
@@ -285,27 +255,27 @@
Module nssh.driver.network_driver
could be "chained" together to respond to more than a "single" staged prompt
Args:
- inputs: list or tuple containing strings representing:
- initial input
- expectation (what should nssh expect after input)
- response (response to expectation)
- finale (what should nssh expect when "done")
+ inputs: list or tuple containing strings representing
+ channel_input - initial input to send
+ expected_prompt - prompt to expect after initial input
+ response - response to prompt
+ final_prompt - final prompt to expect
hidden_response: True/False response is hidden (i.e. password input)
Returns:
- N/A # noqa
+ responses: List of Scrape Response objects
Raises:
- N/A # noqa
+ N/A
"""
self.acquire_priv(str(self.default_desired_priv))
- results = self.channel.send_inputs_interact(inputs, hidden_response)
- return results
+ responses = self.channel.send_inputs_interact(inputs, hidden_response)
+ return responses
def send_configs(
self, configs: Union[str, List[str]], strip_prompt: bool = True
- ) -> List[Result]:
+ ) -> List[Response]:
"""
Send configuration(s)
@@ -314,55 +284,29 @@
Module nssh.driver.network_driver
strip_prompt: True/False strip prompt from returned output
Returns:
- N/A # noqa
+ responses: List of Scrape Response objects
Raises:
- N/A # noqa
+ N/A
"""
self.acquire_priv("configuration")
- result = self.channel.send_inputs(configs, strip_prompt)
+ responses = self.channel.send_inputs(configs, strip_prompt)
self.acquire_priv(str(self.default_desired_priv))
- return result
-
- def textfsm_parse_output(
- self, command: str, output: str
- ) -> Union[List[Union[List[Any], Dict[str, Any]]], Dict[str, Any]]:
- """
- Parse output with TextFSM and ntc-templates
-
- Always return a non-string value -- if parsing fails to produce list/dict, return empty dict
-
- Args:
- command: command used to get output
- output: output from command
-
- Returns:
- output: parsed output
-
- Raises:
- N/A # noqa
-
- """
- template = _textfsm_get_template(self.textfsm_platform, command)
- if isinstance(template, TextIOWrapper):
- structured_output = textfsm_parse(template, output)
- if isinstance(structured_output, (dict, list)):
- return structured_output
- return {}
+ return responses
def get_prompt(self) -> str:
"""
Convenience method to get device prompt from Channel
Args:
- N/A # noqa
+ N/A
Returns:
- prompt: prompt received from channel.get_prompt
+ str: prompt received from channel.get_prompt
Raises:
- N/A # noqa
+ N/A
"""
prompt: str = self.channel.get_prompt()
@@ -393,7 +337,7 @@
Escalate to the next privilege level up
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ UnknownPrivLevel: if priv level cant be attained
+ TypeError: if invalid next prompt value
"""
current_priv = self._determine_current_priv(self.channel.get_prompt())
@@ -510,13 +454,13 @@
Raises
Deescalate to the next privilege level down
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ UnknownPrivLevel: if no default priv level set to deescalate to
"""
current_priv = self._determine_current_priv(self.channel.get_prompt())
@@ -535,10 +479,10 @@
Raises
Args:
desired_priv: string name of desired privilege level
- (see nssh.driver.<driver_category.device_type>.driver for levels)
+ (see scrapli.driver.<driver_category.device_type>.driver for levels)
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
CouldNotAcquirePrivLevel: if requested priv level not attained
@@ -560,36 +504,35 @@
Raises
priv_attempt_counter += 1
def send_commands(
- self, commands: Union[str, List[str]], strip_prompt: bool = True, textfsm: bool = False,
- ) -> List[Result]:
+ self, commands: Union[str, List[str]], strip_prompt: bool = True
+ ) -> List[Response]:
"""
Send command(s)
Args:
commands: string or list of strings to send to device in privilege exec mode
strip_prompt: True/False strip prompt from returned output
- textfsm: True/False try to parse each command with textfsm
Returns:
- results: list of SSH2NetResult objects
+ responses: list of Scrape Response objects
Raises:
- N/A # noqa
+ N/A
"""
self.acquire_priv(str(self.default_desired_priv))
- results = self.channel.send_inputs(commands, strip_prompt)
- if not textfsm:
- return results
- for result in results:
- result.structured_result = self.textfsm_parse_output(
- result.channel_input, result.result
- )
- return results
+ responses = self.channel.send_inputs(commands, strip_prompt)
+
+ # update the response objects with textfsm platform; we do this here because the underlying
+ # channel doesn't know or care about platforms
+ for response in responses:
+ response.textfsm_platform = self.textfsm_platform
+
+ return responses
def send_interactive(
self, inputs: Union[List[str], Tuple[str, str, str, str]], hidden_response: bool = False,
- ) -> List[Result]:
+ ) -> List[Response]:
"""
Send inputs in an interactive fashion; used to handle prompts
@@ -600,27 +543,27 @@
Raises
could be "chained" together to respond to more than a "single" staged prompt
Args:
- inputs: list or tuple containing strings representing:
- initial input
- expectation (what should nssh expect after input)
- response (response to expectation)
- finale (what should nssh expect when "done")
+ inputs: list or tuple containing strings representing
+ channel_input - initial input to send
+ expected_prompt - prompt to expect after initial input
+ response - response to prompt
+ final_prompt - final prompt to expect
hidden_response: True/False response is hidden (i.e. password input)
Returns:
- N/A # noqa
+ responses: List of Scrape Response objects
Raises:
- N/A # noqa
+ N/A
"""
self.acquire_priv(str(self.default_desired_priv))
- results = self.channel.send_inputs_interact(inputs, hidden_response)
- return results
+ responses = self.channel.send_inputs_interact(inputs, hidden_response)
+ return responses
def send_configs(
self, configs: Union[str, List[str]], strip_prompt: bool = True
- ) -> List[Result]:
+ ) -> List[Response]:
"""
Send configuration(s)
@@ -629,55 +572,29 @@
Raises
strip_prompt: True/False strip prompt from returned output
Returns:
- N/A # noqa
+ responses: List of Scrape Response objects
Raises:
- N/A # noqa
+ N/A
"""
self.acquire_priv("configuration")
- result = self.channel.send_inputs(configs, strip_prompt)
+ responses = self.channel.send_inputs(configs, strip_prompt)
self.acquire_priv(str(self.default_desired_priv))
- return result
-
- def textfsm_parse_output(
- self, command: str, output: str
- ) -> Union[List[Union[List[Any], Dict[str, Any]]], Dict[str, Any]]:
- """
- Parse output with TextFSM and ntc-templates
-
- Always return a non-string value -- if parsing fails to produce list/dict, return empty dict
-
- Args:
- command: command used to get output
- output: output from command
-
- Returns:
- output: parsed output
-
- Raises:
- N/A # noqa
-
- """
- template = _textfsm_get_template(self.textfsm_platform, command)
- if isinstance(template, TextIOWrapper):
- structured_output = textfsm_parse(template, output)
- if isinstance(structured_output, (dict, list)):
- return structured_output
- return {}
+ return responses
def get_prompt(self) -> str:
"""
Convenience method to get device prompt from Channel
Args:
- N/A # noqa
+ N/A
Returns:
- prompt: prompt received from channel.get_prompt
+ str: prompt received from channel.get_prompt
Raises:
- N/A # noqa
+ N/A
"""
prompt: str = self.channel.get_prompt()
@@ -700,20 +617,20 @@
string name of desired privilege level
-(see nssh.driver..driver for levels)
+(see scrapli.driver..driver for levels)
Returns
N/A
-# noqa
+# noqa: DAR202
Raises
@@ -745,10 +662,10 @@
Raises
Args:
desired_priv: string name of desired privilege level
- (see nssh.driver.<driver_category.device_type>.driver for levels)
+ (see scrapli.driver.<driver_category.device_type>.driver for levels)
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
CouldNotAcquirePrivLevel: if requested priv level not attained
@@ -770,23 +687,21 @@
Raises
priv_attempt_counter += 1
-
+
def get_prompt(self)
Convenience method to get device prompt from Channel
Args
-
N/A
-# noqa
+
N/A
Returns
-
prompt
+
str
prompt received from channel.get_prompt
Raises
-
N/A
-# noqa
+
N/A
@@ -798,21 +713,21 @@
Raises
Convenience method to get device prompt from Channel
Args:
- N/A # noqa
+ N/A
Returns:
- prompt: prompt received from channel.get_prompt
+ str: prompt received from channel.get_prompt
Raises:
- N/A # noqa
+ N/A
"""
prompt: str = self.channel.get_prompt()
return prompt
string or list of strings to send to device in privilege exec mode
strip_prompt
True/False strip prompt from returned output
-
textfsm
-
True/False try to parse each command with textfsm
Returns
-
results
-
list of SSH2NetResult objects
+
responses
+
list of Scrape Response objects
Raises
-
N/A
-# noqa
+
N/A
@@ -841,35 +753,34 @@
Raises
Expand source code
def send_commands(
- self, commands: Union[str, List[str]], strip_prompt: bool = True, textfsm: bool = False,
-) -> List[Result]:
+ self, commands: Union[str, List[str]], strip_prompt: bool = True
+) -> List[Response]:
"""
Send command(s)
Args:
commands: string or list of strings to send to device in privilege exec mode
strip_prompt: True/False strip prompt from returned output
- textfsm: True/False try to parse each command with textfsm
Returns:
- results: list of SSH2NetResult objects
+ responses: list of Scrape Response objects
Raises:
- N/A # noqa
+ N/A
"""
self.acquire_priv(str(self.default_desired_priv))
- results = self.channel.send_inputs(commands, strip_prompt)
- if not textfsm:
- return results
- for result in results:
- result.structured_result = self.textfsm_parse_output(
- result.channel_input, result.result
- )
- return results
+ responses = self.channel.send_inputs(commands, strip_prompt)
+
+ # update the response objects with textfsm platform; we do this here because the underlying
+ # channel doesn't know or care about platforms
+ for response in responses:
+ response.textfsm_platform = self.textfsm_platform
+
+ return responses
list or tuple containing strings representing:
-initial input
-expectation (what should nssh expect after input)
-response (response to expectation)
-finale (what should nssh expect when "done")
+
list or tuple containing strings representing
+channel_input - initial input to send
+expected_prompt - prompt to expect after initial input
+response - response to prompt
+final_prompt - final prompt to expect
hidden_response
True/False response is hidden (i.e. password input)
Returns
-
N/A
-# noqa
-
+
responses
+
List of Scrape Response objects
Raises
-
N/A
-# noqa
+
N/A
@@ -958,7 +865,7 @@
Raises
def send_interactive(
self, inputs: Union[List[str], Tuple[str, str, str, str]], hidden_response: bool = False,
-) -> List[Result]:
+) -> List[Response]:
"""
Send inputs in an interactive fashion; used to handle prompts
@@ -969,196 +876,88 @@
Raises
could be "chained" together to respond to more than a "single" staged prompt
Args:
- inputs: list or tuple containing strings representing:
- initial input
- expectation (what should nssh expect after input)
- response (response to expectation)
- finale (what should nssh expect when "done")
+ inputs: list or tuple containing strings representing
+ channel_input - initial input to send
+ expected_prompt - prompt to expect after initial input
+ response - response to prompt
+ final_prompt - final prompt to expect
hidden_response: True/False response is hidden (i.e. password input)
Returns:
- N/A # noqa
+ responses: List of Scrape Response objects
Raises:
- N/A # noqa
+ N/A
"""
self.acquire_priv(str(self.default_desired_priv))
- results = self.channel.send_inputs_interact(inputs, hidden_response)
- return results
diff --git a/docs/nssh/exceptions.html b/docs/scrapli/exceptions.html
similarity index 78%
rename from docs/nssh/exceptions.html
rename to docs/scrapli/exceptions.html
index 52eeb7a4..99bf248a 100644
--- a/docs/nssh/exceptions.html
+++ b/docs/scrapli/exceptions.html
@@ -4,8 +4,8 @@
-nssh.exceptions API documentation
-
+scrapli.exceptions API documentation
+
@@ -17,27 +17,27 @@
-
Module nssh.exceptions
+
Module scrapli.exceptions
-
nssh.exceptions
+
scrapli.exceptions
Expand source code
-
"""nssh.exceptions"""
+
"""scrapli.exceptions"""
-class NSSHTimeout(Exception):
- """Exception for any nssh timeouts"""
+class ScrapliTimeout(Exception):
+ """Exception for any scrapli timeouts"""
class MissingDependencies(Exception):
"""Exception for any missing (probably optional) dependencies"""
-class NSSHAuthenticationFailed(Exception):
- """Exception for nssh authentication failure"""
+class ScrapliAuthenticationFailed(Exception):
+ """Exception for scrapli authentication failure"""
class UnknownPrivLevel(Exception):
@@ -57,7 +57,7 @@
Module nssh.exceptions
Classes
-
+
class CouldNotAcquirePrivLevel(...)
@@ -76,7 +76,7 @@
Ancestors
builtins.BaseException
-
+
class MissingDependencies(...)
@@ -95,18 +95,18 @@
Ancestors
builtins.BaseException
-
-class NSSHAuthenticationFailed
+
+class ScrapliAuthenticationFailed(...)
-
Exception for nssh authentication failure
+
Exception for scrapli authentication failure
Expand source code
-
class NSSHAuthenticationFailed(Exception):
- """Exception for nssh authentication failure"""
+
class ScrapliAuthenticationFailed(Exception):
+ """Exception for scrapli authentication failure"""
Ancestors
@@ -114,18 +114,18 @@
Ancestors
builtins.BaseException
-
-class NSSHTimeout
+
+class ScrapliTimeout(...)
-
Exception for any nssh timeouts
+
Exception for any scrapli timeouts
Expand source code
-
class NSSHTimeout(Exception):
- """Exception for any nssh timeouts"""
+
class ScrapliTimeout(Exception):
+ """Exception for any scrapli timeouts"""
output: bytes string each line right stripped
Raises:
- N/A # noqa
+ N/A
"""
check_prompt = prompt or class_prompt
@@ -72,10 +72,10 @@
Module nssh.helper
output: bytes string to process
Returns:
- output: bytes string each line right stripped
+ bytes: bytes string each line right stripped
Raises:
- N/A # noqa
+ N/A
"""
return b"\n".join([line.rstrip() for line in output.splitlines()])
@@ -91,10 +91,10 @@
Module nssh.helper
output: bytes from previous reads if needed
Returns:
- output: output read from channel with comms_ansi characters removed
+ bytes: bytes output read from channel with comms_ansi characters removed
Raises:
- N/A # noqa
+ N/A
"""
ansi_escape_pattern = re.compile(rb"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
@@ -113,7 +113,7 @@
output: bytes string each line right stripped
Raises:
- N/A # noqa
+ N/A
"""
check_prompt = prompt or class_prompt
@@ -325,7 +329,7 @@
Raises
return re.compile(re.escape(bytes_check_prompt))
-
+
def normalize_lines(output)
@@ -338,13 +342,12 @@
Args
Returns
-
output
+
bytes
bytes string each line right stripped
Raises
-
N/A
-# noqa
+
N/A
@@ -361,16 +364,16 @@
Raises
output: bytes string to process
Returns:
- output: bytes string each line right stripped
+ bytes: bytes string each line right stripped
Raises:
- N/A # noqa
+ N/A
"""
return b"\n".join([line.rstrip() for line in output.splitlines()])
-
+
def strip_ansi(output)
@@ -383,13 +386,12 @@
Args
Returns
-
output
-
output read from channel with comms_ansi characters removed
+
bytes
+
bytes output read from channel with comms_ansi characters removed
Raises
-
N/A
-# noqa
+
N/A
@@ -406,10 +408,10 @@
Raises
output: bytes from previous reads if needed
Returns:
- output: output read from channel with comms_ansi characters removed
+ bytes: bytes output read from channel with comms_ansi characters removed
Raises:
- N/A # noqa
+ N/A
"""
ansi_escape_pattern = re.compile(rb"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
@@ -417,7 +419,7 @@
Convert netmiko style "ConnectHandler" device creation to nssh style
+
Convert netmiko style "ConnectHandler" device creation to scrapli style
Args
auto_open
@@ -140,7 +140,7 @@
Args
Returns
NetmikoNetworkDriver
-
NSSH connection object for specified device-type
+
Scrape connection object for specified device-type
Raises
@@ -153,14 +153,14 @@
Raises
def connect_handler(auto_open: bool = True, **kwargs: Dict[str, Any]) -> NetmikoNetworkDriver:
"""
- Convert netmiko style "ConnectHandler" device creation to nssh style
+ Convert netmiko style "ConnectHandler" device creation to scrapli style
Args:
auto_open: auto open connection or not (primarily for testing purposes)
**kwargs: keyword arguments
Returns:
- NetmikoNetworkDriver: NSSH connection object for specified device-type
+ NetmikoNetworkDriver: Scrape connection object for specified device-type
Raises:
TypeError: if unsupported netmiko device type is provided
@@ -171,7 +171,7 @@
Raises
raise TypeError(f"Argument 'device_type' must be string, got {type(netmiko_device_type)}")
if netmiko_device_type not in NETMIKO_DEVICE_TYPE_MAPPER.keys():
- raise TypeError(f"Unsupported netmiko device type for nssh: {kwargs['device_type']}")
+ raise TypeError(f"Unsupported netmiko device type for scrapli: {kwargs['device_type']}")
driver_class = NETMIKO_DEVICE_TYPE_MAPPER.get(netmiko_device_type).get("driver")
driver_args = NETMIKO_DEVICE_TYPE_MAPPER.get(netmiko_device_type).get("arg_mapper")
@@ -185,7 +185,7 @@
Raises
if auto_open:
driver.open()
- # Below is a dirty way to patch netmiko methods into nssh without having a factory function
+ # Below is a dirty way to patch netmiko methods into scrapli without having a factory function
# and a million classes... as this is just for testing interoperability we'll let this slide...
driver.find_prompt = types.MethodType(netmiko_find_prompt, driver)
driver.send_command = types.MethodType(netmiko_send_command, driver)
@@ -200,14 +200,14 @@
NSSH is the base class for NetworkDriver, and subsequent platform specific drivers
-(i.e. IOSXEDriver). NSSH can be used on its own and offers a semi-pexpect like experience in
+
Scrape Object
+
Scrape is the base class for NetworkDriver, and subsequent platform specific drivers (i.e.
+IOSXEDriver). Scrape can be used on its own and offers a semi-pexpect like experience in
that it doesn't know or care about privilege levels, platform types, and things like that.
Args
@@ -232,7 +232,7 @@
Args
comms_prompt_pattern
raw string regex pattern – preferably use ^ and $ anchors!
this is the single most important attribute here! if this does not match a prompt,
-nssh will not work!
+scrapli will not work!
IMPORTANT: regex search uses multi-line + case insensitive flags. multi-line allows
for highly reliably matching for prompts after stripping trailing white space,
case insensitive is just a convenience factor so i can be lazy.
@@ -249,21 +249,22 @@
Args
ssh_config_file
string to path for ssh config file, True to use default ssh config file
or False to ignore default ssh config file
system|ssh2|paramiko|telnet – type of transport to use
system uses system available ssh (/usr/bin/ssh)
ssh2 uses ssh2-python
paramiko uses… paramiko
+telnet uses telnetlib
choice of driver depends on the features you need. in general system is easiest as
it will just "auto-magically" use your ssh config file (~/.ssh/config or
/etc/ssh/config_file). ssh2 is very very fast as it is a thin wrapper around libssh2
however it is slightly feature limited. paramiko is slower than ssh2, but has more
-features built in (though nssh does not expose/support them all).
+features built in (though scrapli does not expose/support them all).
session_pre_login_handler: Union[str, Callable[..., Any]] = "",
session_disable_paging: Union[str, Callable[..., Any]] = "terminal length 0",
ssh_config_file: Union[str, bool] = True,
- driver: str = "system",
+ transport: str = "system",
):
"""
- NSSH Object
+ Scrape Object
- NSSH is the base class for NetworkDriver, and subsequent platform specific drivers
- (i.e. IOSXEDriver). NSSH can be used on its own and offers a semi-pexpect like experience in
+ Scrape is the base class for NetworkDriver, and subsequent platform specific drivers (i.e.
+ IOSXEDriver). Scrape can be used on its own and offers a semi-pexpect like experience in
that it doesn't know or care about privilege levels, platform types, and things like that.
Args:
@@ -316,7 +317,7 @@
Raises
timeout_ops: timeout for ssh channel operations
comms_prompt_pattern: raw string regex pattern -- preferably use `^` and `$` anchors!
this is the single most important attribute here! if this does not match a prompt,
- nssh will not work!
+ scrapli will not work!
IMPORTANT: regex search uses multi-line + case insensitive flags. multi-line allows
for highly reliably matching for prompts after stripping trailing white space,
case insensitive is just a convenience factor so i can be lazy.
@@ -328,18 +329,19 @@
Raises
string to send to device to disable paging
ssh_config_file: string to path for ssh config file, True to use default ssh config file
or False to ignore default ssh config file
- driver: system|ssh2|paramiko -- type of ssh driver to use
+ transport: system|ssh2|paramiko|telnet -- type of transport to use
system uses system available ssh (/usr/bin/ssh)
ssh2 uses ssh2-python
paramiko uses... paramiko
+ telnet uses telnetlib
choice of driver depends on the features you need. in general system is easiest as
it will just "auto-magically" use your ssh config file (~/.ssh/config or
/etc/ssh/config_file). ssh2 is very very fast as it is a thin wrapper around libssh2
however it is slightly feature limited. paramiko is slower than ssh2, but has more
- features built in (though nssh does not expose/support them all).
+ features built in (though scrapli does not expose/support them all).
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
TypeError: if auth_strict_key is not a bool
@@ -375,10 +377,12 @@
Raises
raise TypeError(f"ssh_config_file should be str or bool, got {type(ssh_config_file)}")
self.ssh_config_file = ssh_config_file
- if driver not in ("ssh2", "paramiko", "system"):
- raise ValueError(f"transport should be one of ssh2|paramiko|system, got {driver}")
+ if transport not in ("ssh2", "paramiko", "system", "telnet"):
+ raise ValueError(
+ f"transport should be one of ssh2|paramiko|system|telnet, got {transport}"
+ )
self.transport: Transport
- self.transport_class, self.transport_args = self._transport_factory(driver)
+ self.transport_class, self.transport_args = self._transport_factory(transport)
self.channel: Channel
self.channel_args = {}
@@ -387,18 +391,18 @@
Patch `send_command_timing` in netmiko connect handler
- Really just a shim to send_command, nssh doesnt support/need timing mechanics -- adjust the
- timers on the connection object if needed, or adjust them on the fly in your code.
+ Really just a shim to send_command, scrapli doesnt support/need timing mechanics -- adjust
+ the timers on the connection object if needed, or adjust them on the fly in your code.
Args:
command_string: string or list of strings to send as commands
**kwargs: keyword arguments to support other netmiko args without blowing up
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ N/A
"""
def connect_handler(auto_open: bool = True, **kwargs: Dict[str, Any]) -> NetmikoNetworkDriver:
"""
- Convert netmiko style "ConnectHandler" device creation to nssh style
+ Convert netmiko style "ConnectHandler" device creation to scrapli style
Args:
auto_open: auto open connection or not (primarily for testing purposes)
**kwargs: keyword arguments
Returns:
- NetmikoNetworkDriver: NSSH connection object for specified device-type
+ NetmikoNetworkDriver: Scrape connection object for specified device-type
Raises:
TypeError: if unsupported netmiko device type is provided
@@ -114,7 +114,7 @@
Module nssh.netmiko_compatability
raise TypeError(f"Argument 'device_type' must be string, got {type(netmiko_device_type)}")
if netmiko_device_type not in NETMIKO_DEVICE_TYPE_MAPPER.keys():
- raise TypeError(f"Unsupported netmiko device type for nssh: {kwargs['device_type']}")
+ raise TypeError(f"Unsupported netmiko device type for scrapli: {kwargs['device_type']}")
driver_class = NETMIKO_DEVICE_TYPE_MAPPER.get(netmiko_device_type).get("driver")
driver_args = NETMIKO_DEVICE_TYPE_MAPPER.get(netmiko_device_type).get("arg_mapper")
@@ -128,7 +128,7 @@
Module nssh.netmiko_compatability
if auto_open:
driver.open()
- # Below is a dirty way to patch netmiko methods into nssh without having a factory function
+ # Below is a dirty way to patch netmiko methods into scrapli without having a factory function
# and a million classes... as this is just for testing interoperability we'll let this slide...
driver.find_prompt = types.MethodType(netmiko_find_prompt, driver)
driver.send_command = types.MethodType(netmiko_send_command, driver)
@@ -140,16 +140,16 @@
kwargs["session_pre_login_handler"] = ""
kwargs["session_disable_paging"] = ""
- transformed_kwargs = {k: v for (k, v) in kwargs.items() if k in VALID_NSSH_KWARGS}
+ transformed_kwargs = {k: v for (k, v) in kwargs.items() if k in VALID_SCRAPLI_KWARGS}
return transformed_kwargs
def netmiko_find_prompt(self: NetmikoNetworkDriver) -> str:
"""
- Patch `find_prompt` in netmiko connect handler to `get_prompt` in nssh
+ Patch `find_prompt` in netmiko connect handler to `get_prompt` in scrapli
Args:
- N/A # noqa
+ self: NetmikoNetworkDriver object -- `self` as this gets shoe-horned into NetworkDriver via
+ types MethodType
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ N/A
"""
return self.get_prompt()
@@ -204,17 +205,19 @@
Module nssh.netmiko_compatability
Patch `send_command` in netmiko connect handler
Patch and support strip_prompt, use_textfsm, and textfsm_template args. Return a single
- string to match netmiko functionality (instead of nssh result object)
+ string to match netmiko functionality (instead of scrapli result object)
Args:
+ self: NetmikoNetworkDriver object -- `self` as this gets shoe-horned into NetworkDriver via
+ types MethodType
command_string: string or list of strings to send as commands
**kwargs: keyword arguments to support other netmiko args without blowing up
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ N/A
"""
provided_strip_prompt = kwargs.pop("strip_prompt", None)
@@ -227,7 +230,7 @@
Module nssh.netmiko_compatability
textfsm_template = kwargs.pop("textfsm_template", None)
if expect_string:
- err = "nssh netmiko interoperability does not support expect_string!"
+ err = "scrapli netmiko interoperability does not support expect_string!"
msg = f"***** {err} {'*' * (80 - len(err))}"
fix = (
f"To resolve this issue, use native or driver mode with `send_inputs_interact` "
@@ -271,18 +274,20 @@
Module nssh.netmiko_compatability
"""
Patch `send_command_timing` in netmiko connect handler
- Really just a shim to send_command, nssh doesnt support/need timing mechanics -- adjust the
+ Really just a shim to send_command, scrapli doesnt support/need timing mechanics -- adjust the
timers on the connection object if needed, or adjust them on the fly in your code.
Args:
+ self: NetmikoNetworkDriver object -- `self` as this gets shoe-horned into NetworkDriver via
+ types MethodType
command_string: string or list of strings to send as commands
**kwargs: keyword arguments to support other netmiko args without blowing up
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ N/A
"""
return self.send_command(command_string, **kwargs)
@@ -294,18 +299,20 @@
Module nssh.netmiko_compatability
"""
Patch `send_config_set` in netmiko connect handler
- Note: nssh strips commands always (as it retains them in the result object anyway), so there
+ Note: scrapli strips commands always (as it retains them in the result object anyway), so there
is no interesting output from this as there would be in netmiko.
Args:
+ self: NetmikoNetworkDriver object -- `self` as this gets shoe-horned into NetworkDriver via
+ types MethodType
config_commands: configuration command(s) to send to device
**kwargs: keyword arguments to support other netmiko args without blowing up
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ N/A
"""
provided_strip_prompt = kwargs.pop("strip_prompt", None)
@@ -323,7 +330,7 @@
Module nssh.netmiko_compatability
results = self.channel.send_inputs(config_commands, strip_prompt)
else:
results = self.send_configs(config_commands, strip_prompt)
- # nssh always strips command, so there isn't typically anything useful coming back from this
+ # scrapli always strips command, so there isn't typically anything useful coming back from this
result = "\n".join([r.result for r in results])
return result
@@ -335,11 +342,11 @@
Module nssh.netmiko_compatability
Functions
-
+
def connect_handler(auto_open=True, **kwargs)
-
Convert netmiko style "ConnectHandler" device creation to nssh style
+
Convert netmiko style "ConnectHandler" device creation to scrapli style
Scrape connection object for specified device-type
Raises
@@ -363,14 +370,14 @@
Raises
def connect_handler(auto_open: bool = True, **kwargs: Dict[str, Any]) -> NetmikoNetworkDriver:
"""
- Convert netmiko style "ConnectHandler" device creation to nssh style
+ Convert netmiko style "ConnectHandler" device creation to scrapli style
Args:
auto_open: auto open connection or not (primarily for testing purposes)
**kwargs: keyword arguments
Returns:
- NetmikoNetworkDriver: NSSH connection object for specified device-type
+ NetmikoNetworkDriver: Scrape connection object for specified device-type
Raises:
TypeError: if unsupported netmiko device type is provided
@@ -381,7 +388,7 @@
Raises
raise TypeError(f"Argument 'device_type' must be string, got {type(netmiko_device_type)}")
if netmiko_device_type not in NETMIKO_DEVICE_TYPE_MAPPER.keys():
- raise TypeError(f"Unsupported netmiko device type for nssh: {kwargs['device_type']}")
+ raise TypeError(f"Unsupported netmiko device type for scrapli: {kwargs['device_type']}")
driver_class = NETMIKO_DEVICE_TYPE_MAPPER.get(netmiko_device_type).get("driver")
driver_args = NETMIKO_DEVICE_TYPE_MAPPER.get(netmiko_device_type).get("arg_mapper")
@@ -395,7 +402,7 @@
Raises
if auto_open:
driver.open()
- # Below is a dirty way to patch netmiko methods into nssh without having a factory function
+ # Below is a dirty way to patch netmiko methods into scrapli without having a factory function
# and a million classes... as this is just for testing interoperability we'll let this slide...
driver.find_prompt = types.MethodType(netmiko_find_prompt, driver)
driver.send_command = types.MethodType(netmiko_send_command, driver)
@@ -405,24 +412,26 @@
Raises
return driver
-
+
def netmiko_find_prompt(self)
-
Patch find_prompt in netmiko connect handler to get_prompt in nssh
+
Patch find_prompt in netmiko connect handler to get_prompt in scrapli
Args
-
N/A
-# noqa
+
+
self
+
NetmikoNetworkDriver object – self as this gets shoe-horned into NetworkDriver via
+types MethodType
+
Returns
N/A
-# noqa
+# noqa: DAR202
Raises
-
N/A
-# noqa
+
N/A
@@ -431,30 +440,34 @@
Raises
def netmiko_find_prompt(self: NetmikoNetworkDriver) -> str:
"""
- Patch `find_prompt` in netmiko connect handler to `get_prompt` in nssh
+ Patch `find_prompt` in netmiko connect handler to `get_prompt` in scrapli
Args:
- N/A # noqa
+ self: NetmikoNetworkDriver object -- `self` as this gets shoe-horned into NetworkDriver via
+ types MethodType
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ N/A
"""
return self.get_prompt()
Patch and support strip_prompt, use_textfsm, and textfsm_template args. Return a single
-string to match netmiko functionality (instead of nssh result object)
+string to match netmiko functionality (instead of scrapli result object)
Args
+
self
+
NetmikoNetworkDriver object – self as this gets shoe-horned into NetworkDriver via
+types MethodType
command_string
string or list of strings to send as commands
**kwargs
@@ -463,13 +476,12 @@
Args
Returns
N/A
-# noqa
+# noqa: DAR202
Raises
-
N/A
-# noqa
+
N/A
@@ -483,17 +495,19 @@
Raises
Patch `send_command` in netmiko connect handler
Patch and support strip_prompt, use_textfsm, and textfsm_template args. Return a single
- string to match netmiko functionality (instead of nssh result object)
+ string to match netmiko functionality (instead of scrapli result object)
Args:
+ self: NetmikoNetworkDriver object -- `self` as this gets shoe-horned into NetworkDriver via
+ types MethodType
command_string: string or list of strings to send as commands
**kwargs: keyword arguments to support other netmiko args without blowing up
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ N/A
"""
provided_strip_prompt = kwargs.pop("strip_prompt", None)
@@ -506,7 +520,7 @@
Raises
textfsm_template = kwargs.pop("textfsm_template", None)
if expect_string:
- err = "nssh netmiko interoperability does not support expect_string!"
+ err = "scrapli netmiko interoperability does not support expect_string!"
msg = f"***** {err} {'*' * (80 - len(err))}"
fix = (
f"To resolve this issue, use native or driver mode with `send_inputs_interact` "
@@ -544,15 +558,18 @@
Patch send_command_timing in netmiko connect handler
-
Really just a shim to send_command, nssh doesnt support/need timing mechanics – adjust the
+
Really just a shim to send_command, scrapli doesnt support/need timing mechanics – adjust the
timers on the connection object if needed, or adjust them on the fly in your code.
Args
+
self
+
NetmikoNetworkDriver object – self as this gets shoe-horned into NetworkDriver via
+types MethodType
command_string
string or list of strings to send as commands
**kwargs
@@ -561,13 +578,12 @@
Args
Returns
N/A
-# noqa
+# noqa: DAR202
Raises
-
N/A
-# noqa
+
N/A
@@ -580,32 +596,37 @@
Raises
"""
Patch `send_command_timing` in netmiko connect handler
- Really just a shim to send_command, nssh doesnt support/need timing mechanics -- adjust the
+ Really just a shim to send_command, scrapli doesnt support/need timing mechanics -- adjust the
timers on the connection object if needed, or adjust them on the fly in your code.
Args:
+ self: NetmikoNetworkDriver object -- `self` as this gets shoe-horned into NetworkDriver via
+ types MethodType
command_string: string or list of strings to send as commands
**kwargs: keyword arguments to support other netmiko args without blowing up
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ N/A
"""
return self.send_command(command_string, **kwargs)
-
Note: nssh strips commands always (as it retains them in the result object anyway), so there
+
Note: scrapli strips commands always (as it retains them in the result object anyway), so there
is no interesting output from this as there would be in netmiko.
Args
+
self
+
NetmikoNetworkDriver object – self as this gets shoe-horned into NetworkDriver via
+types MethodType
config_commands
configuration command(s) to send to device
**kwargs
@@ -614,13 +635,12 @@
Args
Returns
N/A
-# noqa
+# noqa: DAR202
Raises
-
N/A
-# noqa
+
N/A
@@ -633,18 +653,20 @@
Raises
"""
Patch `send_config_set` in netmiko connect handler
- Note: nssh strips commands always (as it retains them in the result object anyway), so there
+ Note: scrapli strips commands always (as it retains them in the result object anyway), so there
is no interesting output from this as there would be in netmiko.
Args:
+ self: NetmikoNetworkDriver object -- `self` as this gets shoe-horned into NetworkDriver via
+ types MethodType
config_commands: configuration command(s) to send to device
**kwargs: keyword arguments to support other netmiko args without blowing up
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ N/A
"""
provided_strip_prompt = kwargs.pop("strip_prompt", None)
@@ -662,20 +684,20 @@
Raises
results = self.channel.send_inputs(config_commands, strip_prompt)
else:
results = self.send_configs(config_commands, strip_prompt)
- # nssh always strips command, so there isn't typically anything useful coming back from this
+ # scrapli always strips command, so there isn't typically anything useful coming back from this
result = "\n".join([r.result for r in results])
return result
-
+
def transform_netmiko_kwargs(kwargs)
-
Transform netmiko style ConnectHandler arguments to nssh style
+
Transform netmiko style ConnectHandler arguments to scrapli style
Args
kwargs
-
netmiko-style ConnectHandler kwargs to transform to nssh style
+
netmiko-style ConnectHandler kwargs to transform to scrapli style
kwargs["session_pre_login_handler"] = ""
kwargs["session_disable_paging"] = ""
- transformed_kwargs = {k: v for (k, v) in kwargs.items() if k in VALID_NSSH_KWARGS}
+ transformed_kwargs = {k: v for (k, v) in kwargs.items() if k in VALID_SCRAPLI_KWARGS}
return transformed_kwargs
@@ -739,7 +760,7 @@
Raises
Classes
-
+
class NetmikoNetworkDriver(auth_secondary='', **kwargs)
@@ -755,13 +776,12 @@
Args
Returns
N/A
-# noqa
+# noqa: DAR202
Raises
-
N/A
-# noqa
+
N/A
@@ -777,36 +797,36 @@
Raises
Patch `send_command_timing` in netmiko connect handler
- Really just a shim to send_command, nssh doesnt support/need timing mechanics -- adjust the
- timers on the connection object if needed, or adjust them on the fly in your code.
+ Really just a shim to send_command, scrapli doesnt support/need timing mechanics -- adjust
+ the timers on the connection object if needed, or adjust them on the fly in your code.
Args:
command_string: string or list of strings to send as commands
**kwargs: keyword arguments to support other netmiko args without blowing up
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ N/A
"""
Netmiko style NetworkDriver with send_command method to appease typing
Patch send_command_timing in netmiko connect handler
-
Really just a shim to send_command, nssh doesnt support/need timing mechanics – adjust the
-timers on the connection object if needed, or adjust them on the fly in your code.
+
Really just a shim to send_command, scrapli doesnt support/need timing mechanics – adjust
+the timers on the connection object if needed, or adjust them on the fly in your code.
Args
command_string
@@ -817,13 +837,12 @@
Args
Returns
N/A
-# noqa
+# noqa: DAR202
Raises
-
N/A
-# noqa
+
N/A
@@ -838,18 +857,18 @@
Raises
Patch `send_command_timing` in netmiko connect handler
- Really just a shim to send_command, nssh doesnt support/need timing mechanics -- adjust the
- timers on the connection object if needed, or adjust them on the fly in your code.
+ Really just a shim to send_command, scrapli doesnt support/need timing mechanics -- adjust
+ the timers on the connection object if needed, or adjust them on the fly in your code.
Args:
command_string: string or list of strings to send as commands
**kwargs: keyword arguments to support other netmiko args without blowing up
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ N/A
"""
@@ -857,17 +876,16 @@
diff --git a/docs/nssh/result.html b/docs/scrapli/response.html
similarity index 69%
rename from docs/nssh/result.html
rename to docs/scrapli/response.html
index 8f5b4749..e01417cf 100644
--- a/docs/nssh/result.html
+++ b/docs/scrapli/response.html
@@ -4,8 +4,8 @@
-nssh.result API documentation
-
+scrapli.response API documentation
+
@@ -17,31 +17,35 @@
-
Module nssh.result
+
Module scrapli.response
-
nssh.response
+
scrapli.response
Expand source code
-
"""nssh.response"""
+
"""scrapli.response"""
from datetime import datetime
+from io import TextIOWrapper
from typing import Any, Dict, List, Optional, Union
+from scrapli.helper import _textfsm_get_template, textfsm_parse
-class Result:
+
+class Response:
def __init__(
self,
host: str,
channel_input: str,
+ textfsm_platform: str = "",
expectation: Optional[str] = None,
- response: Optional[str] = None,
+ channel_response: Optional[str] = None,
finale: Optional[str] = None,
failed_when_contains: Optional[List[str]] = None,
):
"""
- NSSH Result
+ Scrapli Response
Store channel_input, resulting output, and start/end/elapsed time information. Attempt to
determine if command was successful or not and reflect that in a failed attribute.
@@ -49,15 +53,17 @@
Module nssh.result
Args:
host: host that was operated on
channel_input: input that got sent down the channel
+ textfsm_platform: ntc-templates friendly platform type
expectation: used for send_inputs_interact -- string to expect back from the channel
after initial input
- response: used for send_inputs_interact -- string to use to respond to expected prompt
+ channel_response: used for send_inputs_interact -- string to use to respond to expected
+ prompt
finale: string of prompt to look for to know when "done" with interaction
failed_when_contains: list of strings that, if present in final output, represent a
failed command/interaction
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
N/A # noqa
@@ -69,8 +75,9 @@
Store channel_input, resulting output, and start/end/elapsed time information. Attempt to
determine if command was successful or not and reflect that in a failed attribute.
Args
@@ -173,11 +200,14 @@
Args
host that was operated on
channel_input
input that got sent down the channel
+
textfsm_platform
+
ntc-templates friendly platform type
expectation
used for send_inputs_interact – string to expect back from the channel
after initial input
-
response
-
used for send_inputs_interact – string to use to respond to expected prompt
+
channel_response
+
used for send_inputs_interact – string to use to respond to expected
+prompt
finale
string of prompt to look for to know when "done" with interaction
failed_when_contains
@@ -187,7 +217,7 @@
Args
Returns
N/A
-# noqa
+# noqa: DAR202
Raises
@@ -200,18 +230,19 @@
Raises
Expand source code
-
class Result:
+
class Response:
def __init__(
self,
host: str,
channel_input: str,
+ textfsm_platform: str = "",
expectation: Optional[str] = None,
- response: Optional[str] = None,
+ channel_response: Optional[str] = None,
finale: Optional[str] = None,
failed_when_contains: Optional[List[str]] = None,
):
"""
- NSSH Result
+ Scrapli Response
Store channel_input, resulting output, and start/end/elapsed time information. Attempt to
determine if command was successful or not and reflect that in a failed attribute.
@@ -219,15 +250,17 @@
Raises
Args:
host: host that was operated on
channel_input: input that got sent down the channel
+ textfsm_platform: ntc-templates friendly platform type
expectation: used for send_inputs_interact -- string to expect back from the channel
after initial input
- response: used for send_inputs_interact -- string to use to respond to expected prompt
+ channel_response: used for send_inputs_interact -- string to use to respond to expected
+ prompt
finale: string of prompt to look for to know when "done" with interaction
failed_when_contains: list of strings that, if present in final output, represent a
failed command/interaction
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
N/A # noqa
@@ -239,8 +272,9 @@
diff --git a/docs/nssh/transport/cssh2.html b/docs/scrapli/transport/cssh2.html
similarity index 78%
rename from docs/nssh/transport/cssh2.html
rename to docs/scrapli/transport/cssh2.html
index e92ae547..92f0272d 100644
--- a/docs/nssh/transport/cssh2.html
+++ b/docs/scrapli/transport/cssh2.html
@@ -4,8 +4,8 @@
-nssh.transport.cssh2 API documentation
-
+scrapli.transport.cssh2 API documentation
+
@@ -17,23 +17,23 @@
-
Module nssh.transport.cssh2
+
Module scrapli.transport.cssh2
-
nssh.transport.cssh2
+
scrapli.transport.cssh2
Expand source code
-
"""nssh.transport.cssh2"""
+
"""scrapli.transport.cssh2"""
import warnings
from logging import getLogger
from threading import Lock
from typing import Optional
-from nssh.exceptions import MissingDependencies, NSSHAuthenticationFailed
-from nssh.transport.socket import Socket
-from nssh.transport.transport import Transport
+from scrapli.exceptions import MissingDependencies, ScrapliAuthenticationFailed
+from scrapli.transport.socket import Socket
+from scrapli.transport.transport import Transport
LOG = getLogger("transport")
@@ -77,11 +77,10 @@
Module nssh.transport.cssh2
timeout_socket: timeout for establishing socket in seconds
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if socket handshake fails
- AuthenticationFailed: if all authentication means fail
+ MissingDependencies: if ssh2-python is not installed
"""
self.host: str = host
@@ -110,7 +109,7 @@
Module nssh.transport.cssh2
f"To resolve this issue, install '{exc.name}'. You can do this in one of the "
"following ways:\n"
"1: 'pip install -r requirements-ssh2.txt'\n"
- "2: 'pip install nssh[ssh2]'"
+ "2: 'pip install scrapli[ssh2]'"
)
warning = "\n" + msg + "\n" + fix + "\n" + msg
warnings.warn(warning)
@@ -124,14 +123,14 @@
Module nssh.transport.cssh2
Parent method to open session, authenticate and acquire shell
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if socket handshake fails
- AuthenticationFailed: if all authentication means fail
+ exc: if socket handshake fails
+ ScrapliAuthenticationFailed: if all authentication means fail
"""
if not self.socket_isalive():
@@ -151,7 +150,7 @@
Module nssh.transport.cssh2
if not self.isauthenticated():
msg = f"Authentication to host {self.host} failed"
LOG.critical(msg)
- raise NSSHAuthenticationFailed(msg)
+ raise ScrapliAuthenticationFailed(msg)
self._open_channel()
self.session_lock.release()
@@ -160,13 +159,13 @@
Module nssh.transport.cssh2
Parent method to try all means of authentication
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ N/A
"""
if self.auth_public_key:
@@ -190,13 +189,13 @@
Module nssh.transport.cssh2
Attempt to authenticate with public key authentication
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if unknown (i.e. not auth failed) exception occurs
+ exc: if unknown (i.e. not auth failed) exception occurs
"""
try:
@@ -217,13 +216,13 @@
Module nssh.transport.cssh2
Attempt to authenticate with password authentication
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if unknown (i.e. not auth failed) exception occurs
+ exc: if unknown (i.e. not auth failed) exception occurs
"""
try:
@@ -242,13 +241,13 @@
Module nssh.transport.cssh2
Attempt to authenticate with keyboard interactive authentication
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if unknown (i.e. not auth failed) exception occurs
+ exc: if unknown (i.e. not auth failed) exception occurs
"""
try:
@@ -277,13 +276,13 @@
Check if socket is alive and session is authenticated
Args:
- N/A # noqa
+ N/A
Returns:
bool: True if socket is alive and session authenticated, else False
Raises:
- N/A # noqa
+ N/A
"""
if self.socket_isalive() and not self.channel.eof() and self.isauthenticated():
@@ -351,14 +350,13 @@
Module nssh.transport.cssh2
Read data from the channel
Args:
- N/A # noqa
+ N/A
Returns:
- bytes_read: int of bytes read
- output: bytes output as read from channel
+ bytes: bytes output as read from channel
Raises:
- N/A # noqa
+ N/A
"""
output: bytes
@@ -373,30 +371,14 @@
class SSH2Transport(host, port=22, auth_username='', auth_public_key='', auth_password='', timeout_ssh=5000, timeout_socket=5)
@@ -474,15 +456,13 @@
Args
Returns
N/A
-# noqa
+# noqa: DAR202
Raises
-
Exception
-
if socket handshake fails
-
AuthenticationFailed
-
if all authentication means fail
+
MissingDependencies
+
if ssh2-python is not installed
@@ -516,11 +496,10 @@
Raises
timeout_socket: timeout for establishing socket in seconds
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if socket handshake fails
- AuthenticationFailed: if all authentication means fail
+ MissingDependencies: if ssh2-python is not installed
"""
self.host: str = host
@@ -549,7 +528,7 @@
Raises
f"To resolve this issue, install '{exc.name}'. You can do this in one of the "
"following ways:\n"
"1: 'pip install -r requirements-ssh2.txt'\n"
- "2: 'pip install nssh[ssh2]'"
+ "2: 'pip install scrapli[ssh2]'"
)
warning = "\n" + msg + "\n" + fix + "\n" + msg
warnings.warn(warning)
@@ -563,14 +542,14 @@
Raises
Parent method to open session, authenticate and acquire shell
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if socket handshake fails
- AuthenticationFailed: if all authentication means fail
+ exc: if socket handshake fails
+ ScrapliAuthenticationFailed: if all authentication means fail
"""
if not self.socket_isalive():
@@ -590,7 +569,7 @@
Raises
if not self.isauthenticated():
msg = f"Authentication to host {self.host} failed"
LOG.critical(msg)
- raise NSSHAuthenticationFailed(msg)
+ raise ScrapliAuthenticationFailed(msg)
self._open_channel()
self.session_lock.release()
@@ -599,13 +578,13 @@
Raises
Parent method to try all means of authentication
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ N/A
"""
if self.auth_public_key:
@@ -629,13 +608,13 @@
Raises
Attempt to authenticate with public key authentication
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if unknown (i.e. not auth failed) exception occurs
+ exc: if unknown (i.e. not auth failed) exception occurs
"""
try:
@@ -656,13 +635,13 @@
Raises
Attempt to authenticate with password authentication
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if unknown (i.e. not auth failed) exception occurs
+ exc: if unknown (i.e. not auth failed) exception occurs
"""
try:
@@ -681,13 +660,13 @@
Raises
Attempt to authenticate with keyboard interactive authentication
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if unknown (i.e. not auth failed) exception occurs
+ exc: if unknown (i.e. not auth failed) exception occurs
"""
try:
@@ -716,13 +695,13 @@
Check if socket is alive and session is authenticated
Args:
- N/A # noqa
+ N/A
Returns:
bool: True if socket is alive and session authenticated, else False
Raises:
- N/A # noqa
+ N/A
"""
if self.socket_isalive() and not self.channel.eof() and self.isauthenticated():
@@ -790,14 +769,13 @@
Raises
Read data from the channel
Args:
- N/A # noqa
+ N/A
Returns:
- bytes_read: int of bytes read
- output: bytes output as read from channel
+ bytes: bytes output as read from channel
Raises:
- N/A # noqa
+ N/A
"""
output: bytes
@@ -812,30 +790,14 @@
Parent method to open session, authenticate and acquire shell
Args
-
N/A
-# noqa
+
N/A
Returns
N/A
-# noqa
+# noqa: DAR202
Raises
-
Exception
+
exc
if socket handshake fails
-
AuthenticationFailed
+
ScrapliAuthenticationFailed
if all authentication means fail
@@ -1006,14 +1003,14 @@
Raises
Parent method to open session, authenticate and acquire shell
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if socket handshake fails
- AuthenticationFailed: if all authentication means fail
+ exc: if socket handshake fails
+ ScrapliAuthenticationFailed: if all authentication means fail
"""
if not self.socket_isalive():
@@ -1033,30 +1030,26 @@
Raises
if not self.isauthenticated():
msg = f"Authentication to host {self.host} failed"
LOG.critical(msg)
- raise NSSHAuthenticationFailed(msg)
+ raise ScrapliAuthenticationFailed(msg)
self._open_channel()
self.session_lock.release()
-
+
def read(self)
Read data from the channel
Args
-
N/A
-# noqa
+
N/A
Returns
-
bytes_read
-
int of bytes read
-
output
+
bytes
bytes output as read from channel
Raises
-
N/A
-# noqa
+
N/A
@@ -1068,14 +1061,13 @@
Raises
Read data from the channel
Args:
- N/A # noqa
+ N/A
Returns:
- bytes_read: int of bytes read
- output: bytes output as read from channel
+ bytes: bytes output as read from channel
Raises:
- N/A # noqa
+ N/A
"""
output: bytes
@@ -1086,21 +1078,19 @@
class MikoTransport(host, port=22, auth_username='', auth_public_key='', auth_password='', timeout_ssh=5000, timeout_socket=5)
@@ -110,15 +117,13 @@
Args
Returns
N/A
-# noqa
+# noqa: DAR202
Raises
-
Exception
-
if socket handshake fails
-
AuthenticationFailed
-
if all authentication means fail
+
MissingDependencies
+
if paramiko is not installed
@@ -152,11 +157,10 @@
Raises
timeout_ssh: timeout for ssh transport in milliseconds
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if socket handshake fails
- AuthenticationFailed: if all authentication means fail
+ MissingDependencies: if paramiko is not installed
"""
self.host: str = host
@@ -188,7 +192,7 @@
Raises
f"To resolve this issue, install '{exc.name}'. You can do this in one of the "
"following ways:\n"
"1: 'pip install -r requirements-paramiko.txt'\n"
- "2: 'pip install nssh[paramiko]'"
+ "2: 'pip install scrapli[paramiko]'"
)
warning = "\n" + msg + "\n" + fix + "\n" + msg
warnings.warn(warning)
@@ -202,14 +206,14 @@
Raises
Parent method to open session, authenticate and acquire shell
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if socket handshake fails
- AuthenticationFailed: if all authentication means fail
+ exc: if socket handshake fails
+ ScrapliAuthenticationFailed: if all authentication means fail
"""
if not self.socket_isalive():
@@ -228,7 +232,7 @@
Raises
if not self.isauthenticated():
msg = f"Authentication to host {self.host} failed"
LOG.critical(msg)
- raise NSSHAuthenticationFailed(msg)
+ raise ScrapliAuthenticationFailed(msg)
self._open_channel()
self.session_lock.release()
@@ -237,13 +241,13 @@
Raises
Parent method to try all means of authentication
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ N/A
"""
if self.auth_public_key:
@@ -263,13 +267,13 @@
Raises
Attempt to authenticate with public key authentication
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if unknown (i.e. not auth failed) exception occurs
+ exc: if unknown (i.e. not auth failed) exception occurs
"""
try:
@@ -290,13 +294,13 @@
Raises
Attempt to authenticate with password authentication
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if unknown (i.e. not auth failed) exception occurs
+ exc: if unknown (i.e. not auth failed) exception occurs
"""
try:
@@ -319,13 +323,13 @@
Check if socket is alive and session is authenticated
Args:
- N/A # noqa
+ N/A
Returns:
bool: True if socket is alive and session authenticated, else False
Raises:
- N/A # noqa
+ N/A
"""
if self.socket_isalive() and self.session.is_alive() and self.isauthenticated():
@@ -394,14 +398,13 @@
Raises
Read data from the channel
Args:
- N/A # noqa
+ N/A
Returns:
- bytes_read: int of bytes read
- output: bytes output as read from channel
+ bytes: bytes output as read from channel
Raises:
- N/A # noqa
+ N/A
"""
channel_read: bytes = self.channel.recv(65535)
@@ -415,35 +418,14 @@
Parent method to open session, authenticate and acquire shell
Args
-
N/A
-# noqa
+
N/A
Returns
N/A
-# noqa
+# noqa: DAR202
Raises
-
Exception
+
exc
if socket handshake fails
-
AuthenticationFailed
+
ScrapliAuthenticationFailed
if all authentication means fail
@@ -611,14 +628,14 @@
Raises
Parent method to open session, authenticate and acquire shell
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if socket handshake fails
- AuthenticationFailed: if all authentication means fail
+ exc: if socket handshake fails
+ ScrapliAuthenticationFailed: if all authentication means fail
"""
if not self.socket_isalive():
@@ -637,30 +654,26 @@
Raises
if not self.isauthenticated():
msg = f"Authentication to host {self.host} failed"
LOG.critical(msg)
- raise NSSHAuthenticationFailed(msg)
+ raise ScrapliAuthenticationFailed(msg)
self._open_channel()
self.session_lock.release()
-
+
def read(self)
Read data from the channel
Args
-
N/A
-# noqa
+
N/A
Returns
-
bytes_read
-
int of bytes read
-
output
+
bytes
bytes output as read from channel
Raises
-
N/A
-# noqa
+
N/A
@@ -672,14 +685,13 @@
Raises
Read data from the channel
Args:
- N/A # noqa
+ N/A
Returns:
- bytes_read: int of bytes read
- output: bytes output as read from channel
+ bytes: bytes output as read from channel
Raises:
- N/A # noqa
+ N/A
"""
channel_read: bytes = self.channel.recv(65535)
@@ -689,26 +701,24 @@
class SSH2Transport(host, port=22, auth_username='', auth_public_key='', auth_password='', timeout_ssh=5000, timeout_socket=5)
@@ -739,15 +749,13 @@
Args
Returns
N/A
-# noqa
+# noqa: DAR202
Raises
-
Exception
-
if socket handshake fails
-
AuthenticationFailed
-
if all authentication means fail
+
MissingDependencies
+
if ssh2-python is not installed
@@ -781,11 +789,10 @@
Raises
timeout_socket: timeout for establishing socket in seconds
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if socket handshake fails
- AuthenticationFailed: if all authentication means fail
+ MissingDependencies: if ssh2-python is not installed
"""
self.host: str = host
@@ -814,7 +821,7 @@
Raises
f"To resolve this issue, install '{exc.name}'. You can do this in one of the "
"following ways:\n"
"1: 'pip install -r requirements-ssh2.txt'\n"
- "2: 'pip install nssh[ssh2]'"
+ "2: 'pip install scrapli[ssh2]'"
)
warning = "\n" + msg + "\n" + fix + "\n" + msg
warnings.warn(warning)
@@ -828,14 +835,14 @@
Raises
Parent method to open session, authenticate and acquire shell
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if socket handshake fails
- AuthenticationFailed: if all authentication means fail
+ exc: if socket handshake fails
+ ScrapliAuthenticationFailed: if all authentication means fail
"""
if not self.socket_isalive():
@@ -855,7 +862,7 @@
Raises
if not self.isauthenticated():
msg = f"Authentication to host {self.host} failed"
LOG.critical(msg)
- raise NSSHAuthenticationFailed(msg)
+ raise ScrapliAuthenticationFailed(msg)
self._open_channel()
self.session_lock.release()
@@ -864,13 +871,13 @@
Raises
Parent method to try all means of authentication
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ N/A
"""
if self.auth_public_key:
@@ -894,13 +901,13 @@
Raises
Attempt to authenticate with public key authentication
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if unknown (i.e. not auth failed) exception occurs
+ exc: if unknown (i.e. not auth failed) exception occurs
"""
try:
@@ -921,13 +928,13 @@
Raises
Attempt to authenticate with password authentication
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if unknown (i.e. not auth failed) exception occurs
+ exc: if unknown (i.e. not auth failed) exception occurs
"""
try:
@@ -946,13 +953,13 @@
Raises
Attempt to authenticate with keyboard interactive authentication
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if unknown (i.e. not auth failed) exception occurs
+ exc: if unknown (i.e. not auth failed) exception occurs
"""
try:
@@ -981,13 +988,13 @@
Check if socket is alive and session is authenticated
Args:
- N/A # noqa
+ N/A
Returns:
bool: True if socket is alive and session authenticated, else False
Raises:
- N/A # noqa
+ N/A
"""
if self.socket_isalive() and not self.channel.eof() and self.isauthenticated():
@@ -1055,14 +1062,13 @@
Raises
Read data from the channel
Args:
- N/A # noqa
+ N/A
Returns:
- bytes_read: int of bytes read
- output: bytes output as read from channel
+ bytes: bytes output as read from channel
Raises:
- N/A # noqa
+ N/A
"""
output: bytes
@@ -1077,30 +1083,14 @@
Parent method to open session, authenticate and acquire shell
Args
-
N/A
-# noqa
+
N/A
Returns
N/A
-# noqa
+# noqa: DAR202
Raises
-
Exception
+
exc
if socket handshake fails
-
AuthenticationFailed
+
ScrapliAuthenticationFailed
if all authentication means fail
@@ -1271,14 +1296,14 @@
Raises
Parent method to open session, authenticate and acquire shell
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if socket handshake fails
- AuthenticationFailed: if all authentication means fail
+ exc: if socket handshake fails
+ ScrapliAuthenticationFailed: if all authentication means fail
"""
if not self.socket_isalive():
@@ -1298,30 +1323,26 @@
Raises
if not self.isauthenticated():
msg = f"Authentication to host {self.host} failed"
LOG.critical(msg)
- raise NSSHAuthenticationFailed(msg)
+ raise ScrapliAuthenticationFailed(msg)
self._open_channel()
self.session_lock.release()
-
+
def read(self)
Read data from the channel
Args
-
N/A
-# noqa
+
N/A
Returns
-
bytes_read
-
int of bytes read
-
output
+
bytes
bytes output as read from channel
Raises
-
N/A
-# noqa
+
N/A
@@ -1333,14 +1354,13 @@
Raises
Read data from the channel
Args:
- N/A # noqa
+ N/A
Returns:
- bytes_read: int of bytes read
- output: bytes output as read from channel
+ bytes: bytes output as read from channel
Raises:
- N/A # noqa
+ N/A
"""
output: bytes
@@ -1351,26 +1371,24 @@
prompt pattern expected for device, same as the one provided to
channel – system ssh needs to know this to know how to decide if we are properly
sending/receiving data – i.e. we are not stuck at some password prompt or some
-other failure scenario. If using driver, this should be passed from driver (NSSH, or
-IOSXE, etc.) to this Transport class.
+other failure scenario. If using driver, this should be passed from driver (Scrape,
+or IOSXE, etc.) to this Transport class.
comms_return_char
return character to use on the channel, same as the one provided to
channel – system ssh needs to know this to know what to send so that we can probe
the channel to make sure we are authenticated and sending/receiving data. If using
-driver, this should be passed from driver (NSSH, or IOSXE, etc.) to this Transport
+driver, this should be passed from driver (Scrape, or IOSXE, etc.) to this Transport
class.
ssh_config_file
string to path for ssh config file, True to use default ssh config file
@@ -1417,13 +1435,13 @@
Args
Returns
N/A
-# noqa
+# noqa: DAR202
Raises
-
AuthenticationFailed
-
if all authentication means fail
+
N/A
+
@@ -1462,21 +1480,21 @@
Raises
comms_prompt_pattern: prompt pattern expected for device, same as the one provided to
channel -- system ssh needs to know this to know how to decide if we are properly
sending/receiving data -- i.e. we are not stuck at some password prompt or some
- other failure scenario. If using driver, this should be passed from driver (NSSH, or
- IOSXE, etc.) to this Transport class.
+ other failure scenario. If using driver, this should be passed from driver (Scrape,
+ or IOSXE, etc.) to this Transport class.
comms_return_char: return character to use on the channel, same as the one provided to
channel -- system ssh needs to know this to know what to send so that we can probe
the channel to make sure we are authenticated and sending/receiving data. If using
- driver, this should be passed from driver (NSSH, or IOSXE, etc.) to this Transport
+ driver, this should be passed from driver (Scrape, or IOSXE, etc.) to this Transport
class.
ssh_config_file: string to path for ssh config file, True to use default ssh config file
or False to ignore default ssh config file
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- AuthenticationFailed: if all authentication means fail
+ N/A
"""
self.host: str = host
@@ -1493,7 +1511,7 @@
output = pty_session.read()
if b"password" in output.lower():
pty_session.write(self.auth_password.encode())
- pty_session.write(b"\n")
+ pty_session.write(self.comms_return_char.encode())
break
attempt_count += 1
if attempt_count > 250:
@@ -1673,13 +1692,13 @@
Raises
Beyond that we lock the session and send the return character and re-read the channel.
Args:
- N/A # noqa
+ pty_session: PtyProcess session object
Returns:
- authenticated: True if authenticated, else False
+ bool: True if authenticated, else False
Raises:
- N/A # noqa
+ N/A
"""
if pty_session.isalive() and not pty_session.eof():
@@ -1707,13 +1726,13 @@
Helper class that provides a standard way to create an ABC using
+inheritance.
+
TelnetTransport Object
+
Inherit from Transport ABC and Socket base class:
+TelnetTransport <- Transport (ABC)
+TelnetTransport <- Socket
+
Args
+
+
host
+
host ip/name to connect to
+
port
+
port to connect to
+
auth_username
+
username for authentication
+
auth_public_key
+
path to public key for authentication
+
auth_password
+
password for authentication
+
timeout_socket
+
timeout for establishing socket in seconds
+
timeout_ssh
+
timeout for ssh transport in milliseconds
+
comms_prompt_pattern
+
prompt pattern expected for device, same as the one provided to
+channel – system ssh needs to know this to know how to decide if we are properly
+sending/receiving data – i.e. we are not stuck at some password prompt or some
+other failure scenario. If using driver, this should be passed from driver (Scrape,
+or IOSXE, etc.) to this Transport class.
+
comms_return_char
+
return character to use on the channel, same as the one provided to
+channel – system ssh needs to know this to know what to send so that we can probe
+the channel to make sure we are authenticated and sending/receiving data. If using
+driver, this should be passed from driver (Scrape, or IOSXE, etc.) to this Transport
+class.
+
+
Returns
+
+
N/A
+# noqa: DAR202
+
+
+
Raises
+
+
N/A
+
+
+
+
+Expand source code
+
+
class TelnetTransport(Transport):
+ def __init__(
+ self,
+ host: str,
+ port: int = 23,
+ auth_username: str = "",
+ auth_public_key: str = "",
+ auth_password: str = "",
+ timeout_ssh: int = 5000,
+ timeout_socket: int = 5,
+ comms_prompt_pattern: str = r"^[a-z0-9.\-@()/:]{1,32}[#>$]$",
+ comms_return_char: str = "\n",
+ ): # pylint: disable=W0231
+ """
+ TelnetTransport Object
+
+ Inherit from Transport ABC and Socket base class:
+ TelnetTransport <- Transport (ABC)
+ TelnetTransport <- Socket
+
+ Args:
+ host: host ip/name to connect to
+ port: port to connect to
+ auth_username: username for authentication
+ auth_public_key: path to public key for authentication
+ auth_password: password for authentication
+ timeout_socket: timeout for establishing socket in seconds
+ timeout_ssh: timeout for ssh transport in milliseconds
+ comms_prompt_pattern: prompt pattern expected for device, same as the one provided to
+ channel -- system ssh needs to know this to know how to decide if we are properly
+ sending/receiving data -- i.e. we are not stuck at some password prompt or some
+ other failure scenario. If using driver, this should be passed from driver (Scrape,
+ or IOSXE, etc.) to this Transport class.
+ comms_return_char: return character to use on the channel, same as the one provided to
+ channel -- system ssh needs to know this to know what to send so that we can probe
+ the channel to make sure we are authenticated and sending/receiving data. If using
+ driver, this should be passed from driver (Scrape, or IOSXE, etc.) to this Transport
+ class.
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ N/A
+
+ """
+ self.host: str = host
+ self.port: int = port
+ self.timeout_ssh: int = timeout_ssh
+ self.timeout_socket: int = timeout_socket
+ self.session_lock: Lock = Lock()
+ self.auth_username: str = auth_username
+ self.auth_public_key: str = auth_public_key
+ self.auth_password: str = auth_password
+ self.comms_prompt_pattern: str = comms_prompt_pattern
+ self.comms_return_char: str = comms_return_char
+
+ self.session: ScrapliTelnet
+ self.lib_auth_exception = ScrapliAuthenticationFailed
+ self._isauthenticated = False
+
+ def open(self) -> None:
+ """
+ Open channel, acquire pty, request interactive shell
+
+ Args:
+ N/A
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ ScrapliAuthenticationFailed: if cant successfully authenticate
+
+ """
+ self.session_lock.acquire()
+ telnet_session = ScrapliTelnet(host=self.host, port=self.port)
+ LOG.debug(f"Session to host {self.host} spawned")
+ self.session_lock.release()
+ self._authenticate(telnet_session)
+ if not self._telnet_isauthenticated(telnet_session):
+ raise ScrapliAuthenticationFailed(
+ f"Could not authenticate over telnet to host: {self.host}"
+ )
+ LOG.debug(f"Authenticated to host {self.host} with password")
+ print("SUCH GOOD SUCCESS")
+ self.session = telnet_session
+
+ def _authenticate(self, telnet_session: ScrapliTelnet) -> None:
+ """
+ Parent private method to handle telnet authentication
+
+ Args:
+ telnet_session: Telnet session object
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ N/A
+
+ """
+ self._authenticate_username(telnet_session)
+ self._authenticate_password(telnet_session)
+
+ def _authenticate_username(self, telnet_session: ScrapliTelnet) -> None:
+ """
+ Private method to enter username for telnet authentication
+
+ Args:
+ telnet_session: Telnet session object
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ exc: if unknown (i.e. not auth failed) exception occurs
+
+ """
+ self.session_lock.acquire()
+ try:
+ attempt_count = 0
+ while True:
+ output = telnet_session.read_eager()
+ if b"username:" in output.lower():
+ telnet_session.write(self.auth_username.encode())
+ telnet_session.write(self.comms_return_char.encode())
+ break
+ attempt_count += 1
+ if attempt_count > 1000:
+ break
+ except self.lib_auth_exception as exc:
+ LOG.critical(f"Did not see username prompt from {self.host} failed. Exception: {exc}.")
+ raise exc
+ finally:
+ self.session_lock.release()
+
+ def _authenticate_password(self, telnet_session: ScrapliTelnet) -> None:
+ """
+ Private method to enter password for telnet authentication
+
+ Args:
+ telnet_session: Telnet session object
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ exc: if unknown (i.e. not auth failed) exception occurs
+
+ """
+ self.session_lock.acquire()
+ try:
+ attempt_count = 0
+ while True:
+ output = telnet_session.read_eager()
+ if b"password" in output.lower():
+ telnet_session.write(self.auth_password.encode())
+ telnet_session.write(self.comms_return_char.encode())
+ break
+ attempt_count += 1
+ if attempt_count > 1000:
+ break
+ except self.lib_auth_exception as exc:
+ LOG.critical(f"Password authentication with host {self.host} failed. Exception: {exc}.")
+ except Exception as exc:
+ LOG.critical(
+ "Unknown error occurred during password authentication with host "
+ f"{self.host}; Exception: {exc}"
+ )
+ raise exc
+ finally:
+ self.session_lock.release()
+
+ def _telnet_isauthenticated(self, telnet_session: ScrapliTelnet) -> bool:
+ """
+ Check if session is authenticated
+
+ This is very naive -- it only knows if the sub process is alive and has not received an EOF.
+ Beyond that we lock the session and send the return character and re-read the channel.
+
+ Args:
+ telnet_session: Telnet session object
+
+ Returns:
+ bool: True if authenticated, else False
+
+ Raises:
+ N/A
+
+ """
+ if not telnet_session.eof:
+ prompt_pattern = get_prompt_pattern("", self.comms_prompt_pattern)
+ telnet_session_fd = telnet_session.fileno()
+ self.session_lock.acquire()
+ telnet_session.write(self.comms_return_char.encode())
+ time.sleep(0.25)
+ fd_ready, _, _ = select([telnet_session_fd], [], [], 0)
+ if telnet_session_fd in fd_ready:
+ output = telnet_session.read_eager()
+ # we do not need to deal w/ line replacement for the actual output, only for
+ # parsing if a prompt-like thing is at the end of the output
+ output = re.sub(b"\r", b"\n", output.strip())
+ channel_match = re.search(prompt_pattern, output)
+ if channel_match:
+ self.session_lock.release()
+ self._isauthenticated = True
+ return True
+ self.session_lock.release()
+ return False
+
+ def close(self) -> None:
+ """
+ Close session and socket
+
+ Args:
+ N/A
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ N/A
+
+ """
+ self.session_lock.acquire()
+ self.session.close()
+ LOG.debug(f"Channel to host {self.host} closed")
+ self.session_lock.release()
+
+ def isalive(self) -> bool:
+ """
+ Check if socket is alive and session is authenticated
+
+ Args:
+ N/A
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ N/A
+
+ """
+
+ def read(self) -> bytes:
+ """
+ Read data from the channel
+
+ Args:
+ N/A
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ N/A
+
+ """
+ return self.session.read_eager()
+
+ def write(self, channel_input: str) -> None:
+ """
+ Write data to the channel
+
+ Args:
+ channel_input: string to send to channel
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ N/A
+
+ """
+ self.session.write(channel_input.encode())
+
+ def set_timeout(self, timeout: Optional[int] = None) -> None:
+ """
+ Set session timeout
+
+ Args:
+ timeout: timeout in seconds
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ N/A
+
+ """
+
+ def set_blocking(self, blocking: bool = False) -> None:
+ """
+ Set session blocking configuration
+
+ Args:
+ blocking: True/False set session to blocking
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ N/A
+
+ """
diff --git a/docs/nssh/transport/miko.html b/docs/scrapli/transport/miko.html
similarity index 77%
rename from docs/nssh/transport/miko.html
rename to docs/scrapli/transport/miko.html
index 2743abcf..f2b5fcf0 100644
--- a/docs/nssh/transport/miko.html
+++ b/docs/scrapli/transport/miko.html
@@ -4,8 +4,8 @@
-nssh.transport.miko API documentation
-
+scrapli.transport.miko API documentation
+
@@ -17,24 +17,23 @@
-
Module nssh.transport.miko
+
Module scrapli.transport.miko
-
nssh.transport.miko
+
scrapli.transport.miko
Expand source code
-
"""nssh.transport.miko"""
-import time
+
"""scrapli.transport.miko"""
import warnings
from logging import getLogger
from threading import Lock
from typing import Optional
-from nssh.exceptions import MissingDependencies, NSSHAuthenticationFailed
-from nssh.transport.socket import Socket
-from nssh.transport.transport import Transport
+from scrapli.exceptions import MissingDependencies, ScrapliAuthenticationFailed
+from scrapli.transport.socket import Socket
+from scrapli.transport.transport import Transport
LOG = getLogger("transport")
@@ -77,11 +76,10 @@
Module nssh.transport.miko
timeout_ssh: timeout for ssh transport in milliseconds
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if socket handshake fails
- AuthenticationFailed: if all authentication means fail
+ MissingDependencies: if paramiko is not installed
"""
self.host: str = host
@@ -113,7 +111,7 @@
Module nssh.transport.miko
f"To resolve this issue, install '{exc.name}'. You can do this in one of the "
"following ways:\n"
"1: 'pip install -r requirements-paramiko.txt'\n"
- "2: 'pip install nssh[paramiko]'"
+ "2: 'pip install scrapli[paramiko]'"
)
warning = "\n" + msg + "\n" + fix + "\n" + msg
warnings.warn(warning)
@@ -127,14 +125,14 @@
Module nssh.transport.miko
Parent method to open session, authenticate and acquire shell
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if socket handshake fails
- AuthenticationFailed: if all authentication means fail
+ exc: if socket handshake fails
+ ScrapliAuthenticationFailed: if all authentication means fail
"""
if not self.socket_isalive():
@@ -153,7 +151,7 @@
Module nssh.transport.miko
if not self.isauthenticated():
msg = f"Authentication to host {self.host} failed"
LOG.critical(msg)
- raise NSSHAuthenticationFailed(msg)
+ raise ScrapliAuthenticationFailed(msg)
self._open_channel()
self.session_lock.release()
@@ -162,13 +160,13 @@
Module nssh.transport.miko
Parent method to try all means of authentication
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ N/A
"""
if self.auth_public_key:
@@ -188,13 +186,13 @@
Module nssh.transport.miko
Attempt to authenticate with public key authentication
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if unknown (i.e. not auth failed) exception occurs
+ exc: if unknown (i.e. not auth failed) exception occurs
"""
try:
@@ -215,13 +213,13 @@
Module nssh.transport.miko
Attempt to authenticate with password authentication
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if unknown (i.e. not auth failed) exception occurs
+ exc: if unknown (i.e. not auth failed) exception occurs
"""
try:
@@ -244,13 +242,13 @@
Check if socket is alive and session is authenticated
Args:
- N/A # noqa
+ N/A
Returns:
bool: True if socket is alive and session authenticated, else False
Raises:
- N/A # noqa
+ N/A
"""
if self.socket_isalive() and self.session.is_alive() and self.isauthenticated():
@@ -319,14 +317,13 @@
Module nssh.transport.miko
Read data from the channel
Args:
- N/A # noqa
+ N/A
Returns:
- bytes_read: int of bytes read
- output: bytes output as read from channel
+ bytes: bytes output as read from channel
Raises:
- N/A # noqa
+ N/A
"""
channel_read: bytes = self.channel.recv(65535)
@@ -340,35 +337,14 @@
class MikoTransport(host, port=22, auth_username='', auth_public_key='', auth_password='', timeout_ssh=5000, timeout_socket=5)
@@ -447,15 +423,13 @@
Args
Returns
N/A
-# noqa
+# noqa: DAR202
Raises
-
Exception
-
if socket handshake fails
-
AuthenticationFailed
-
if all authentication means fail
+
MissingDependencies
+
if paramiko is not installed
@@ -489,11 +463,10 @@
Raises
timeout_ssh: timeout for ssh transport in milliseconds
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if socket handshake fails
- AuthenticationFailed: if all authentication means fail
+ MissingDependencies: if paramiko is not installed
"""
self.host: str = host
@@ -525,7 +498,7 @@
Raises
f"To resolve this issue, install '{exc.name}'. You can do this in one of the "
"following ways:\n"
"1: 'pip install -r requirements-paramiko.txt'\n"
- "2: 'pip install nssh[paramiko]'"
+ "2: 'pip install scrapli[paramiko]'"
)
warning = "\n" + msg + "\n" + fix + "\n" + msg
warnings.warn(warning)
@@ -539,14 +512,14 @@
Raises
Parent method to open session, authenticate and acquire shell
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if socket handshake fails
- AuthenticationFailed: if all authentication means fail
+ exc: if socket handshake fails
+ ScrapliAuthenticationFailed: if all authentication means fail
"""
if not self.socket_isalive():
@@ -565,7 +538,7 @@
Raises
if not self.isauthenticated():
msg = f"Authentication to host {self.host} failed"
LOG.critical(msg)
- raise NSSHAuthenticationFailed(msg)
+ raise ScrapliAuthenticationFailed(msg)
self._open_channel()
self.session_lock.release()
@@ -574,13 +547,13 @@
Raises
Parent method to try all means of authentication
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- N/A # noqa
+ N/A
"""
if self.auth_public_key:
@@ -600,13 +573,13 @@
Raises
Attempt to authenticate with public key authentication
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if unknown (i.e. not auth failed) exception occurs
+ exc: if unknown (i.e. not auth failed) exception occurs
"""
try:
@@ -627,13 +600,13 @@
Raises
Attempt to authenticate with password authentication
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if unknown (i.e. not auth failed) exception occurs
+ exc: if unknown (i.e. not auth failed) exception occurs
"""
try:
@@ -656,13 +629,13 @@
Check if socket is alive and session is authenticated
Args:
- N/A # noqa
+ N/A
Returns:
bool: True if socket is alive and session authenticated, else False
Raises:
- N/A # noqa
+ N/A
"""
if self.socket_isalive() and self.session.is_alive() and self.isauthenticated():
@@ -731,14 +704,13 @@
Raises
Read data from the channel
Args:
- N/A # noqa
+ N/A
Returns:
- bytes_read: int of bytes read
- output: bytes output as read from channel
+ bytes: bytes output as read from channel
Raises:
- N/A # noqa
+ N/A
"""
channel_read: bytes = self.channel.recv(65535)
@@ -752,35 +724,14 @@
Parent method to open session, authenticate and acquire shell
Args
-
N/A
-# noqa
+
N/A
Returns
N/A
-# noqa
+# noqa: DAR202
Raises
-
Exception
+
exc
if socket handshake fails
-
AuthenticationFailed
+
ScrapliAuthenticationFailed
if all authentication means fail
@@ -948,14 +934,14 @@
Raises
Parent method to open session, authenticate and acquire shell
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- Exception: if socket handshake fails
- AuthenticationFailed: if all authentication means fail
+ exc: if socket handshake fails
+ ScrapliAuthenticationFailed: if all authentication means fail
"""
if not self.socket_isalive():
@@ -974,30 +960,26 @@
Raises
if not self.isauthenticated():
msg = f"Authentication to host {self.host} failed"
LOG.critical(msg)
- raise NSSHAuthenticationFailed(msg)
+ raise ScrapliAuthenticationFailed(msg)
self._open_channel()
self.session_lock.release()
-
+
def read(self)
Read data from the channel
Args
-
N/A
-# noqa
+
N/A
Returns
-
bytes_read
-
int of bytes read
-
output
+
bytes
bytes output as read from channel
Raises
-
N/A
-# noqa
+
N/A
@@ -1009,14 +991,13 @@
Raises
Read data from the channel
Args:
- N/A # noqa
+ N/A
Returns:
- bytes_read: int of bytes read
- output: bytes output as read from channel
+ bytes: bytes output as read from channel
Raises:
- N/A # noqa
+ N/A
"""
channel_read: bytes = self.channel.recv(65535)
@@ -1026,21 +1007,19 @@
Magic repr method for Socket
Args:
- N/A # noqa
+ N/A
Returns:
- repr: repr for class object
+ str: repr for class object
Raises:
- N/A # noqa
+ N/A
"""
return f"Socket {self.__dict__}"
@@ -95,13 +95,14 @@
Module nssh.transport.socket
Open underlying socket
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- SetupTimeout: if socket connection times out
+ ConnectionRefusedError: if socket refuses connection
+ ScrapliTimeout: if socket connection times out
"""
if not self.socket_isalive():
@@ -118,7 +119,7 @@
Module nssh.transport.socket
)
except socket.timeout:
LOG.critical(f"Timed out trying to open socket to {self.host} on port {self.port}")
- raise NSSHTimeout(
+ raise ScrapliTimeout(
f"Timed out trying to open socket to {self.host} on port {self.port}"
)
LOG.debug(f"Socket to host {self.host} opened")
@@ -128,13 +129,13 @@
Magic repr method for Socket
Args:
- N/A # noqa
+ N/A
Returns:
- repr: repr for class object
+ str: repr for class object
Raises:
- N/A # noqa
+ N/A
"""
return f"Socket {self.__dict__}"
@@ -244,13 +245,14 @@
Classes
Open underlying socket
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- SetupTimeout: if socket connection times out
+ ConnectionRefusedError: if socket refuses connection
+ ScrapliTimeout: if socket connection times out
"""
if not self.socket_isalive():
@@ -267,7 +269,7 @@
Classes
)
except socket.timeout:
LOG.critical(f"Timed out trying to open socket to {self.host} on port {self.port}")
- raise NSSHTimeout(
+ raise ScrapliTimeout(
f"Timed out trying to open socket to {self.host} on port {self.port}"
)
LOG.debug(f"Socket to host {self.host} opened")
@@ -277,13 +279,13 @@
Check if socket is alive
Args:
- N/A # noqa
+ N/A
Returns:
bool True/False if socket is alive
Raises:
- N/A # noqa
+ N/A
"""
try:
@@ -410,23 +408,24 @@
Raises
return False
-
+
def socket_open(self)
Open underlying socket
Args
-
N/A
-# noqa
+
N/A
Returns
N/A
-# noqa
+# noqa: DAR202
Raises
-
SetupTimeout
+
ConnectionRefusedError
+
if socket refuses connection
+
ScrapliTimeout
if socket connection times out
@@ -438,13 +437,14 @@
Raises
Open underlying socket
Args:
- N/A # noqa
+ N/A
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- SetupTimeout: if socket connection times out
+ ConnectionRefusedError: if socket refuses connection
+ ScrapliTimeout: if socket connection times out
"""
if not self.socket_isalive():
@@ -461,7 +461,7 @@
Raises
)
except socket.timeout:
LOG.critical(f"Timed out trying to open socket to {self.host} on port {self.port}")
- raise NSSHTimeout(
+ raise ScrapliTimeout(
f"Timed out trying to open socket to {self.host} on port {self.port}"
)
LOG.debug(f"Socket to host {self.host} opened")
@@ -480,17 +480,17 @@
comms_prompt_pattern: prompt pattern expected for device, same as the one provided to
channel -- system ssh needs to know this to know how to decide if we are properly
sending/receiving data -- i.e. we are not stuck at some password prompt or some
- other failure scenario. If using driver, this should be passed from driver (NSSH, or
- IOSXE, etc.) to this Transport class.
+ other failure scenario. If using driver, this should be passed from driver (Scrape,
+ or IOSXE, etc.) to this Transport class.
comms_return_char: return character to use on the channel, same as the one provided to
channel -- system ssh needs to know this to know what to send so that we can probe
the channel to make sure we are authenticated and sending/receiving data. If using
- driver, this should be passed from driver (NSSH, or IOSXE, etc.) to this Transport
+ driver, this should be passed from driver (Scrape, or IOSXE, etc.) to this Transport
class.
ssh_config_file: string to path for ssh config file, True to use default ssh config file
or False to ignore default ssh config file
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- AuthenticationFailed: if all authentication means fail
+ N/A
"""
self.host: str = host
@@ -124,7 +125,7 @@
output = pty_session.read()
if b"password" in output.lower():
pty_session.write(self.auth_password.encode())
- pty_session.write(b"\n")
+ pty_session.write(self.comms_return_char.encode())
break
attempt_count += 1
if attempt_count > 250:
@@ -304,13 +306,13 @@
Module nssh.transport.systemssh
Beyond that we lock the session and send the return character and re-read the channel.
Args:
- N/A # noqa
+ pty_session: PtyProcess session object
Returns:
- authenticated: True if authenticated, else False
+ bool: True if authenticated, else False
Raises:
- N/A # noqa
+ N/A
"""
if pty_session.isalive() and not pty_session.eof():
@@ -338,13 +340,13 @@
prompt pattern expected for device, same as the one provided to
channel – system ssh needs to know this to know how to decide if we are properly
sending/receiving data – i.e. we are not stuck at some password prompt or some
-other failure scenario. If using driver, this should be passed from driver (NSSH, or
-IOSXE, etc.) to this Transport class.
+other failure scenario. If using driver, this should be passed from driver (Scrape,
+or IOSXE, etc.) to this Transport class.
comms_return_char
return character to use on the channel, same as the one provided to
channel – system ssh needs to know this to know what to send so that we can probe
the channel to make sure we are authenticated and sending/receiving data. If using
-driver, this should be passed from driver (NSSH, or IOSXE, etc.) to this Transport
+driver, this should be passed from driver (Scrape, or IOSXE, etc.) to this Transport
class.
ssh_config_file
string to path for ssh config file, True to use default ssh config file
@@ -527,13 +508,13 @@
Args
Returns
N/A
-# noqa
+# noqa: DAR202
Raises
-
AuthenticationFailed
-
if all authentication means fail
+
N/A
+
@@ -572,21 +553,21 @@
Raises
comms_prompt_pattern: prompt pattern expected for device, same as the one provided to
channel -- system ssh needs to know this to know how to decide if we are properly
sending/receiving data -- i.e. we are not stuck at some password prompt or some
- other failure scenario. If using driver, this should be passed from driver (NSSH, or
- IOSXE, etc.) to this Transport class.
+ other failure scenario. If using driver, this should be passed from driver (Scrape,
+ or IOSXE, etc.) to this Transport class.
comms_return_char: return character to use on the channel, same as the one provided to
channel -- system ssh needs to know this to know what to send so that we can probe
the channel to make sure we are authenticated and sending/receiving data. If using
- driver, this should be passed from driver (NSSH, or IOSXE, etc.) to this Transport
+ driver, this should be passed from driver (Scrape, or IOSXE, etc.) to this Transport
class.
ssh_config_file: string to path for ssh config file, True to use default ssh config file
or False to ignore default ssh config file
Returns:
- N/A # noqa
+ N/A # noqa: DAR202
Raises:
- AuthenticationFailed: if all authentication means fail
+ N/A
"""
self.host: str = host
@@ -603,7 +584,7 @@
output = pty_session.read()
if b"password" in output.lower():
pty_session.write(self.auth_password.encode())
- pty_session.write(b"\n")
+ pty_session.write(self.comms_return_char.encode())
break
attempt_count += 1
if attempt_count > 250:
@@ -783,13 +765,13 @@
Raises
Beyond that we lock the session and send the return character and re-read the channel.
Args:
- N/A # noqa
+ pty_session: PtyProcess session object
Returns:
- authenticated: True if authenticated, else False
+ bool: True if authenticated, else False
Raises:
- N/A # noqa
+ N/A
"""
if pty_session.isalive() and not pty_session.eof():
@@ -817,13 +799,13 @@
"""scrapli.transport.telnet"""
+import re
+import time
+from logging import getLogger
+from select import select
+from telnetlib import Telnet
+from threading import Lock
+from typing import Optional
+
+from scrapli.exceptions import ScrapliAuthenticationFailed
+from scrapli.helper import get_prompt_pattern
+from scrapli.transport.transport import Transport
+
+LOG = getLogger("transport")
+
+TELNET_TRANSPORT_ARGS = (
+ "host",
+ "port",
+ "timeout_ssh",
+ "timeout_socket",
+ "auth_username",
+ "auth_public_key",
+ "auth_password",
+ "comms_prompt_pattern",
+ "comms_return_char",
+)
+
+
+class ScrapliTelnet(Telnet):
+ def __init__(self, host: str, port: int) -> None:
+ self.eof: bool
+ super().__init__(host, port)
+
+
+class TelnetTransport(Transport):
+ def __init__(
+ self,
+ host: str,
+ port: int = 23,
+ auth_username: str = "",
+ auth_public_key: str = "",
+ auth_password: str = "",
+ timeout_ssh: int = 5000,
+ timeout_socket: int = 5,
+ comms_prompt_pattern: str = r"^[a-z0-9.\-@()/:]{1,32}[#>$]$",
+ comms_return_char: str = "\n",
+ ): # pylint: disable=W0231
+ """
+ TelnetTransport Object
+
+ Inherit from Transport ABC and Socket base class:
+ TelnetTransport <- Transport (ABC)
+ TelnetTransport <- Socket
+
+ Args:
+ host: host ip/name to connect to
+ port: port to connect to
+ auth_username: username for authentication
+ auth_public_key: path to public key for authentication
+ auth_password: password for authentication
+ timeout_socket: timeout for establishing socket in seconds
+ timeout_ssh: timeout for ssh transport in milliseconds
+ comms_prompt_pattern: prompt pattern expected for device, same as the one provided to
+ channel -- system ssh needs to know this to know how to decide if we are properly
+ sending/receiving data -- i.e. we are not stuck at some password prompt or some
+ other failure scenario. If using driver, this should be passed from driver (Scrape,
+ or IOSXE, etc.) to this Transport class.
+ comms_return_char: return character to use on the channel, same as the one provided to
+ channel -- system ssh needs to know this to know what to send so that we can probe
+ the channel to make sure we are authenticated and sending/receiving data. If using
+ driver, this should be passed from driver (Scrape, or IOSXE, etc.) to this Transport
+ class.
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ N/A
+
+ """
+ self.host: str = host
+ self.port: int = port
+ self.timeout_ssh: int = timeout_ssh
+ self.timeout_socket: int = timeout_socket
+ self.session_lock: Lock = Lock()
+ self.auth_username: str = auth_username
+ self.auth_public_key: str = auth_public_key
+ self.auth_password: str = auth_password
+ self.comms_prompt_pattern: str = comms_prompt_pattern
+ self.comms_return_char: str = comms_return_char
+
+ self.session: ScrapliTelnet
+ self.lib_auth_exception = ScrapliAuthenticationFailed
+ self._isauthenticated = False
+
+ def open(self) -> None:
+ """
+ Open channel, acquire pty, request interactive shell
+
+ Args:
+ N/A
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ ScrapliAuthenticationFailed: if cant successfully authenticate
+
+ """
+ self.session_lock.acquire()
+ telnet_session = ScrapliTelnet(host=self.host, port=self.port)
+ LOG.debug(f"Session to host {self.host} spawned")
+ self.session_lock.release()
+ self._authenticate(telnet_session)
+ if not self._telnet_isauthenticated(telnet_session):
+ raise ScrapliAuthenticationFailed(
+ f"Could not authenticate over telnet to host: {self.host}"
+ )
+ LOG.debug(f"Authenticated to host {self.host} with password")
+ print("SUCH GOOD SUCCESS")
+ self.session = telnet_session
+
+ def _authenticate(self, telnet_session: ScrapliTelnet) -> None:
+ """
+ Parent private method to handle telnet authentication
+
+ Args:
+ telnet_session: Telnet session object
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ N/A
+
+ """
+ self._authenticate_username(telnet_session)
+ self._authenticate_password(telnet_session)
+
+ def _authenticate_username(self, telnet_session: ScrapliTelnet) -> None:
+ """
+ Private method to enter username for telnet authentication
+
+ Args:
+ telnet_session: Telnet session object
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ exc: if unknown (i.e. not auth failed) exception occurs
+
+ """
+ self.session_lock.acquire()
+ try:
+ attempt_count = 0
+ while True:
+ output = telnet_session.read_eager()
+ if b"username:" in output.lower():
+ telnet_session.write(self.auth_username.encode())
+ telnet_session.write(self.comms_return_char.encode())
+ break
+ attempt_count += 1
+ if attempt_count > 1000:
+ break
+ except self.lib_auth_exception as exc:
+ LOG.critical(f"Did not see username prompt from {self.host} failed. Exception: {exc}.")
+ raise exc
+ finally:
+ self.session_lock.release()
+
+ def _authenticate_password(self, telnet_session: ScrapliTelnet) -> None:
+ """
+ Private method to enter password for telnet authentication
+
+ Args:
+ telnet_session: Telnet session object
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ exc: if unknown (i.e. not auth failed) exception occurs
+
+ """
+ self.session_lock.acquire()
+ try:
+ attempt_count = 0
+ while True:
+ output = telnet_session.read_eager()
+ if b"password" in output.lower():
+ telnet_session.write(self.auth_password.encode())
+ telnet_session.write(self.comms_return_char.encode())
+ break
+ attempt_count += 1
+ if attempt_count > 1000:
+ break
+ except self.lib_auth_exception as exc:
+ LOG.critical(f"Password authentication with host {self.host} failed. Exception: {exc}.")
+ except Exception as exc:
+ LOG.critical(
+ "Unknown error occurred during password authentication with host "
+ f"{self.host}; Exception: {exc}"
+ )
+ raise exc
+ finally:
+ self.session_lock.release()
+
+ def _telnet_isauthenticated(self, telnet_session: ScrapliTelnet) -> bool:
+ """
+ Check if session is authenticated
+
+ This is very naive -- it only knows if the sub process is alive and has not received an EOF.
+ Beyond that we lock the session and send the return character and re-read the channel.
+
+ Args:
+ telnet_session: Telnet session object
+
+ Returns:
+ bool: True if authenticated, else False
+
+ Raises:
+ N/A
+
+ """
+ if not telnet_session.eof:
+ prompt_pattern = get_prompt_pattern("", self.comms_prompt_pattern)
+ telnet_session_fd = telnet_session.fileno()
+ self.session_lock.acquire()
+ telnet_session.write(self.comms_return_char.encode())
+ time.sleep(0.25)
+ fd_ready, _, _ = select([telnet_session_fd], [], [], 0)
+ if telnet_session_fd in fd_ready:
+ output = telnet_session.read_eager()
+ # we do not need to deal w/ line replacement for the actual output, only for
+ # parsing if a prompt-like thing is at the end of the output
+ output = re.sub(b"\r", b"\n", output.strip())
+ channel_match = re.search(prompt_pattern, output)
+ if channel_match:
+ self.session_lock.release()
+ self._isauthenticated = True
+ return True
+ self.session_lock.release()
+ return False
+
+ def close(self) -> None:
+ """
+ Close session and socket
+
+ Args:
+ N/A
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ N/A
+
+ """
+ self.session_lock.acquire()
+ self.session.close()
+ LOG.debug(f"Channel to host {self.host} closed")
+ self.session_lock.release()
+
+ def isalive(self) -> bool:
+ """
+ Check if socket is alive and session is authenticated
+
+ Args:
+ N/A
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ N/A
+
+ """
+
+ def read(self) -> bytes:
+ """
+ Read data from the channel
+
+ Args:
+ N/A
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ N/A
+
+ """
+ return self.session.read_eager()
+
+ def write(self, channel_input: str) -> None:
+ """
+ Write data to the channel
+
+ Args:
+ channel_input: string to send to channel
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ N/A
+
+ """
+ self.session.write(channel_input.encode())
+
+ def set_timeout(self, timeout: Optional[int] = None) -> None:
+ """
+ Set session timeout
+
+ Args:
+ timeout: timeout in seconds
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ N/A
+
+ """
+
+ def set_blocking(self, blocking: bool = False) -> None:
+ """
+ Set session blocking configuration
+
+ Args:
+ blocking: True/False set session to blocking
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ N/A
+
+ """
+
+
+
+
+
+
+
+
+
+
Classes
+
+
+class ScrapliTelnet
+(host, port)
+
+
+
Telnet interface class.
+
An instance of this class represents a connection to a telnet
+server.
+The instance is initially not connected; the open()
+method must be used to establish a connection.
+Alternatively, the
+host name and optional port number can be passed to the
+constructor, too.
+
Don't try to reopen an already connected instance.
+
This class has many read_*() methods.
+Note that some of them
+raise EOFError when the end of the connection is read, because
+they can return an empty string for other reasons.
+See the
+individual doc strings.
+
read_until(expected, [timeout])
+Read until the expected string has been seen, or a timeout is
+hit (default is no timeout); may block.
+
read_all()
+Read all data until EOF; may block.
+
read_some()
+Read at least one byte or EOF; may block.
+
read_very_eager()
+Read all data available already queued or on the socket,
+without blocking.
+
read_eager()
+Read either data already queued or some data available on the
+socket, without blocking.
+
read_lazy()
+Read all data in the raw queue (processing it first), without
+doing any socket I/O.
+
read_very_lazy()
+Reads all data in the cooked queue, without doing any socket
+I/O.
+
read_sb_data()
+Reads available data between SB … SE sequence. Don't block.
+
set_option_negotiation_callback(callback)
+Each time a telnet option is read on the input flow, this callback
+(if set) is called with the following parameters :
+callback(telnet socket, command, option)
+option will be chr(0) when there is no option.
+No other action is done afterwards by telnetlib.
+
Constructor.
+
When called without arguments, create an unconnected instance.
+With a hostname argument, it connects the instance; port number
+and timeout are optional.
Helper class that provides a standard way to create an ABC using
+inheritance.
+
TelnetTransport Object
+
Inherit from Transport ABC and Socket base class:
+TelnetTransport <- Transport (ABC)
+TelnetTransport <- Socket
+
Args
+
+
host
+
host ip/name to connect to
+
port
+
port to connect to
+
auth_username
+
username for authentication
+
auth_public_key
+
path to public key for authentication
+
auth_password
+
password for authentication
+
timeout_socket
+
timeout for establishing socket in seconds
+
timeout_ssh
+
timeout for ssh transport in milliseconds
+
comms_prompt_pattern
+
prompt pattern expected for device, same as the one provided to
+channel – system ssh needs to know this to know how to decide if we are properly
+sending/receiving data – i.e. we are not stuck at some password prompt or some
+other failure scenario. If using driver, this should be passed from driver (Scrape,
+or IOSXE, etc.) to this Transport class.
+
comms_return_char
+
return character to use on the channel, same as the one provided to
+channel – system ssh needs to know this to know what to send so that we can probe
+the channel to make sure we are authenticated and sending/receiving data. If using
+driver, this should be passed from driver (Scrape, or IOSXE, etc.) to this Transport
+class.
+
+
Returns
+
+
N/A
+# noqa: DAR202
+
+
+
Raises
+
+
N/A
+
+
+
+
+Expand source code
+
+
class TelnetTransport(Transport):
+ def __init__(
+ self,
+ host: str,
+ port: int = 23,
+ auth_username: str = "",
+ auth_public_key: str = "",
+ auth_password: str = "",
+ timeout_ssh: int = 5000,
+ timeout_socket: int = 5,
+ comms_prompt_pattern: str = r"^[a-z0-9.\-@()/:]{1,32}[#>$]$",
+ comms_return_char: str = "\n",
+ ): # pylint: disable=W0231
+ """
+ TelnetTransport Object
+
+ Inherit from Transport ABC and Socket base class:
+ TelnetTransport <- Transport (ABC)
+ TelnetTransport <- Socket
+
+ Args:
+ host: host ip/name to connect to
+ port: port to connect to
+ auth_username: username for authentication
+ auth_public_key: path to public key for authentication
+ auth_password: password for authentication
+ timeout_socket: timeout for establishing socket in seconds
+ timeout_ssh: timeout for ssh transport in milliseconds
+ comms_prompt_pattern: prompt pattern expected for device, same as the one provided to
+ channel -- system ssh needs to know this to know how to decide if we are properly
+ sending/receiving data -- i.e. we are not stuck at some password prompt or some
+ other failure scenario. If using driver, this should be passed from driver (Scrape,
+ or IOSXE, etc.) to this Transport class.
+ comms_return_char: return character to use on the channel, same as the one provided to
+ channel -- system ssh needs to know this to know what to send so that we can probe
+ the channel to make sure we are authenticated and sending/receiving data. If using
+ driver, this should be passed from driver (Scrape, or IOSXE, etc.) to this Transport
+ class.
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ N/A
+
+ """
+ self.host: str = host
+ self.port: int = port
+ self.timeout_ssh: int = timeout_ssh
+ self.timeout_socket: int = timeout_socket
+ self.session_lock: Lock = Lock()
+ self.auth_username: str = auth_username
+ self.auth_public_key: str = auth_public_key
+ self.auth_password: str = auth_password
+ self.comms_prompt_pattern: str = comms_prompt_pattern
+ self.comms_return_char: str = comms_return_char
+
+ self.session: ScrapliTelnet
+ self.lib_auth_exception = ScrapliAuthenticationFailed
+ self._isauthenticated = False
+
+ def open(self) -> None:
+ """
+ Open channel, acquire pty, request interactive shell
+
+ Args:
+ N/A
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ ScrapliAuthenticationFailed: if cant successfully authenticate
+
+ """
+ self.session_lock.acquire()
+ telnet_session = ScrapliTelnet(host=self.host, port=self.port)
+ LOG.debug(f"Session to host {self.host} spawned")
+ self.session_lock.release()
+ self._authenticate(telnet_session)
+ if not self._telnet_isauthenticated(telnet_session):
+ raise ScrapliAuthenticationFailed(
+ f"Could not authenticate over telnet to host: {self.host}"
+ )
+ LOG.debug(f"Authenticated to host {self.host} with password")
+ print("SUCH GOOD SUCCESS")
+ self.session = telnet_session
+
+ def _authenticate(self, telnet_session: ScrapliTelnet) -> None:
+ """
+ Parent private method to handle telnet authentication
+
+ Args:
+ telnet_session: Telnet session object
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ N/A
+
+ """
+ self._authenticate_username(telnet_session)
+ self._authenticate_password(telnet_session)
+
+ def _authenticate_username(self, telnet_session: ScrapliTelnet) -> None:
+ """
+ Private method to enter username for telnet authentication
+
+ Args:
+ telnet_session: Telnet session object
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ exc: if unknown (i.e. not auth failed) exception occurs
+
+ """
+ self.session_lock.acquire()
+ try:
+ attempt_count = 0
+ while True:
+ output = telnet_session.read_eager()
+ if b"username:" in output.lower():
+ telnet_session.write(self.auth_username.encode())
+ telnet_session.write(self.comms_return_char.encode())
+ break
+ attempt_count += 1
+ if attempt_count > 1000:
+ break
+ except self.lib_auth_exception as exc:
+ LOG.critical(f"Did not see username prompt from {self.host} failed. Exception: {exc}.")
+ raise exc
+ finally:
+ self.session_lock.release()
+
+ def _authenticate_password(self, telnet_session: ScrapliTelnet) -> None:
+ """
+ Private method to enter password for telnet authentication
+
+ Args:
+ telnet_session: Telnet session object
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ exc: if unknown (i.e. not auth failed) exception occurs
+
+ """
+ self.session_lock.acquire()
+ try:
+ attempt_count = 0
+ while True:
+ output = telnet_session.read_eager()
+ if b"password" in output.lower():
+ telnet_session.write(self.auth_password.encode())
+ telnet_session.write(self.comms_return_char.encode())
+ break
+ attempt_count += 1
+ if attempt_count > 1000:
+ break
+ except self.lib_auth_exception as exc:
+ LOG.critical(f"Password authentication with host {self.host} failed. Exception: {exc}.")
+ except Exception as exc:
+ LOG.critical(
+ "Unknown error occurred during password authentication with host "
+ f"{self.host}; Exception: {exc}"
+ )
+ raise exc
+ finally:
+ self.session_lock.release()
+
+ def _telnet_isauthenticated(self, telnet_session: ScrapliTelnet) -> bool:
+ """
+ Check if session is authenticated
+
+ This is very naive -- it only knows if the sub process is alive and has not received an EOF.
+ Beyond that we lock the session and send the return character and re-read the channel.
+
+ Args:
+ telnet_session: Telnet session object
+
+ Returns:
+ bool: True if authenticated, else False
+
+ Raises:
+ N/A
+
+ """
+ if not telnet_session.eof:
+ prompt_pattern = get_prompt_pattern("", self.comms_prompt_pattern)
+ telnet_session_fd = telnet_session.fileno()
+ self.session_lock.acquire()
+ telnet_session.write(self.comms_return_char.encode())
+ time.sleep(0.25)
+ fd_ready, _, _ = select([telnet_session_fd], [], [], 0)
+ if telnet_session_fd in fd_ready:
+ output = telnet_session.read_eager()
+ # we do not need to deal w/ line replacement for the actual output, only for
+ # parsing if a prompt-like thing is at the end of the output
+ output = re.sub(b"\r", b"\n", output.strip())
+ channel_match = re.search(prompt_pattern, output)
+ if channel_match:
+ self.session_lock.release()
+ self._isauthenticated = True
+ return True
+ self.session_lock.release()
+ return False
+
+ def close(self) -> None:
+ """
+ Close session and socket
+
+ Args:
+ N/A
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ N/A
+
+ """
+ self.session_lock.acquire()
+ self.session.close()
+ LOG.debug(f"Channel to host {self.host} closed")
+ self.session_lock.release()
+
+ def isalive(self) -> bool:
+ """
+ Check if socket is alive and session is authenticated
+
+ Args:
+ N/A
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ N/A
+
+ """
+
+ def read(self) -> bytes:
+ """
+ Read data from the channel
+
+ Args:
+ N/A
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ N/A
+
+ """
+ return self.session.read_eager()
+
+ def write(self, channel_input: str) -> None:
+ """
+ Write data to the channel
+
+ Args:
+ channel_input: string to send to channel
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ N/A
+
+ """
+ self.session.write(channel_input.encode())
+
+ def set_timeout(self, timeout: Optional[int] = None) -> None:
+ """
+ Set session timeout
+
+ Args:
+ timeout: timeout in seconds
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ N/A
+
+ """
+
+ def set_blocking(self, blocking: bool = False) -> None:
+ """
+ Set session blocking configuration
+
+ Args:
+ blocking: True/False set session to blocking
+
+ Returns:
+ N/A # noqa: DAR202
+
+ Raises:
+ N/A
+
+ """
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/nssh/transport/transport.html b/docs/scrapli/transport/transport.html
similarity index 71%
rename from docs/nssh/transport/transport.html
rename to docs/scrapli/transport/transport.html
index 71b85104..d606cf52 100644
--- a/docs/nssh/transport/transport.html
+++ b/docs/scrapli/transport/transport.html
@@ -4,8 +4,8 @@
-nssh.transport.transport API documentation
-
+scrapli.transport.transport API documentation
+
@@ -17,15 +17,15 @@
-
Module nssh.transport.transport
+
Module scrapli.transport.transport
-
nssh.transport.transport
+
scrapli.transport.transport
Expand source code
-
"""nssh.transport.transport"""
+
"""scrapli.transport.transport"""
from abc import ABC, abstractmethod
from threading import Lock
from typing import Dict, Optional, Union
@@ -42,13 +42,13 @@
Module nssh.transport.transport
Magic bool method for Socket
Args:
- N/A # noqa
+ N/A
Returns:
bool: True/False if socket is alive or not
Raises:
- N/A # noqa
+ N/A
"""
return self.isalive()
@@ -58,13 +58,13 @@