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 @@ - - - - - - -nssh.driver.core API documentation - - - - - - - - - -
-
-
-

Module nssh.driver.core

-
-
-

nssh.driver.core

-
- -Expand source code - -
"""nssh.driver.core"""
-from nssh.driver.core.arista_eos.driver import EOSDriver
-from nssh.driver.core.cisco_iosxe.driver import IOSXEDriver
-from nssh.driver.core.cisco_iosxr.driver import IOSXRDriver
-from nssh.driver.core.cisco_nxos.driver import NXOSDriver
-from nssh.driver.core.juniper_junos.driver import JunosDriver
-
-__all__ = (
-    "EOSDriver",
-    "IOSXEDriver",
-    "IOSXRDriver",
-    "NXOSDriver",
-    "JunosDriver",
-)
-
-
-
-

Sub-modules

-
-
nssh.driver.core.arista_eos
-
-

nssh.driver.core.arista_eos

-
-
nssh.driver.core.cisco_iosxe
-
-

nssh.driver.core.cisco_iosxe

-
-
nssh.driver.core.cisco_iosxr
-
-

nssh.driver.core.cisco_iosxr

-
-
nssh.driver.core.cisco_nxos
-
-

nssh.driver.core.cisco_nxos

-
-
nssh.driver.core.juniper_junos
-
-

nssh.driver.core.juniper_junos

-
-
-
-
-
-
-
-
-

Classes

-
-
-class EOSDriver -(auth_secondary='', **kwargs) -
-
-

EOSDriver Object

-

Args

-
-
auth_secondary
-
password to use for secondary authentication (enable)
-
**kwargs
-
keyword args to pass to inherited class(es)
-
-

Returns

-
-
N/A -# noqa
-
 
-
-

Raises

-
-
N/A -# noqa
-
 
-
-
- -Expand source code - -
class EOSDriver(NetworkDriver):
-    def __init__(self, auth_secondary: str = "", **kwargs: Dict[str, Any]):
-        """
-        EOSDriver Object
-
-        Args:
-            auth_secondary: password to use for secondary authentication (enable)
-            **kwargs: keyword args to pass to inherited class(es)
-
-        Returns:
-            N/A  # noqa
-
-        Raises:
-            N/A  # noqa
-        """
-        super().__init__(auth_secondary, **kwargs)
-        self.privs = PRIVS
-        self.default_desired_priv = "privilege_exec"
-        self.textfsm_platform = "arista_eos"
-        self.exit_command = "exit"
-
-

Ancestors

- -

Inherited members

- -
-
-class IOSXEDriver -(auth_secondary='', **kwargs) -
-
-

IOSXEDriver Object

-

Args

-
-
auth_secondary
-
password to use for secondary authentication (enable)
-
**kwargs
-
keyword args to pass to inherited class(es)
-
-

Returns

-
-
N/A -# noqa
-
 
-
-

Raises

-
-
N/A -# noqa
-
 
-
-
- -Expand source code - -
class IOSXEDriver(NetworkDriver):
-    def __init__(self, auth_secondary: str = "", **kwargs: Dict[str, Any]):
-        """
-        IOSXEDriver Object
-
-        Args:
-            auth_secondary: password to use for secondary authentication (enable)
-            **kwargs: keyword args to pass to inherited class(es)
-
-        Returns:
-            N/A  # noqa
-
-        Raises:
-            N/A  # noqa
-        """
-        super().__init__(auth_secondary, **kwargs)
-        self.privs = PRIVS
-        self.default_desired_priv = "privilege_exec"
-        self.textfsm_platform = "cisco_ios"
-        self.exit_command = "exit"
-
-

Ancestors

- -

Inherited members

- -
-
-class IOSXRDriver -(auth_secondary='', **kwargs) -
-
-

IOSXRDriver Object

-

Args

-
-
auth_secondary
-
password to use for secondary authentication (enable)
-
**kwargs
-
keyword args to pass to inherited class(es)
-
-

Returns

-
-
N/A -# noqa
-
 
-
-

Raises

-
-
N/A -# noqa
-
 
-
-
- -Expand source code - -
class IOSXRDriver(NetworkDriver):
-    def __init__(self, auth_secondary: str = "", **kwargs: Dict[str, Any]):
-        """
-        IOSXRDriver Object
-
-        Args:
-            auth_secondary: password to use for secondary authentication (enable)
-            **kwargs: keyword args to pass to inherited class(es)
-
-        Returns:
-            N/A  # noqa
-
-        Raises:
-            N/A  # noqa
-        """
-        super().__init__(auth_secondary, **kwargs)
-        self.privs = PRIVS
-        self.default_desired_priv = "privilege_exec"
-        self.textfsm_platform = "cisco_xr"
-        self.exit_command = "exit"
-
-

Ancestors

- -

Inherited members

- -
-
-class JunosDriver -(auth_secondary='', **kwargs) -
-
-

JunosDriver Object

-

Args

-
-
auth_secondary
-
password to use for secondary authentication (enable)
-
**kwargs
-
keyword args to pass to inherited class(es)
-
-

Returns

-
-
N/A -# noqa
-
 
-
-

Raises

-
-
N/A -# noqa
-
 
-
-
- -Expand source code - -
class JunosDriver(NetworkDriver):
-    def __init__(self, auth_secondary: str = "", **kwargs: Dict[str, Any]):
-        """
-        JunosDriver Object
-
-        Args:
-            auth_secondary: password to use for secondary authentication (enable)
-            **kwargs: keyword args to pass to inherited class(es)
-
-        Returns:
-            N/A  # noqa
-
-        Raises:
-            N/A  # noqa
-        """
-        super().__init__(auth_secondary, **kwargs)
-        self.privs = PRIVS
-        self.default_desired_priv = "exec"
-        self.textfsm_platform = "juniper_junos"
-        self.exit_command = "exit"
-
-

Ancestors

- -

Inherited members

- -
-
-class NXOSDriver -(auth_secondary='', **kwargs) -
-
-

NXOSDriver Object

-

Args

-
-
auth_secondary
-
password to use for secondary authentication (enable)
-
**kwargs
-
keyword args to pass to inherited class(es)
-
-

Returns

-
-
N/A -# noqa
-
 
-
-

Raises

-
-
N/A -# noqa
-
 
-
-
- -Expand source code - -
class NXOSDriver(NetworkDriver):
-    def __init__(self, auth_secondary: str = "", **kwargs: Dict[str, Any]):
-        """
-        NXOSDriver Object
-
-        Args:
-            auth_secondary: password to use for secondary authentication (enable)
-            **kwargs: keyword args to pass to inherited class(es)
-
-        Returns:
-            N/A  # noqa
-
-        Raises:
-            N/A  # noqa
-        """
-        super().__init__(auth_secondary, **kwargs)
-        self.privs = PRIVS
-        self.default_desired_priv = "privilege_exec"
-        self.textfsm_platform = "cisco_nxos"
-        self.exit_command = "exit"
-
-

Ancestors

- -

Inherited members

- -
-
-
-
- -
- - - - - \ 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 @@

Module nssh.channel.channel

Magic str method for Channel Args: - N/A # noqa + N/A Returns: - N/A # noqa + str: str for class object Raises: - N/A # noqa + N/A """ - return "nssh Channel Object" + return "scrapli Channel Object" def __repr__(self) -> str: """ Magic repr method for Channel Args: - N/A # noqa + N/A Returns: - repr: repr for class object + str: repr for class object Raises: - N/A # noqa + N/A """ class_dict = self.__dict__.copy() class_dict.pop("transport") - return f"nssh Channel {class_dict}" + return f"scrapli Channel {class_dict}" def _restructure_output(self, output: bytes, strip_prompt: bool = False) -> bytes: """ @@ -124,10 +125,10 @@

Module nssh.channel.channel

strip_prompt: bool True/False whether to strip prompt or not Returns: - output: bytes of joined output lines optionally with prompt removed + bytes: output of joined output lines optionally with prompt removed Raises: - N/A # noqa + N/A """ output = normalize_lines(output) @@ -145,13 +146,13 @@

Module nssh.channel.channel

Private method to read chunk and strip comms_ansi if needed Args: - N/A # noqa + N/A Returns: - output: output read from channel + bytes: output read from channel Raises: - N/A # noqa + N/A """ new_output = self.transport.read() @@ -168,10 +169,10 @@

Module nssh.channel.channel

channel_input: string to write to channel Returns: - output: output read from channel + bytes: output read from channel Raises: - N/A # noqa + N/A """ output = b"" @@ -188,10 +189,10 @@

Module nssh.channel.channel

prompt: prompt to look for if not looking for base prompt (self.comms_prompt_pattern) Returns: - output: output read from channel + bytes: output read from channel Raises: - N/A # noqa + N/A """ prompt_pattern = get_prompt_pattern(prompt, self.comms_prompt_pattern) @@ -202,7 +203,7 @@

Module nssh.channel.channel

while True: output += self._read_chunk() - output = re.sub(b"\r", b"", output.strip()) + output = re.sub(b"\r", b"", output) channel_match = re.search(prompt_pattern, output) if channel_match: self.transport.set_blocking(True) @@ -214,18 +215,17 @@

Module nssh.channel.channel

Get current channel prompt Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ prompt_pattern = get_prompt_pattern("", self.comms_prompt_pattern) self.transport.set_timeout(1000) - self.transport.flush() self.transport.write(self.comms_return_char) LOG.debug(f"Write (sending return character): {repr(self.comms_return_char)}") while True: @@ -239,7 +239,7 @@

Module nssh.channel.channel

def send_inputs( self, inputs: Union[str, List[str], Tuple[str]], strip_prompt: bool = True - ) -> List[Result]: + ) -> List[Response]: """ Primary entry point to send data to devices in shell mode; accept inputs and return results @@ -248,24 +248,24 @@

Module nssh.channel.channel

strip_prompt: strip prompt or not, defaults to True (yes, strip the prompt) Returns: - results: list of Result object(s) + responses: list of Response object(s) Raises: - N/A # noqa + N/A """ if isinstance(inputs, (list, tuple)): raw_inputs = tuple(inputs) else: raw_inputs = (inputs,) - results = [] + responses = [] for channel_input in raw_inputs: - result = Result(self.transport.host, channel_input) + response = Response(self.transport.host, channel_input) raw_result, processed_result = self._send_input(channel_input, strip_prompt) - 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(self, channel_input: str, strip_prompt: bool) -> Tuple[bytes, bytes]: @@ -280,12 +280,11 @@

Module nssh.channel.channel

result: 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: {channel_input}; strip_prompt: {strip_prompt}") self.transport.write(channel_input) LOG.debug(f"Write: {repr(channel_input)}") @@ -298,78 +297,74 @@

Module nssh.channel.channel

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 @@

Module nssh.channel.channel

Send return char to device Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ - self.transport.flush() self.transport.write(self.comms_return_char) LOG.debug(f"Write (sending return character): {repr(self.comms_return_char)}")
@@ -421,7 +415,7 @@

Module nssh.channel.channel

Classes

-
+
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 @@

Raises

Magic str method for Channel Args: - N/A # noqa + N/A Returns: - N/A # noqa + str: str for class object Raises: - N/A # noqa + N/A """ - return "nssh Channel Object" + return "scrapli Channel Object" def __repr__(self) -> str: """ Magic repr method for Channel Args: - N/A # noqa + N/A Returns: - repr: repr for class object + str: repr for class object Raises: - N/A # noqa + N/A """ class_dict = self.__dict__.copy() class_dict.pop("transport") - return f"nssh Channel {class_dict}" + return f"scrapli Channel {class_dict}" def _restructure_output(self, output: bytes, strip_prompt: bool = False) -> bytes: """ @@ -534,10 +529,10 @@

Raises

strip_prompt: bool True/False whether to strip prompt or not Returns: - output: bytes of joined output lines optionally with prompt removed + bytes: output of joined output lines optionally with prompt removed Raises: - N/A # noqa + N/A """ output = normalize_lines(output) @@ -555,13 +550,13 @@

Raises

Private method to read chunk and strip comms_ansi if needed Args: - N/A # noqa + N/A Returns: - output: output read from channel + bytes: output read from channel Raises: - N/A # noqa + N/A """ new_output = self.transport.read() @@ -578,10 +573,10 @@

Raises

channel_input: string to write to channel Returns: - output: output read from channel + bytes: output read from channel Raises: - N/A # noqa + N/A """ output = b"" @@ -598,10 +593,10 @@

Raises

prompt: prompt to look for if not looking for base prompt (self.comms_prompt_pattern) Returns: - output: output read from channel + bytes: output read from channel Raises: - N/A # noqa + N/A """ prompt_pattern = get_prompt_pattern(prompt, self.comms_prompt_pattern) @@ -612,7 +607,7 @@

Raises

while True: output += self._read_chunk() - output = re.sub(b"\r", b"", output.strip()) + output = re.sub(b"\r", b"", output) channel_match = re.search(prompt_pattern, output) if channel_match: self.transport.set_blocking(True) @@ -624,18 +619,17 @@

Raises

Get current channel prompt Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ prompt_pattern = get_prompt_pattern("", self.comms_prompt_pattern) self.transport.set_timeout(1000) - self.transport.flush() self.transport.write(self.comms_return_char) LOG.debug(f"Write (sending return character): {repr(self.comms_return_char)}") while True: @@ -649,7 +643,7 @@

Raises

def send_inputs( self, inputs: Union[str, List[str], Tuple[str]], strip_prompt: bool = True - ) -> List[Result]: + ) -> List[Response]: """ Primary entry point to send data to devices in shell mode; accept inputs and return results @@ -658,24 +652,24 @@

Raises

strip_prompt: strip prompt or not, defaults to True (yes, strip the prompt) Returns: - results: list of Result object(s) + responses: list of Response object(s) Raises: - N/A # noqa + N/A """ if isinstance(inputs, (list, tuple)): raw_inputs = tuple(inputs) else: raw_inputs = (inputs,) - results = [] + responses = [] for channel_input in raw_inputs: - result = Result(self.transport.host, channel_input) + response = Response(self.transport.host, channel_input) raw_result, processed_result = self._send_input(channel_input, strip_prompt) - 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(self, channel_input: str, strip_prompt: bool) -> Tuple[bytes, bytes]: @@ -690,12 +684,11 @@

Raises

result: 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: {channel_input}; strip_prompt: {strip_prompt}") self.transport.write(channel_input) LOG.debug(f"Write: {repr(channel_input)}") @@ -708,78 +701,74 @@

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 + 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 @@

Raises

Send return char to device Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ - self.transport.flush() self.transport.write(self.comms_return_char) LOG.debug(f"Write (sending return character): {repr(self.comms_return_char)}")

Methods

-
+
def get_prompt(self, *args, **kwargs)
@@ -848,7 +836,7 @@

Methods

signal.signal(signal.SIGALRM, old)
-
+
def send_inputs(self, inputs, strip_prompt=True)
@@ -862,13 +850,12 @@

Args

Returns

-
results
-
list of Result object(s)
+
responses
+
list of Response object(s)

Raises

-
N/A -# noqa
+
N/A
 
@@ -877,7 +864,7 @@

Raises

def send_inputs(
     self, inputs: Union[str, List[str], Tuple[str]], strip_prompt: bool = True
-) -> List[Result]:
+) -> List[Response]:
     """
     Primary entry point to send data to devices in shell mode; accept inputs and return results
 
@@ -886,57 +873,51 @@ 

Raises

strip_prompt: strip prompt or not, defaults to True (yes, strip the prompt) Returns: - results: list of Result object(s) + responses: list of Response object(s) Raises: - N/A # noqa + N/A """ if isinstance(inputs, (list, tuple)): raw_inputs = tuple(inputs) else: raw_inputs = (inputs,) - results = [] + responses = [] for channel_input in raw_inputs: - result = Result(self.transport.host, channel_input) + response = Response(self.transport.host, channel_input) raw_result, processed_result = self._send_input(channel_input, strip_prompt) - 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
-
+
def send_inputs_interact(self, inputs, hidden_response=False)
-

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
+ response.raw_result = raw_result.decode() + response.record_response(processed_result.decode().strip()) + responses.append(response) + return responses
@@ -997,17 +974,17 @@

Index

  • Super-module

  • Classes

    diff --git a/docs/nssh/channel/index.html b/docs/scrapli/channel/index.html similarity index 75% rename from docs/nssh/channel/index.html rename to docs/scrapli/channel/index.html index d39996b0..a54f675c 100644 --- a/docs/nssh/channel/index.html +++ b/docs/scrapli/channel/index.html @@ -4,8 +4,8 @@ -nssh.channel API documentation - +scrapli.channel API documentation + @@ -17,16 +17,16 @@
    -

    Module nssh.channel

    +

    Module scrapli.channel

    -

    nssh.channel

    +

    scrapli.channel

    Expand source code -
    """nssh.channel"""
    -from nssh.channel.channel import CHANNEL_ARGS, Channel
    +
    """scrapli.channel"""
    +from scrapli.channel.channel import CHANNEL_ARGS, Channel
     
     __all__ = ("Channel", "CHANNEL_ARGS")
    @@ -34,9 +34,9 @@

    Module nssh.channel

    Sub-modules

    -
    nssh.channel.channel
    +
    scrapli.channel.channel
    -

    nssh.channel.channel

    +

    scrapli.channel.channel

    @@ -47,7 +47,7 @@

    Sub-modules

    Classes

    -
    +
    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 @@

    Raises

    Magic str method for Channel Args: - N/A # noqa + N/A Returns: - N/A # noqa + str: str for class object Raises: - N/A # noqa + N/A """ - return "nssh Channel Object" + return "scrapli Channel Object" def __repr__(self) -> str: """ Magic repr method for Channel Args: - N/A # noqa + N/A Returns: - repr: repr for class object + str: repr for class object Raises: - N/A # noqa + N/A """ class_dict = self.__dict__.copy() class_dict.pop("transport") - return f"nssh Channel {class_dict}" + return f"scrapli Channel {class_dict}" def _restructure_output(self, output: bytes, strip_prompt: bool = False) -> bytes: """ @@ -160,10 +161,10 @@

    Raises

    strip_prompt: bool True/False whether to strip prompt or not Returns: - output: bytes of joined output lines optionally with prompt removed + bytes: output of joined output lines optionally with prompt removed Raises: - N/A # noqa + N/A """ output = normalize_lines(output) @@ -181,13 +182,13 @@

    Raises

    Private method to read chunk and strip comms_ansi if needed Args: - N/A # noqa + N/A Returns: - output: output read from channel + bytes: output read from channel Raises: - N/A # noqa + N/A """ new_output = self.transport.read() @@ -204,10 +205,10 @@

    Raises

    channel_input: string to write to channel Returns: - output: output read from channel + bytes: output read from channel Raises: - N/A # noqa + N/A """ output = b"" @@ -224,10 +225,10 @@

    Raises

    prompt: prompt to look for if not looking for base prompt (self.comms_prompt_pattern) Returns: - output: output read from channel + bytes: output read from channel Raises: - N/A # noqa + N/A """ prompt_pattern = get_prompt_pattern(prompt, self.comms_prompt_pattern) @@ -238,7 +239,7 @@

    Raises

    while True: output += self._read_chunk() - output = re.sub(b"\r", b"", output.strip()) + output = re.sub(b"\r", b"", output) channel_match = re.search(prompt_pattern, output) if channel_match: self.transport.set_blocking(True) @@ -250,18 +251,17 @@

    Raises

    Get current channel prompt Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ prompt_pattern = get_prompt_pattern("", self.comms_prompt_pattern) self.transport.set_timeout(1000) - self.transport.flush() self.transport.write(self.comms_return_char) LOG.debug(f"Write (sending return character): {repr(self.comms_return_char)}") while True: @@ -275,7 +275,7 @@

    Raises

    def send_inputs( self, inputs: Union[str, List[str], Tuple[str]], strip_prompt: bool = True - ) -> List[Result]: + ) -> List[Response]: """ Primary entry point to send data to devices in shell mode; accept inputs and return results @@ -284,24 +284,24 @@

    Raises

    strip_prompt: strip prompt or not, defaults to True (yes, strip the prompt) Returns: - results: list of Result object(s) + responses: list of Response object(s) Raises: - N/A # noqa + N/A """ if isinstance(inputs, (list, tuple)): raw_inputs = tuple(inputs) else: raw_inputs = (inputs,) - results = [] + responses = [] for channel_input in raw_inputs: - result = Result(self.transport.host, channel_input) + response = Response(self.transport.host, channel_input) raw_result, processed_result = self._send_input(channel_input, strip_prompt) - 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(self, channel_input: str, strip_prompt: bool) -> Tuple[bytes, bytes]: @@ -316,12 +316,11 @@

    Raises

    result: 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: {channel_input}; strip_prompt: {strip_prompt}") self.transport.write(channel_input) LOG.debug(f"Write: {repr(channel_input)}") @@ -334,78 +333,74 @@

    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 + 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 @@

    Raises

    Send return char to device Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ - self.transport.flush() self.transport.write(self.comms_return_char) LOG.debug(f"Write (sending return character): {repr(self.comms_return_char)}")

    Methods

    -
    +
    def get_prompt(self, *args, **kwargs)
    @@ -474,7 +468,7 @@

    Methods

    signal.signal(signal.SIGALRM, old)
    -
    +
    def send_inputs(self, inputs, strip_prompt=True)
    @@ -488,13 +482,12 @@

    Args

    Returns

    -
    results
    -
    list of Result object(s)
    +
    responses
    +
    list of Response object(s)

    Raises

    -
    N/A -# noqa
    +
    N/A
     
    @@ -503,7 +496,7 @@

    Raises

    def send_inputs(
         self, inputs: Union[str, List[str], Tuple[str]], strip_prompt: bool = True
    -) -> List[Result]:
    +) -> List[Response]:
         """
         Primary entry point to send data to devices in shell mode; accept inputs and return results
     
    @@ -512,57 +505,51 @@ 

    Raises

    strip_prompt: strip prompt or not, defaults to True (yes, strip the prompt) Returns: - results: list of Result object(s) + responses: list of Response object(s) Raises: - N/A # noqa + N/A """ if isinstance(inputs, (list, tuple)): raw_inputs = tuple(inputs) else: raw_inputs = (inputs,) - results = [] + responses = [] for channel_input in raw_inputs: - result = Result(self.transport.host, channel_input) + response = Response(self.transport.host, channel_input) raw_result, processed_result = self._send_input(channel_input, strip_prompt) - 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
    -
    +
    def send_inputs_interact(self, inputs, hidden_response=False)
    -

    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
    + response.raw_result = raw_result.decode() + response.record_response(processed_result.decode().strip()) + responses.append(response) + return responses
    @@ -623,22 +606,22 @@

    Index

    • Super-module

    • Sub-modules

    • Classes

      diff --git a/docs/nssh/decorators.html b/docs/scrapli/decorators.html similarity index 93% rename from docs/nssh/decorators.html rename to docs/scrapli/decorators.html index 4fd8a977..c04c49d4 100644 --- a/docs/nssh/decorators.html +++ b/docs/scrapli/decorators.html @@ -4,8 +4,8 @@ -nssh.decorators API documentation - +scrapli.decorators API documentation + @@ -17,19 +17,19 @@
      -

      Module nssh.decorators

      +

      Module scrapli.decorators

      -

      nssh.decorators

      +

      scrapli.decorators

      Expand source code -
      """nssh.decorators"""
      +
      """scrapli.decorators"""
       import logging
       from typing import Any, Callable, Dict, Union
       
      -LOG = logging.getLogger("nssh")
      +LOG = logging.getLogger("scrapli")
       
       
       def operation_timeout(attribute: str) -> Callable[..., Any]:
      @@ -81,7 +81,7 @@ 

      Module nssh.decorators

      Functions

      -
      +
      def operation_timeout(attribute)
      @@ -161,12 +161,12 @@

      Index

      diff --git a/docs/nssh/driver/community/index.html b/docs/scrapli/driver/community/index.html similarity index 92% rename from docs/nssh/driver/community/index.html rename to docs/scrapli/driver/community/index.html index 676d5471..34c3a5e3 100644 --- a/docs/nssh/driver/community/index.html +++ b/docs/scrapli/driver/community/index.html @@ -4,8 +4,8 @@ -nssh.driver.community API documentation - +scrapli.driver.community API documentation + @@ -17,17 +17,15 @@
      -

      Module nssh.driver.community

      +

      Module scrapli.driver.community

      -

      nssh.driver.community

      +

      scrapli.driver.community

      Expand source code -
      """nssh.driver.community"""
      -
      -__all__ = ()
      +
      """scrapli.driver.community"""
      @@ -47,7 +45,7 @@

      Index

      diff --git a/docs/nssh/driver/core/arista_eos/driver.html b/docs/scrapli/driver/core/arista_eos/driver.html similarity index 75% rename from docs/nssh/driver/core/arista_eos/driver.html rename to docs/scrapli/driver/core/arista_eos/driver.html index 01c48f43..23eb1abc 100644 --- a/docs/nssh/driver/core/arista_eos/driver.html +++ b/docs/scrapli/driver/core/arista_eos/driver.html @@ -4,8 +4,8 @@ -nssh.driver.core.arista_eos.driver API documentation - +scrapli.driver.core.arista_eos.driver API documentation + @@ -17,19 +17,19 @@
      -

      Module nssh.driver.core.arista_eos.driver

      +

      Module scrapli.driver.core.arista_eos.driver

      -

      nssh.driver.core.arista_eos.driver

      +

      scrapli.driver.core.arista_eos.driver

      Expand source code -
      """nssh.driver.core.arista_eos.driver"""
      +
      """scrapli.driver.core.arista_eos.driver"""
       from typing import Any, Dict
       
      -from nssh.driver import NetworkDriver
      -from nssh.driver.network_driver import PrivilegeLevel
      +from scrapli.driver import NetworkDriver
      +from scrapli.driver.network_driver import PrivilegeLevel
       
       EOS_ARG_MAPPER = {
           "comms_prompt_regex": r"^[a-z0-9.\-@()/:]{1,32}[#>$]$",
      @@ -108,10 +108,10 @@ 

      Module nssh.driver.core.arista_eos.driver

      **kwargs: keyword args to pass to inherited class(es) Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ super().__init__(auth_secondary, **kwargs) self.privs = PRIVS @@ -129,7 +129,7 @@

      Module nssh.driver.core.arista_eos.driver

      Classes

      -
      +
      class EOSDriver (auth_secondary='', **kwargs)
      @@ -145,13 +145,12 @@

      Args

      Returns

      N/A -# noqa
      +# noqa: DAR202
       

      Raises

      -
      N/A -# noqa
      +
      N/A
       
      @@ -168,10 +167,10 @@

      Raises

      **kwargs: keyword args to pass to inherited class(es) Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ super().__init__(auth_secondary, **kwargs) self.privs = PRIVS @@ -181,22 +180,21 @@

      Raises

      Ancestors

      Inherited members

      @@ -212,13 +210,13 @@

      Index

      • Super-module

      • Classes

      • diff --git a/docs/nssh/driver/core/arista_eos/index.html b/docs/scrapli/driver/core/arista_eos/index.html similarity index 69% rename from docs/nssh/driver/core/arista_eos/index.html rename to docs/scrapli/driver/core/arista_eos/index.html index a9914c15..b6b74007 100644 --- a/docs/nssh/driver/core/arista_eos/index.html +++ b/docs/scrapli/driver/core/arista_eos/index.html @@ -4,8 +4,8 @@ -nssh.driver.core.arista_eos API documentation - +scrapli.driver.core.arista_eos API documentation + @@ -17,16 +17,16 @@
        -

        Module nssh.driver.core.arista_eos

        +

        Module scrapli.driver.core.arista_eos

        -

        nssh.driver.core.arista_eos

        +

        scrapli.driver.core.arista_eos

        Expand source code -
        """nssh.driver.core.arista_eos"""
        -from nssh.driver.core.arista_eos.driver import EOSDriver
        +
        """scrapli.driver.core.arista_eos"""
        +from scrapli.driver.core.arista_eos.driver import EOSDriver
         
         __all__ = ("EOSDriver",)
        @@ -34,9 +34,9 @@

        Module nssh.driver.core.arista_eos

        Sub-modules

        -
        nssh.driver.core.arista_eos.driver
        +
        scrapli.driver.core.arista_eos.driver
        -

        nssh.driver.core.arista_eos.driver

        +

        scrapli.driver.core.arista_eos.driver

        @@ -47,7 +47,7 @@

        Sub-modules

        Classes

        -
        +
        class EOSDriver (auth_secondary='', **kwargs)
        @@ -63,13 +63,12 @@

        Args

        Returns

        N/A -# noqa
        +# noqa: DAR202
         

        Raises

        -
        N/A -# noqa
        +
        N/A
         
        @@ -86,10 +85,10 @@

        Raises

        **kwargs: keyword args to pass to inherited class(es) Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ super().__init__(auth_secondary, **kwargs) self.privs = PRIVS @@ -99,22 +98,21 @@

        Raises

        Ancestors

        Inherited members

        @@ -130,18 +128,18 @@

        Index

        • Super-module

        • Sub-modules

        • Classes

        • diff --git a/docs/nssh/driver/core/cisco_iosxe/driver.html b/docs/scrapli/driver/core/cisco_iosxe/driver.html similarity index 75% rename from docs/nssh/driver/core/cisco_iosxe/driver.html rename to docs/scrapli/driver/core/cisco_iosxe/driver.html index f2296a97..386ec204 100644 --- a/docs/nssh/driver/core/cisco_iosxe/driver.html +++ b/docs/scrapli/driver/core/cisco_iosxe/driver.html @@ -4,8 +4,8 @@ -nssh.driver.core.cisco_iosxe.driver API documentation - +scrapli.driver.core.cisco_iosxe.driver API documentation + @@ -17,19 +17,19 @@
          -

          Module nssh.driver.core.cisco_iosxe.driver

          +

          Module scrapli.driver.core.cisco_iosxe.driver

          -

          nssh.driver.core.cisco_iosxe.driver

          +

          scrapli.driver.core.cisco_iosxe.driver

          Expand source code -
          """nssh.driver.core.cisco_iosxe.driver"""
          +
          """scrapli.driver.core.cisco_iosxe.driver"""
           from typing import Any, Dict
           
          -from nssh.driver import NetworkDriver
          -from nssh.driver.network_driver import PrivilegeLevel
          +from scrapli.driver import NetworkDriver
          +from scrapli.driver.network_driver import PrivilegeLevel
           
           IOSXE_ARG_MAPPER = {
               "comms_prompt_pattern": r"^[a-z0-9.\-@()/:]{1,32}[#>$]$",
          @@ -108,10 +108,10 @@ 

          Module nssh.driver.core.cisco_iosxe.driver

          **kwargs: keyword args to pass to inherited class(es) Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ super().__init__(auth_secondary, **kwargs) self.privs = PRIVS @@ -129,7 +129,7 @@

          Module nssh.driver.core.cisco_iosxe.driver

          Classes

          -
          +
          class IOSXEDriver (auth_secondary='', **kwargs)
          @@ -145,13 +145,12 @@

          Args

          Returns

          N/A -# noqa
          +# noqa: DAR202
           

          Raises

          -
          N/A -# noqa
          +
          N/A
           
          @@ -168,10 +167,10 @@

          Raises

          **kwargs: keyword args to pass to inherited class(es) Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ super().__init__(auth_secondary, **kwargs) self.privs = PRIVS @@ -181,22 +180,21 @@

          Raises

          Ancestors

          Inherited members

          @@ -212,13 +210,13 @@

          Index

          • Super-module

          • Classes

          • diff --git a/docs/nssh/driver/core/cisco_iosxe/index.html b/docs/scrapli/driver/core/cisco_iosxe/index.html similarity index 69% rename from docs/nssh/driver/core/cisco_iosxe/index.html rename to docs/scrapli/driver/core/cisco_iosxe/index.html index 68bba764..099b779a 100644 --- a/docs/nssh/driver/core/cisco_iosxe/index.html +++ b/docs/scrapli/driver/core/cisco_iosxe/index.html @@ -4,8 +4,8 @@ -nssh.driver.core.cisco_iosxe API documentation - +scrapli.driver.core.cisco_iosxe API documentation + @@ -17,16 +17,16 @@
            -

            Module nssh.driver.core.cisco_iosxe

            +

            Module scrapli.driver.core.cisco_iosxe

            -

            nssh.driver.core.cisco_iosxe

            +

            scrapli.driver.core.cisco_iosxe

            Expand source code -
            """nssh.driver.core.cisco_iosxe"""
            -from nssh.driver.core.cisco_iosxe.driver import IOSXEDriver
            +
            """scrapli.driver.core.cisco_iosxe"""
            +from scrapli.driver.core.cisco_iosxe.driver import IOSXEDriver
             
             __all__ = ("IOSXEDriver",)
            @@ -34,9 +34,9 @@

            Module nssh.driver.core.cisco_iosxe

            Sub-modules

            -
            nssh.driver.core.cisco_iosxe.driver
            +
            scrapli.driver.core.cisco_iosxe.driver
            -

            nssh.driver.core.cisco_iosxe.driver

            +

            scrapli.driver.core.cisco_iosxe.driver

            @@ -47,7 +47,7 @@

            Sub-modules

            Classes

            -
            +
            class IOSXEDriver (auth_secondary='', **kwargs)
            @@ -63,13 +63,12 @@

            Args

            Returns

            N/A -# noqa
            +# noqa: DAR202
             

            Raises

            -
            N/A -# noqa
            +
            N/A
             
            @@ -86,10 +85,10 @@

            Raises

            **kwargs: keyword args to pass to inherited class(es) Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ super().__init__(auth_secondary, **kwargs) self.privs = PRIVS @@ -99,22 +98,21 @@

            Raises

            Ancestors

            Inherited members

            @@ -130,18 +128,18 @@

            Index

            • Super-module

            • Sub-modules

            • Classes

            • diff --git a/docs/nssh/driver/core/cisco_iosxr/driver.html b/docs/scrapli/driver/core/cisco_iosxr/driver.html similarity index 74% rename from docs/nssh/driver/core/cisco_iosxr/driver.html rename to docs/scrapli/driver/core/cisco_iosxr/driver.html index 166c7cf8..f76b5be9 100644 --- a/docs/nssh/driver/core/cisco_iosxr/driver.html +++ b/docs/scrapli/driver/core/cisco_iosxr/driver.html @@ -4,8 +4,8 @@ -nssh.driver.core.cisco_iosxr.driver API documentation - +scrapli.driver.core.cisco_iosxr.driver API documentation + @@ -17,24 +17,24 @@
              -

              Module nssh.driver.core.cisco_iosxr.driver

              +

              Module scrapli.driver.core.cisco_iosxr.driver

              -

              nssh.driver.core.cisco_iosxr.driver

              +

              scrapli.driver.core.cisco_iosxr.driver

              Expand source code -
              """nssh.driver.core.cisco_iosxr.driver"""
              +
              """scrapli.driver.core.cisco_iosxr.driver"""
               from typing import Any, Dict
               
              -from nssh.driver import NetworkDriver
              -from nssh.driver.network_driver import PrivilegeLevel
              +from scrapli.driver import NetworkDriver
              +from scrapli.driver.network_driver import PrivilegeLevel
               
               IOSXR_ARG_MAPPER = {
                   "comms_prompt_regex": r"^[a-z0-9.\-@()/:]{1,32}[#>$]$",
                   "comms_return_char": "\n",
              -    "comms_pre_login_handler": "nssh.driver.core.cisco_iosxr.helper.comms_pre_login_handler",
              +    "comms_pre_login_handler": "scrapli.driver.core.cisco_iosxr.helper.comms_pre_login_handler",
                   "comms_disable_paging": "terminal length 0",
               }
               
              @@ -94,10 +94,10 @@ 

              Module nssh.driver.core.cisco_iosxr.driver

              **kwargs: keyword args to pass to inherited class(es) Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ super().__init__(auth_secondary, **kwargs) self.privs = PRIVS @@ -115,7 +115,7 @@

              Module nssh.driver.core.cisco_iosxr.driver

              Classes

              -
              +
              class IOSXRDriver (auth_secondary='', **kwargs)
              @@ -131,13 +131,12 @@

              Args

              Returns

              N/A -# noqa
              +# noqa: DAR202
               

              Raises

              -
              N/A -# noqa
              +
              N/A
               
              @@ -154,10 +153,10 @@

              Raises

              **kwargs: keyword args to pass to inherited class(es) Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ super().__init__(auth_secondary, **kwargs) self.privs = PRIVS @@ -167,22 +166,21 @@

              Raises

              Ancestors

              Inherited members

              @@ -198,13 +196,13 @@

              Index

              • Super-module

              • Classes

              • diff --git a/docs/nssh/driver/core/cisco_iosxr/helper.html b/docs/scrapli/driver/core/cisco_iosxr/helper.html similarity index 87% rename from docs/nssh/driver/core/cisco_iosxr/helper.html rename to docs/scrapli/driver/core/cisco_iosxr/helper.html index e1e8e80c..fc092e39 100644 --- a/docs/nssh/driver/core/cisco_iosxr/helper.html +++ b/docs/scrapli/driver/core/cisco_iosxr/helper.html @@ -4,8 +4,8 @@ -nssh.driver.core.cisco_iosxr.helper API documentation - +scrapli.driver.core.cisco_iosxr.helper API documentation + @@ -17,18 +17,18 @@
                -

                Module nssh.driver.core.cisco_iosxr.helper

                +

                Module scrapli.driver.core.cisco_iosxr.helper

                -

                nssh.driver.core.cisco_iosxr.helper

                +

                scrapli.driver.core.cisco_iosxr.helper

                Expand source code -
                """nssh.driver.core.cisco_iosxr.helper"""
                +
                """scrapli.driver.core.cisco_iosxr.helper"""
                 import time
                 
                -from nssh.driver import NetworkDriver
                +from scrapli.driver import NetworkDriver
                 
                 
                 def comms_pre_login_handler(cls: NetworkDriver) -> None:  # pylint: disable=W0613
                @@ -39,10 +39,10 @@ 

                Module nssh.driver.core.cisco_iosxr.helper

                cls: IOSXRDriver object Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ # sleep for session to establish; without this we never find base prompt @@ -56,7 +56,7 @@

                Module nssh.driver.core.cisco_iosxr.helper

                Functions

                -
                +
                def comms_pre_login_handler(cls)
                @@ -69,13 +69,12 @@

                Args

                Returns

                N/A -# noqa
                +# noqa: DAR202
                 

                Raises

                -
                N/A -# noqa
                +
                N/A
                 
                @@ -90,10 +89,10 @@

                Raises

                cls: IOSXRDriver object Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ # sleep for session to establish; without this we never find base prompt @@ -113,12 +112,12 @@

                Index

                diff --git a/docs/nssh/driver/core/cisco_iosxr/index.html b/docs/scrapli/driver/core/cisco_iosxr/index.html similarity index 66% rename from docs/nssh/driver/core/cisco_iosxr/index.html rename to docs/scrapli/driver/core/cisco_iosxr/index.html index 83934c65..20ba24a9 100644 --- a/docs/nssh/driver/core/cisco_iosxr/index.html +++ b/docs/scrapli/driver/core/cisco_iosxr/index.html @@ -4,8 +4,8 @@ -nssh.driver.core.cisco_iosxr API documentation - +scrapli.driver.core.cisco_iosxr API documentation + @@ -17,16 +17,16 @@
                -

                Module nssh.driver.core.cisco_iosxr

                +

                Module scrapli.driver.core.cisco_iosxr

                -

                nssh.driver.core.cisco_iosxr

                +

                scrapli.driver.core.cisco_iosxr

                Expand source code -
                """nssh.driver.core.cisco_iosxr"""
                -from nssh.driver.core.cisco_iosxr.driver import IOSXRDriver
                +
                """scrapli.driver.core.cisco_iosxr"""
                +from scrapli.driver.core.cisco_iosxr.driver import IOSXRDriver
                 
                 __all__ = ("IOSXRDriver",)
                @@ -34,13 +34,13 @@

                Module nssh.driver.core.cisco_iosxr

                Sub-modules

                -
                nssh.driver.core.cisco_iosxr.driver
                +
                scrapli.driver.core.cisco_iosxr.driver
                -

                nssh.driver.core.cisco_iosxr.driver

                +

                scrapli.driver.core.cisco_iosxr.driver

                -
                nssh.driver.core.cisco_iosxr.helper
                +
                scrapli.driver.core.cisco_iosxr.helper
                -

                nssh.driver.core.cisco_iosxr.helper

                +

                scrapli.driver.core.cisco_iosxr.helper

                @@ -51,7 +51,7 @@

                Sub-modules

                Classes

                -
                +
                class IOSXRDriver (auth_secondary='', **kwargs)
                @@ -67,13 +67,12 @@

                Args

                Returns

                N/A -# noqa
                +# noqa: DAR202
                 

                Raises

                -
                N/A -# noqa
                +
                N/A
                 
                @@ -90,10 +89,10 @@

                Raises

                **kwargs: keyword args to pass to inherited class(es) Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ super().__init__(auth_secondary, **kwargs) self.privs = PRIVS @@ -103,22 +102,21 @@

                Raises

                Ancestors

                Inherited members

                @@ -134,19 +132,19 @@

                Index

                • Super-module

                • Sub-modules

                • Classes

                • diff --git a/docs/nssh/driver/core/cisco_nxos/driver.html b/docs/scrapli/driver/core/cisco_nxos/driver.html similarity index 75% rename from docs/nssh/driver/core/cisco_nxos/driver.html rename to docs/scrapli/driver/core/cisco_nxos/driver.html index fd16161b..1288f7a7 100644 --- a/docs/nssh/driver/core/cisco_nxos/driver.html +++ b/docs/scrapli/driver/core/cisco_nxos/driver.html @@ -4,8 +4,8 @@ -nssh.driver.core.cisco_nxos.driver API documentation - +scrapli.driver.core.cisco_nxos.driver API documentation + @@ -17,19 +17,19 @@
                  -

                  Module nssh.driver.core.cisco_nxos.driver

                  +

                  Module scrapli.driver.core.cisco_nxos.driver

                  -

                  nssh.driver.core.cisco_nxos.driver

                  +

                  scrapli.driver.core.cisco_nxos.driver

                  Expand source code -
                  """nssh.driver.core.cisco_nxos.driver"""
                  +
                  """scrapli.driver.core.cisco_nxos.driver"""
                   from typing import Any, Dict
                   
                  -from nssh.driver import NetworkDriver
                  -from nssh.driver.network_driver import PrivilegeLevel
                  +from scrapli.driver import NetworkDriver
                  +from scrapli.driver.network_driver import PrivilegeLevel
                   
                   NXOS_ARG_MAPPER = {
                       "comms_prompt_regex": r"^[a-z0-9.\-@()/:]{1,32}[#>$]$",
                  @@ -108,10 +108,10 @@ 

                  Module nssh.driver.core.cisco_nxos.driver

                  **kwargs: keyword args to pass to inherited class(es) Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ super().__init__(auth_secondary, **kwargs) self.privs = PRIVS @@ -129,7 +129,7 @@

                  Module nssh.driver.core.cisco_nxos.driver

                  Classes

                  -
                  +
                  class NXOSDriver (auth_secondary='', **kwargs)
                  @@ -145,13 +145,12 @@

                  Args

                  Returns

                  N/A -# noqa
                  +# noqa: DAR202
                   

                  Raises

                  -
                  N/A -# noqa
                  +
                  N/A
                   
                  @@ -168,10 +167,10 @@

                  Raises

                  **kwargs: keyword args to pass to inherited class(es) Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ super().__init__(auth_secondary, **kwargs) self.privs = PRIVS @@ -181,22 +180,21 @@

                  Raises

                  Ancestors

                  Inherited members

                  @@ -212,13 +210,13 @@

                  Index

                  • Super-module

                  • Classes

                  • diff --git a/docs/nssh/driver/core/cisco_nxos/index.html b/docs/scrapli/driver/core/cisco_nxos/index.html similarity index 69% rename from docs/nssh/driver/core/cisco_nxos/index.html rename to docs/scrapli/driver/core/cisco_nxos/index.html index 30cf4d9e..3928ed8e 100644 --- a/docs/nssh/driver/core/cisco_nxos/index.html +++ b/docs/scrapli/driver/core/cisco_nxos/index.html @@ -4,8 +4,8 @@ -nssh.driver.core.cisco_nxos API documentation - +scrapli.driver.core.cisco_nxos API documentation + @@ -17,16 +17,16 @@
                    -

                    Module nssh.driver.core.cisco_nxos

                    +

                    Module scrapli.driver.core.cisco_nxos

                    -

                    nssh.driver.core.cisco_nxos

                    +

                    scrapli.driver.core.cisco_nxos

                    Expand source code -
                    """nssh.driver.core.cisco_nxos"""
                    -from nssh.driver.core.cisco_nxos.driver import NXOSDriver
                    +
                    """scrapli.driver.core.cisco_nxos"""
                    +from scrapli.driver.core.cisco_nxos.driver import NXOSDriver
                     
                     __all__ = ("NXOSDriver",)
                    @@ -34,9 +34,9 @@

                    Module nssh.driver.core.cisco_nxos

                    Sub-modules

                    -
                    nssh.driver.core.cisco_nxos.driver
                    +
                    scrapli.driver.core.cisco_nxos.driver
                    -

                    nssh.driver.core.cisco_nxos.driver

                    +

                    scrapli.driver.core.cisco_nxos.driver

                    @@ -47,7 +47,7 @@

                    Sub-modules

                    Classes

                    -
                    +
                    class NXOSDriver (auth_secondary='', **kwargs)
                    @@ -63,13 +63,12 @@

                    Args

                    Returns

                    N/A -# noqa
                    +# noqa: DAR202
                     

                    Raises

                    -
                    N/A -# noqa
                    +
                    N/A
                     
                    @@ -86,10 +85,10 @@

                    Raises

                    **kwargs: keyword args to pass to inherited class(es) Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ super().__init__(auth_secondary, **kwargs) self.privs = PRIVS @@ -99,22 +98,21 @@

                    Raises

                    Ancestors

                    Inherited members

                    @@ -130,18 +128,18 @@

                    Index

                    • Super-module

                    • Sub-modules

                    • Classes

                    • diff --git a/docs/scrapli/driver/core/index.html b/docs/scrapli/driver/core/index.html new file mode 100644 index 00000000..d5c08695 --- /dev/null +++ b/docs/scrapli/driver/core/index.html @@ -0,0 +1,477 @@ + + + + + + +scrapli.driver.core API documentation + + + + + + + + + +
                      +
                      +
                      +

                      Module scrapli.driver.core

                      +
                      +
                      +

                      scrapli.driver.core

                      +
                      + +Expand source code + +
                      """scrapli.driver.core"""
                      +from scrapli.driver.core.arista_eos.driver import EOSDriver
                      +from scrapli.driver.core.cisco_iosxe.driver import IOSXEDriver
                      +from scrapli.driver.core.cisco_iosxr.driver import IOSXRDriver
                      +from scrapli.driver.core.cisco_nxos.driver import NXOSDriver
                      +from scrapli.driver.core.juniper_junos.driver import JunosDriver
                      +
                      +__all__ = (
                      +    "EOSDriver",
                      +    "IOSXEDriver",
                      +    "IOSXRDriver",
                      +    "NXOSDriver",
                      +    "JunosDriver",
                      +)
                      +
                      +
                      +
                      +

                      Sub-modules

                      +
                      +
                      scrapli.driver.core.arista_eos
                      +
                      +

                      scrapli.driver.core.arista_eos

                      +
                      +
                      scrapli.driver.core.cisco_iosxe
                      +
                      +

                      scrapli.driver.core.cisco_iosxe

                      +
                      +
                      scrapli.driver.core.cisco_iosxr
                      +
                      +

                      scrapli.driver.core.cisco_iosxr

                      +
                      +
                      scrapli.driver.core.cisco_nxos
                      +
                      +

                      scrapli.driver.core.cisco_nxos

                      +
                      +
                      scrapli.driver.core.juniper_junos
                      +
                      +

                      scrapli.driver.core.juniper_junos

                      +
                      +
                      +
                      +
                      +
                      +
                      +
                      +
                      +

                      Classes

                      +
                      +
                      +class EOSDriver +(auth_secondary='', **kwargs) +
                      +
                      +

                      EOSDriver Object

                      +

                      Args

                      +
                      +
                      auth_secondary
                      +
                      password to use for secondary authentication (enable)
                      +
                      **kwargs
                      +
                      keyword args to pass to inherited class(es)
                      +
                      +

                      Returns

                      +
                      +
                      N/A +# noqa: DAR202
                      +
                       
                      +
                      +

                      Raises

                      +
                      +
                      N/A
                      +
                       
                      +
                      +
                      + +Expand source code + +
                      class EOSDriver(NetworkDriver):
                      +    def __init__(self, auth_secondary: str = "", **kwargs: Dict[str, Any]):
                      +        """
                      +        EOSDriver Object
                      +
                      +        Args:
                      +            auth_secondary: password to use for secondary authentication (enable)
                      +            **kwargs: keyword args to pass to inherited class(es)
                      +
                      +        Returns:
                      +            N/A  # noqa: DAR202
                      +
                      +        Raises:
                      +            N/A
                      +        """
                      +        super().__init__(auth_secondary, **kwargs)
                      +        self.privs = PRIVS
                      +        self.default_desired_priv = "privilege_exec"
                      +        self.textfsm_platform = "arista_eos"
                      +        self.exit_command = "exit"
                      +
                      +

                      Ancestors

                      + +

                      Inherited members

                      + +
                      +
                      +class IOSXEDriver +(auth_secondary='', **kwargs) +
                      +
                      +

                      IOSXEDriver Object

                      +

                      Args

                      +
                      +
                      auth_secondary
                      +
                      password to use for secondary authentication (enable)
                      +
                      **kwargs
                      +
                      keyword args to pass to inherited class(es)
                      +
                      +

                      Returns

                      +
                      +
                      N/A +# noqa: DAR202
                      +
                       
                      +
                      +

                      Raises

                      +
                      +
                      N/A
                      +
                       
                      +
                      +
                      + +Expand source code + +
                      class IOSXEDriver(NetworkDriver):
                      +    def __init__(self, auth_secondary: str = "", **kwargs: Dict[str, Any]):
                      +        """
                      +        IOSXEDriver Object
                      +
                      +        Args:
                      +            auth_secondary: password to use for secondary authentication (enable)
                      +            **kwargs: keyword args to pass to inherited class(es)
                      +
                      +        Returns:
                      +            N/A  # noqa: DAR202
                      +
                      +        Raises:
                      +            N/A
                      +        """
                      +        super().__init__(auth_secondary, **kwargs)
                      +        self.privs = PRIVS
                      +        self.default_desired_priv = "privilege_exec"
                      +        self.textfsm_platform = "cisco_ios"
                      +        self.exit_command = "exit"
                      +
                      +

                      Ancestors

                      + +

                      Inherited members

                      + +
                      +
                      +class IOSXRDriver +(auth_secondary='', **kwargs) +
                      +
                      +

                      IOSXRDriver Object

                      +

                      Args

                      +
                      +
                      auth_secondary
                      +
                      password to use for secondary authentication (enable)
                      +
                      **kwargs
                      +
                      keyword args to pass to inherited class(es)
                      +
                      +

                      Returns

                      +
                      +
                      N/A +# noqa: DAR202
                      +
                       
                      +
                      +

                      Raises

                      +
                      +
                      N/A
                      +
                       
                      +
                      +
                      + +Expand source code + +
                      class IOSXRDriver(NetworkDriver):
                      +    def __init__(self, auth_secondary: str = "", **kwargs: Dict[str, Any]):
                      +        """
                      +        IOSXRDriver Object
                      +
                      +        Args:
                      +            auth_secondary: password to use for secondary authentication (enable)
                      +            **kwargs: keyword args to pass to inherited class(es)
                      +
                      +        Returns:
                      +            N/A  # noqa: DAR202
                      +
                      +        Raises:
                      +            N/A
                      +        """
                      +        super().__init__(auth_secondary, **kwargs)
                      +        self.privs = PRIVS
                      +        self.default_desired_priv = "privilege_exec"
                      +        self.textfsm_platform = "cisco_xr"
                      +        self.exit_command = "exit"
                      +
                      +

                      Ancestors

                      + +

                      Inherited members

                      + +
                      +
                      +class JunosDriver +(auth_secondary='', **kwargs) +
                      +
                      +

                      JunosDriver Object

                      +

                      Args

                      +
                      +
                      auth_secondary
                      +
                      password to use for secondary authentication (enable)
                      +
                      **kwargs
                      +
                      keyword args to pass to inherited class(es)
                      +
                      +

                      Returns

                      +
                      +
                      N/A +# noqa: DAR202
                      +
                       
                      +
                      +

                      Raises

                      +
                      +
                      N/A
                      +
                       
                      +
                      +
                      + +Expand source code + +
                      class JunosDriver(NetworkDriver):
                      +    def __init__(self, auth_secondary: str = "", **kwargs: Dict[str, Any]):
                      +        """
                      +        JunosDriver Object
                      +
                      +        Args:
                      +            auth_secondary: password to use for secondary authentication (enable)
                      +            **kwargs: keyword args to pass to inherited class(es)
                      +
                      +        Returns:
                      +            N/A  # noqa: DAR202
                      +
                      +        Raises:
                      +            N/A
                      +        """
                      +        super().__init__(auth_secondary, **kwargs)
                      +        self.privs = PRIVS
                      +        self.default_desired_priv = "exec"
                      +        self.textfsm_platform = "juniper_junos"
                      +        self.exit_command = "exit"
                      +
                      +

                      Ancestors

                      + +

                      Inherited members

                      + +
                      +
                      +class NXOSDriver +(auth_secondary='', **kwargs) +
                      +
                      +

                      NXOSDriver Object

                      +

                      Args

                      +
                      +
                      auth_secondary
                      +
                      password to use for secondary authentication (enable)
                      +
                      **kwargs
                      +
                      keyword args to pass to inherited class(es)
                      +
                      +

                      Returns

                      +
                      +
                      N/A +# noqa: DAR202
                      +
                       
                      +
                      +

                      Raises

                      +
                      +
                      N/A
                      +
                       
                      +
                      +
                      + +Expand source code + +
                      class NXOSDriver(NetworkDriver):
                      +    def __init__(self, auth_secondary: str = "", **kwargs: Dict[str, Any]):
                      +        """
                      +        NXOSDriver Object
                      +
                      +        Args:
                      +            auth_secondary: password to use for secondary authentication (enable)
                      +            **kwargs: keyword args to pass to inherited class(es)
                      +
                      +        Returns:
                      +            N/A  # noqa: DAR202
                      +
                      +        Raises:
                      +            N/A
                      +        """
                      +        super().__init__(auth_secondary, **kwargs)
                      +        self.privs = PRIVS
                      +        self.default_desired_priv = "privilege_exec"
                      +        self.textfsm_platform = "cisco_nxos"
                      +        self.exit_command = "exit"
                      +
                      +

                      Ancestors

                      + +

                      Inherited members

                      + +
                      +
                      +
                      +
                      + +
                      + + + + + \ No newline at end of file diff --git a/docs/nssh/driver/core/juniper_junos/driver.html b/docs/scrapli/driver/core/juniper_junos/driver.html similarity index 73% rename from docs/nssh/driver/core/juniper_junos/driver.html rename to docs/scrapli/driver/core/juniper_junos/driver.html index 4d042a98..213b730b 100644 --- a/docs/nssh/driver/core/juniper_junos/driver.html +++ b/docs/scrapli/driver/core/juniper_junos/driver.html @@ -4,8 +4,8 @@ -nssh.driver.core.juniper_junos.driver API documentation - +scrapli.driver.core.juniper_junos.driver API documentation + @@ -17,25 +17,25 @@
                      -

                      Module nssh.driver.core.juniper_junos.driver

                      +

                      Module scrapli.driver.core.juniper_junos.driver

                      -

                      nssh.driver.core.juniper_junos.driver

                      +

                      scrapli.driver.core.juniper_junos.driver

                      Expand source code -
                      """nssh.driver.core.juniper_junos.driver"""
                      +
                      """scrapli.driver.core.juniper_junos.driver"""
                       from typing import Any, Dict
                       
                      -from nssh.driver import NetworkDriver
                      -from nssh.driver.network_driver import PrivilegeLevel
                      +from scrapli.driver import NetworkDriver
                      +from scrapli.driver.network_driver import PrivilegeLevel
                       
                       JUNOS_ARG_MAPPER = {
                           "comms_prompt_regex": r"^[a-z0-9.\-@()/:]{1,32}[#>$]$",
                           "comms_return_char": "\n",
                           "comms_pre_login_handler": "",
                      -    "comms_disable_paging": "nssh.driver.core.juniper_junos.helper.disable_paging",
                      +    "comms_disable_paging": "scrapli.driver.core.juniper_junos.helper.disable_paging",
                       }
                       
                       PRIVS = {
                      @@ -80,10 +80,10 @@ 

                      Module nssh.driver.core.juniper_junos.driver

                      **kwargs: keyword args to pass to inherited class(es) Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ super().__init__(auth_secondary, **kwargs) self.privs = PRIVS @@ -101,7 +101,7 @@

                      Module nssh.driver.core.juniper_junos.driver

                      Classes

                      -
                      +
                      class JunosDriver (auth_secondary='', **kwargs)
                      @@ -117,13 +117,12 @@

                      Args

                      Returns

                      N/A -# noqa
                      +# noqa: DAR202
                       

                      Raises

                      -
                      N/A -# noqa
                      +
                      N/A
                       
                      @@ -140,10 +139,10 @@

                      Raises

                      **kwargs: keyword args to pass to inherited class(es) Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ super().__init__(auth_secondary, **kwargs) self.privs = PRIVS @@ -153,22 +152,21 @@

                      Raises

                      Ancestors

                      Inherited members

                      @@ -184,13 +182,13 @@

                      Index

                      • Super-module

                      • Classes

                      • diff --git a/docs/nssh/driver/core/juniper_junos/helper.html b/docs/scrapli/driver/core/juniper_junos/helper.html similarity index 87% rename from docs/nssh/driver/core/juniper_junos/helper.html rename to docs/scrapli/driver/core/juniper_junos/helper.html index 760d8b7b..c11e46d5 100644 --- a/docs/nssh/driver/core/juniper_junos/helper.html +++ b/docs/scrapli/driver/core/juniper_junos/helper.html @@ -4,8 +4,8 @@ -nssh.driver.core.juniper_junos.helper API documentation - +scrapli.driver.core.juniper_junos.helper API documentation + @@ -17,16 +17,16 @@
                        -

                        Module nssh.driver.core.juniper_junos.helper

                        +

                        Module scrapli.driver.core.juniper_junos.helper

                        -

                        nssh.driver.core.juniper_junos.helper

                        +

                        scrapli.driver.core.juniper_junos.helper

                        Expand source code -
                        """nssh.driver.core.juniper_junos.helper"""
                        -from nssh.driver import NetworkDriver
                        +
                        """scrapli.driver.core.juniper_junos.helper"""
                        +from scrapli.driver import NetworkDriver
                         
                         
                         def disable_paging(cls: NetworkDriver) -> None:
                        @@ -37,10 +37,10 @@ 

                        Module nssh.driver.core.juniper_junos.helper

                        cls: SSH2NetSocket connection object Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ cls.channel.send_inputs("set cli screen-length 0") cls.channel.send_inputs("set cli screen-width 511")
                        @@ -53,7 +53,7 @@

                        Module nssh.driver.core.juniper_junos.helper

                        Functions

                        -
                        +
                        def disable_paging(cls)
                        @@ -66,13 +66,12 @@

                        Args

                        Returns

                        N/A -# noqa
                        +# noqa: DAR202
                         

                        Raises

                        -
                        N/A -# noqa
                        +
                        N/A
                         
                        @@ -87,10 +86,10 @@

                        Raises

                        cls: SSH2NetSocket connection object Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ cls.channel.send_inputs("set cli screen-length 0") cls.channel.send_inputs("set cli screen-width 511")
                        @@ -109,12 +108,12 @@

                        Index

                        diff --git a/docs/nssh/driver/core/juniper_junos/index.html b/docs/scrapli/driver/core/juniper_junos/index.html similarity index 66% rename from docs/nssh/driver/core/juniper_junos/index.html rename to docs/scrapli/driver/core/juniper_junos/index.html index 9d568109..f4b97e3a 100644 --- a/docs/nssh/driver/core/juniper_junos/index.html +++ b/docs/scrapli/driver/core/juniper_junos/index.html @@ -4,8 +4,8 @@ -nssh.driver.core.juniper_junos API documentation - +scrapli.driver.core.juniper_junos API documentation + @@ -17,16 +17,16 @@
                        -

                        Module nssh.driver.core.juniper_junos

                        +

                        Module scrapli.driver.core.juniper_junos

                        -

                        nssh.driver.core.juniper_junos

                        +

                        scrapli.driver.core.juniper_junos

                        Expand source code -
                        """nssh.driver.core.juniper_junos"""
                        -from nssh.driver.core.juniper_junos.driver import JunosDriver
                        +
                        """scrapli.driver.core.juniper_junos"""
                        +from scrapli.driver.core.juniper_junos.driver import JunosDriver
                         
                         __all__ = ("JunosDriver",)
                        @@ -34,13 +34,13 @@

                        Module nssh.driver.core.juniper_junos

                        Sub-modules

                        -
                        nssh.driver.core.juniper_junos.driver
                        +
                        scrapli.driver.core.juniper_junos.driver
                        -

                        nssh.driver.core.juniper_junos.driver

                        +

                        scrapli.driver.core.juniper_junos.driver

                        -
                        nssh.driver.core.juniper_junos.helper
                        +
                        scrapli.driver.core.juniper_junos.helper
                        -

                        nssh.driver.core.juniper_junos.helper

                        +

                        scrapli.driver.core.juniper_junos.helper

                        @@ -51,7 +51,7 @@

                        Sub-modules

                        Classes

                        -
                        +
                        class JunosDriver (auth_secondary='', **kwargs)
                        @@ -67,13 +67,12 @@

                        Args

                        Returns

                        N/A -# noqa
                        +# noqa: DAR202
                         

                        Raises

                        -
                        N/A -# noqa
                        +
                        N/A
                         
                        @@ -90,10 +89,10 @@

                        Raises

                        **kwargs: keyword args to pass to inherited class(es) Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ super().__init__(auth_secondary, **kwargs) self.privs = PRIVS @@ -103,22 +102,21 @@

                        Raises

                        Ancestors

                        Inherited members

                        @@ -134,19 +132,19 @@

                        Index

                        • Super-module

                        • Sub-modules

                        • Classes

                        • diff --git a/docs/nssh/driver/driver.html b/docs/scrapli/driver/driver.html similarity index 86% rename from docs/nssh/driver/driver.html rename to docs/scrapli/driver/driver.html index 4e8df4d3..ea2eefeb 100644 --- a/docs/nssh/driver/driver.html +++ b/docs/scrapli/driver/driver.html @@ -4,8 +4,8 @@ -nssh.driver.driver API documentation - +scrapli.driver.driver API documentation + @@ -17,30 +17,32 @@
                          -

                          Module nssh.driver.driver

                          +

                          Module scrapli.driver.driver

                          -

                          nssh.driver.driver

                          +

                          scrapli.driver.driver

                          Expand source code -
                          """nssh.driver.driver"""
                          +
                          """scrapli.driver.driver"""
                           import logging
                           import os
                           import re
                           from types import TracebackType
                           from typing import Any, Callable, Dict, Optional, Tuple, Type, Union
                           
                          -from nssh.channel import CHANNEL_ARGS, Channel
                          -from nssh.helper import get_external_function, validate_external_function
                          -from nssh.transport import (
                          +from scrapli.channel import CHANNEL_ARGS, Channel
                          +from scrapli.helper import get_external_function, validate_external_function
                          +from scrapli.transport import (
                               MIKO_TRANSPORT_ARGS,
                               SSH2_TRANSPORT_ARGS,
                               SYSTEM_SSH_TRANSPORT_ARGS,
                          +    TELNET_TRANSPORT_ARGS,
                               MikoTransport,
                               SSH2Transport,
                               SystemSSHTransport,
                          +    TelnetTransport,
                               Transport,
                           )
                           
                          @@ -48,17 +50,19 @@ 

                          Module nssh.driver.driver

                          "system": SystemSSHTransport, "ssh2": SSH2Transport, "paramiko": MikoTransport, + "telnet": TelnetTransport, } TRANSPORT_ARGS: Dict[str, Tuple[str, ...]] = { "system": SYSTEM_SSH_TRANSPORT_ARGS, "ssh2": SSH2_TRANSPORT_ARGS, "paramiko": MIKO_TRANSPORT_ARGS, + "telnet": TELNET_TRANSPORT_ARGS, } -LOG = logging.getLogger("nssh_base") +LOG = logging.getLogger("scrapli_base") -class NSSH: +class Scrape: def __init__( self, host: str = "", @@ -76,13 +80,13 @@

                          Module nssh.driver.driver

                          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 @@

                          Module nssh.driver.driver

                          continue self.channel_args[arg] = getattr(self, arg) - def __enter__(self) -> "NSSH": + def __enter__(self) -> "Scrape": """ Enter method for context manager Args: - N/A # noqa + N/A Returns: self: instance of self Raises: - N/A # noqa + N/A """ self.open() @@ -200,47 +207,47 @@

                          Module nssh.driver.driver

                          traceback: traceback from exception being raised Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.close() def __str__(self) -> str: """ - Magic str method for NSSH + Magic str method for Scrape Args: - N/A # noqa + N/A Returns: - N/A # noqa + str: str representation of object Raises: - N/A # noqa + N/A """ - return f"NSSH Object for host {self.host}" + return f"Scrape Object for host {self.host}" def __repr__(self) -> str: """ - Magic repr method for NSSH + Magic repr method for Scrape Args: - N/A # noqa + N/A Returns: - repr: repr for class object + str: repr for class object Raises: - N/A # noqa + N/A """ class_dict = self.__dict__.copy() class_dict["auth_password"] = "********" - return f"NSSH {class_dict}" + return f"Scrape {class_dict}" def _setup_auth(self, auth_username: str, auth_password: str, auth_public_key: str) -> None: """ @@ -252,10 +259,10 @@

                          Module nssh.driver.driver

                          auth_public_key: public key to parse/set Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.auth_username = auth_username.strip() @@ -280,10 +287,10 @@

                          Module nssh.driver.driver

                          comms_ansi: ansi val to parse/set Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + TypeError: if invalid type args provided """ # try to compile prompt to raise TypeError before opening any connections @@ -309,10 +316,10 @@

                          Module nssh.driver.driver

                          session_disable_paging: disable paging to parse/set Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + TypeError: if invalid type args provided """ if session_pre_login_handler: @@ -420,13 +427,13 @@

                          Module nssh.driver.driver

                          Open Transport (socket/session) and establish channel Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.transport = self.transport_class(**self.transport_args) @@ -438,13 +445,13 @@

                          Module nssh.driver.driver

                          Close Transport (socket/session) Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.transport.close() @@ -454,13 +461,13 @@

                          Module nssh.driver.driver

                          Check if underlying socket/channel is alive Args: - N/A # noqa + N/A Returns: - alive: True/False if socket/channel is alive + bool: True/False if socket/channel is alive Raises: - N/A # noqa + N/A """ try: @@ -479,14 +486,14 @@

                          Module nssh.driver.driver

                          Classes

                          -
                          -class NSSH -(host='', port=22, auth_username='', auth_password='', auth_public_key='', auth_strict_key=True, timeout_socket=5, timeout_ssh=5000, timeout_ops=10, comms_prompt_pattern='^[a-z0-9.\\-@()/:]{1,32}[#>$]$', comms_return_char='\n', comms_ansi=False, session_pre_login_handler='', session_disable_paging='terminal length 0', ssh_config_file=True, driver='system') +
                          +class Scrape +(host='', port=22, auth_username='', auth_password='', auth_public_key='', auth_strict_key=True, timeout_socket=5, timeout_ssh=5000, timeout_ops=10, comms_prompt_pattern='^[a-z0-9.\\-@()/:]{1,32}[#>$]$', comms_return_char='\n', comms_ansi=False, session_pre_login_handler='', session_disable_paging='terminal length 0', ssh_config_file=True, transport='system')
                          -

                          NSSH 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 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).

                          Returns

                          N/A -# noqa
                          +# noqa: DAR202
                           

                          Raises

                          @@ -556,7 +564,7 @@

                          Raises

                          Expand source code -
                          class NSSH:
                          +
                          class Scrape:
                               def __init__(
                                   self,
                                   host: str = "",
                          @@ -574,13 +582,13 @@ 

                          Raises

                          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 @@

                          Raises

                          continue self.channel_args[arg] = getattr(self, arg) - def __enter__(self) -> "NSSH": + def __enter__(self) -> "Scrape": """ Enter method for context manager Args: - N/A # noqa + N/A Returns: self: instance of self Raises: - N/A # noqa + N/A """ self.open() @@ -698,47 +709,47 @@

                          Raises

                          traceback: traceback from exception being raised Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.close() def __str__(self) -> str: """ - Magic str method for NSSH + Magic str method for Scrape Args: - N/A # noqa + N/A Returns: - N/A # noqa + str: str representation of object Raises: - N/A # noqa + N/A """ - return f"NSSH Object for host {self.host}" + return f"Scrape Object for host {self.host}" def __repr__(self) -> str: """ - Magic repr method for NSSH + Magic repr method for Scrape Args: - N/A # noqa + N/A Returns: - repr: repr for class object + str: repr for class object Raises: - N/A # noqa + N/A """ class_dict = self.__dict__.copy() class_dict["auth_password"] = "********" - return f"NSSH {class_dict}" + return f"Scrape {class_dict}" def _setup_auth(self, auth_username: str, auth_password: str, auth_public_key: str) -> None: """ @@ -750,10 +761,10 @@

                          Raises

                          auth_public_key: public key to parse/set Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.auth_username = auth_username.strip() @@ -778,10 +789,10 @@

                          Raises

                          comms_ansi: ansi val to parse/set Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + TypeError: if invalid type args provided """ # try to compile prompt to raise TypeError before opening any connections @@ -807,10 +818,10 @@

                          Raises

                          session_disable_paging: disable paging to parse/set Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + TypeError: if invalid type args provided """ if session_pre_login_handler: @@ -918,13 +929,13 @@

                          Raises

                          Open Transport (socket/session) and establish channel Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.transport = self.transport_class(**self.transport_args) @@ -936,13 +947,13 @@

                          Raises

                          Close Transport (socket/session) Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.transport.close() @@ -952,13 +963,13 @@

                          Raises

                          Check if underlying socket/channel is alive Args: - N/A # noqa + N/A Returns: - alive: True/False if socket/channel is alive + bool: True/False if socket/channel is alive Raises: - N/A # noqa + N/A """ try: @@ -969,28 +980,26 @@

                          Raises

                          Subclasses

                          Methods

                          -
                          +
                          def close(self)

                          Close Transport (socket/session)

                          Args

                          -

                          N/A -# noqa

                          +

                          N/A

                          Returns

                          N/A -# noqa
                          +# noqa: DAR202
                           

                          Raises

                          -
                          N/A -# noqa
                          +
                          N/A
                           
                          @@ -1002,35 +1011,33 @@

                          Raises

                          Close Transport (socket/session) Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.transport.close()
      -
      +
      def isalive(self)

      Check if underlying socket/channel is alive

      Args

      -

      N/A -# noqa

      +

      N/A

      Returns

      -
      alive
      +
      bool
      True/False if socket/channel is alive

      Raises

      -
      N/A -# noqa
      +
      N/A
       
      @@ -1042,13 +1049,13 @@

      Raises

      Check if underlying socket/channel is alive Args: - N/A # noqa + N/A Returns: - alive: True/False if socket/channel is alive + bool: True/False if socket/channel is alive Raises: - N/A # noqa + N/A """ try: @@ -1058,24 +1065,22 @@

      Raises

      return alive
      -
      +
      def open(self)

      Open Transport (socket/session) and establish channel

      Args

      -

      N/A -# noqa

      +

      N/A

      Returns

      N/A -# noqa
      +# noqa: DAR202
       

      Raises

      -
      N/A -# noqa
      +
      N/A
       
      @@ -1087,13 +1092,13 @@

      Raises

      Open Transport (socket/session) and establish channel Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.transport = self.transport_class(**self.transport_args) @@ -1114,17 +1119,17 @@

      Index

      • Super-module

      • Classes

        diff --git a/docs/nssh/driver/index.html b/docs/scrapli/driver/index.html similarity index 72% rename from docs/nssh/driver/index.html rename to docs/scrapli/driver/index.html index 85de7ea5..b37118e3 100644 --- a/docs/nssh/driver/index.html +++ b/docs/scrapli/driver/index.html @@ -4,8 +4,8 @@ -nssh.driver API documentation - +scrapli.driver API documentation + @@ -17,39 +17,39 @@
        -

        Module nssh.driver

        +

        Module scrapli.driver

        -

        nssh.driver

        +

        scrapli.driver

        Expand source code -
        """nssh.driver"""
        -from nssh.driver.driver import NSSH
        -from nssh.driver.network_driver import NetworkDriver
        +
        """scrapli.driver"""
        +from scrapli.driver.driver import Scrape
        +from scrapli.driver.network_driver import NetworkDriver
         
        -__all__ = ("NSSH", "NetworkDriver")
        +__all__ = ("Scrape", "NetworkDriver")

        Sub-modules

        -
        nssh.driver.community
        +
        scrapli.driver.community
        -

        nssh.driver.community

        +

        scrapli.driver.community

        -
        nssh.driver.core
        +
        scrapli.driver.core
        -

        nssh.driver.core

        +

        scrapli.driver.core

        -
        nssh.driver.driver
        +
        scrapli.driver.driver
        -

        nssh.driver.driver

        +

        scrapli.driver.driver

        -
        nssh.driver.network_driver
        +
        scrapli.driver.network_driver
        -

        nssh.driver.network_driver

        +

        scrapli.driver.network_driver

        @@ -60,1291 +60,1197 @@

        Sub-modules

        Classes

        -
        -class NSSH -(host='', port=22, auth_username='', auth_password='', auth_public_key='', auth_strict_key=True, timeout_socket=5, timeout_ssh=5000, timeout_ops=10, comms_prompt_pattern='^[a-z0-9.\\-@()/:]{1,32}[#>$]$', comms_return_char='\n', comms_ansi=False, session_pre_login_handler='', session_disable_paging='terminal length 0', ssh_config_file=True, driver='system') +
        +class NetworkDriver +(auth_secondary='', **kwargs)
        -

        NSSH 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 -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
        -
        nssh.driver.driver
        -
        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)
        +
        **kwargs
        +
        keyword args to pass to inherited class(es)

        Returns

        N/A -# noqa
        +# noqa: DAR202
         

        Raises

        -
        TypeError
        -
        if auth_strict_key is not a bool
        -
        ValueError
        -
        if driver value is invalid
        +
        N/A
        +
         
        Expand source code -
        class NSSH:
        -    def __init__(
        -        self,
        -        host: str = "",
        -        port: int = 22,
        -        auth_username: str = "",
        -        auth_password: str = "",
        -        auth_public_key: str = "",
        -        auth_strict_key: bool = True,
        -        timeout_socket: int = 5,
        -        timeout_ssh: int = 5000,
        -        timeout_ops: int = 10,
        -        comms_prompt_pattern: str = r"^[a-z0-9.\-@()/:]{1,32}[#>$]$",
        -        comms_return_char: str = "\n",
        -        comms_ansi: bool = False,
        -        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",
        -    ):
        +
        class NetworkDriver(Scrape):
        +    def __init__(self, auth_secondary: str = "", **kwargs: Any):
                 """
        -        NSSH 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
        -        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
        -            driver: 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)
        +            **kwargs: keyword args to pass to inherited class(es)
         
                 Returns:
        -            N/A  # noqa
        +            N/A  # noqa: DAR202
         
                 Raises:
        -            TypeError: if auth_strict_key is not a bool
        -            ValueError: if driver value is invalid
        +            N/A
         
                 """
        -        self.host = host.strip()
        -        if not isinstance(port, int):
        -            raise TypeError(f"port should be int, got {type(port)}")
        -        self.port = port
        -
        -        self.auth_username: str = ""
        -        self.auth_password: str = ""
        -        if not isinstance(auth_strict_key, bool):
        -            raise TypeError(f"auth_strict_key should be bool, got {type(auth_strict_key)}")
        -        self.auth_strict_key = auth_strict_key
        -        self._setup_auth(auth_username, auth_password, auth_public_key)
        -
        -        self.timeout_socket = int(timeout_socket)
        -        self.timeout_ssh = int(timeout_ssh)
        -        self.timeout_ops = int(timeout_ops)
        +        super().__init__(**kwargs)
        +        self.auth_secondary = auth_secondary
        +        self.privs = PRIVS
        +        self.default_desired_priv: Optional[str] = None
        +        self.textfsm_platform: str = ""
        +        self.exit_command: str = "exit"
         
        -        self.comms_prompt_pattern: str = ""
        -        self.comms_return_char: str = ""
        -        self.comms_ansi: bool = False
        -        self._setup_comms(comms_prompt_pattern, comms_return_char, comms_ansi)
        +    def _determine_current_priv(self, current_prompt: str) -> PrivilegeLevel:
        +        """
        +        Determine current privilege level from prompt string
         
        -        self.session_pre_login_handler: Optional[Callable[..., Any]] = None
        -        self.session_disable_paging: Union[str, Callable[..., Any]] = ""
        -        self._setup_session(session_pre_login_handler, session_disable_paging)
        +        Args:
        +            current_prompt: string of current prompt
         
        -        if not isinstance(ssh_config_file, (str, bool)):
        -            raise TypeError(f"ssh_config_file should be str or bool, got {type(ssh_config_file)}")
        -        self.ssh_config_file = ssh_config_file
        +        Returns:
        +            PrivilegeLevel: NamedTuple of current privilege level
         
        -        if driver not in ("ssh2", "paramiko", "system"):
        -            raise ValueError(f"transport should be one of ssh2|paramiko|system, got {driver}")
        -        self.transport: Transport
        -        self.transport_class, self.transport_args = self._transport_factory(driver)
        +        Raises:
        +            UnknownPrivLevel: if privilege level cannot be determined
         
        -        self.channel: Channel
        -        self.channel_args = {}
        -        for arg in CHANNEL_ARGS:
        -            if arg == "transport":
        -                continue
        -            self.channel_args[arg] = getattr(self, arg)
        +        """
        +        for priv_level in self.privs.values():
        +            prompt_pattern = get_prompt_pattern("", priv_level.pattern)
        +            if re.search(prompt_pattern, current_prompt.encode()):
        +                return priv_level
        +        raise UnknownPrivLevel
         
        -    def __enter__(self) -> "NSSH":
        +    def _escalate(self) -> None:
                 """
        -        Enter method for context manager
        +        Escalate to the next privilege level up
         
                 Args:
        -            N/A  # noqa
        +            N/A
         
                 Returns:
        -            self: instance of self
        +            N/A  # noqa: DAR202
         
                 Raises:
        -            N/A  # noqa
        +            UnknownPrivLevel: if priv level cant be attained
        +            TypeError: if invalid next prompt value
         
                 """
        -        self.open()
        -        return self
        +        current_priv = self._determine_current_priv(self.channel.get_prompt())
        +        if current_priv.escalate:
        +            next_priv = self.privs.get(current_priv.escalate_priv, None)
        +            if next_priv is None:
        +                raise UnknownPrivLevel(
        +                    f"Could not get next priv level, current priv is {current_priv.name}"
        +                )
        +            next_prompt = next_priv.pattern
        +            if current_priv.escalate_auth:
        +                escalate_cmd: str = current_priv.escalate
        +                escalate_prompt: str = current_priv.escalate_prompt
        +                escalate_auth = self.auth_secondary
        +                if not isinstance(next_prompt, str):
        +                    raise TypeError(
        +                        f"got {type(next_prompt)} for {current_priv.name} escalate priv, "
        +                        "expected str"
        +                    )
        +                self.channel.send_inputs_interact(
        +                    (escalate_cmd, escalate_prompt, escalate_auth, next_prompt),
        +                    hidden_response=True,
        +                )
        +                self.channel.comms_prompt_pattern = next_priv.pattern
        +            else:
        +                self.channel.comms_prompt_pattern = next_priv.pattern
        +                self.channel.send_inputs(current_priv.escalate)
         
        -    def __exit__(
        -        self,
        -        exception_type: Optional[Type[BaseException]],
        -        exception_value: Optional[BaseException],
        -        traceback: Optional[TracebackType],
        -    ) -> None:
        +    def _deescalate(self) -> None:
                 """
        -        Exit method to cleanup for context manager
        +        Deescalate to the next privilege level down
         
                 Args:
        -            exception_type: exception type being raised
        -            exception_value: message from exception being raised
        -            traceback: traceback from exception being raised
        +            N/A
         
                 Returns:
        -            N/A  # noqa
        +            N/A  # noqa: DAR202
         
                 Raises:
        -            N/A  # noqa
        +            UnknownPrivLevel: if no default priv level set to deescalate to
         
                 """
        -        self.close()
        +        current_priv = self._determine_current_priv(self.channel.get_prompt())
        +        if current_priv.deescalate:
        +            next_priv = self.privs.get(current_priv.deescalate_priv, None)
        +            if not next_priv:
        +                raise UnknownPrivLevel(
        +                    "NetworkDriver has no default priv levels, set them or use a network driver"
        +                )
        +            self.channel.comms_prompt_pattern = next_priv.pattern
        +            self.channel.send_inputs(current_priv.deescalate)
         
        -    def __str__(self) -> str:
        +    def acquire_priv(self, desired_priv: str) -> None:
                 """
        -        Magic str method for NSSH
        +        Acquire desired priv level
         
                 Args:
        -            N/A  # noqa
        +            desired_priv: string name of desired privilege level
        +                (see scrapli.driver.<driver_category.device_type>.driver for levels)
         
                 Returns:
        -            N/A  # noqa
        +            N/A  # noqa: DAR202
         
                 Raises:
        -            N/A  # noqa
        +            CouldNotAcquirePrivLevel: if requested priv level not attained
         
                 """
        -        return f"NSSH Object for host {self.host}"
        +        priv_attempt_counter = 0
        +        while True:
        +            current_priv = self._determine_current_priv(self.channel.get_prompt())
        +            if current_priv == self.privs[desired_priv]:
        +                return
        +            if priv_attempt_counter > len(self.privs):
        +                raise CouldNotAcquirePrivLevel(
        +                    f"Could not get to '{desired_priv}' privilege level."
        +                )
        +            if current_priv.level > self.privs[desired_priv].level:
        +                self._deescalate()
        +            else:
        +                self._escalate()
        +            priv_attempt_counter += 1
         
        -    def __repr__(self) -> str:
        +    def send_commands(
        +        self, commands: Union[str, List[str]], strip_prompt: bool = True
        +    ) -> List[Response]:
                 """
        -        Magic repr method for NSSH
        +        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:
        -            repr: repr for class object
        +            responses: list of Scrape Response objects
         
                 Raises:
        -            N/A  # noqa
        +            N/A
         
                 """
        -        class_dict = self.__dict__.copy()
        -        class_dict["auth_password"] = "********"
        -        return f"NSSH {class_dict}"
        +        self.acquire_priv(str(self.default_desired_priv))
        +        responses = self.channel.send_inputs(commands, strip_prompt)
         
        -    def _setup_auth(self, auth_username: str, auth_password: str, auth_public_key: str) -> None:
        -        """
        -        Parse and setup auth attributes
        -
        -        Args:
        -            auth_username: username to parse/set
        -            auth_password: password to parse/set
        -            auth_public_key: public key to parse/set
        -
        -        Returns:
        -            N/A  # noqa
        -
        -        Raises:
        -            N/A  # noqa
        -
        -        """
        -        self.auth_username = auth_username.strip()
        -        if auth_public_key:
        -            self.auth_public_key = os.path.expanduser(auth_public_key.strip().encode())
        -        else:
        -            self.auth_public_key = auth_public_key.encode()
        -        if auth_password:
        -            self.auth_password = auth_password.strip()
        -        else:
        -            self.auth_password = auth_password
        -
        -    def _setup_comms(
        -        self, comms_prompt_pattern: str, comms_return_char: str, comms_ansi: bool
        -    ) -> None:
        -        """
        -        Parse and setup auth attributes
        -
        -        Args:
        -            comms_prompt_pattern: prompt pattern to parse/set
        -            comms_return_char: return char to parse/set
        -            comms_ansi: ansi val to parse/set
        -
        -        Returns:
        -            N/A  # noqa
        -
        -        Raises:
        -            N/A  # noqa
        +        # 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
         
        -        """
        -        # try to compile prompt to raise TypeError before opening any connections
        -        re.compile(comms_prompt_pattern, flags=re.M | re.I)
        -        self.comms_prompt_pattern = comms_prompt_pattern
        -        if not isinstance(comms_return_char, str):
        -            raise TypeError(f"comms_return_char should be str, got {type(comms_return_char)}")
        -        self.comms_return_char = comms_return_char
        -        if not isinstance(comms_ansi, bool):
        -            raise TypeError(f"comms_ansi should be bool, got {type(comms_ansi)}")
        -        self.comms_ansi = comms_ansi
        +        return responses
         
        -    def _setup_session(
        -        self,
        -        session_pre_login_handler: Union[str, Callable[..., Any]],
        -        session_disable_paging: Union[str, Callable[..., Any]],
        -    ) -> None:
        +    def send_interactive(
        +        self, inputs: Union[List[str], Tuple[str, str, str, str]], hidden_response: bool = False,
        +    ) -> List[Response]:
                 """
        -        Parse and setup session attributes
        -
        -        Args:
        -            session_pre_login_handler: pre login handler to parse/set
        -            session_disable_paging: disable paging to parse/set
        -
        -        Returns:
        -            N/A  # noqa
        -
        -        Raises:
        -            N/A  # noqa
        +        Send inputs in an interactive fashion; used to handle prompts
         
        -        """
        -        if session_pre_login_handler:
        -            self.session_pre_login_handler = self._set_session_pre_login_handler(
        -                session_pre_login_handler
        -            )
        -        else:
        -            self.session_pre_login_handler = None
        -        if callable(session_disable_paging):
        -            self.session_disable_paging = session_disable_paging
        -        if not isinstance(session_disable_paging, str) and not callable(session_disable_paging):
        -            raise TypeError(
        -                "session_disable_paging should be str or callable, got "
        -                f"{type(session_disable_paging)}"
        -            )
        -        if session_disable_paging != "terminal length 0":
        -            try:
        -                self.session_disable_paging = self._set_session_disable_paging(
        -                    session_disable_paging
        -                )
        -            except TypeError:
        -                self.session_disable_paging = session_disable_paging
        -        else:
        -            self.session_disable_paging = "terminal length 0"
        +        accepts inputs and looks for expected prompt;
        +        sends the appropriate response, then waits for the "finale"
        +        returns the results of the interaction
         
        -    @staticmethod
        -    def _set_session_pre_login_handler(
        -        session_pre_login_handler: Union[str, Callable[..., Any]]
        -    ) -> Optional[Callable[..., Any]]:
        -        """
        -        Return session_pre_login_handler argument
        +        could be "chained" together to respond to more than a "single" staged prompt
         
                 Args:
        -            session_pre_login_handler: callable function, or string representing a path to
        -                a callable
        +            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:
        -            session_pre_login_handler: callable or default empty string value
        +            responses: List of Scrape Response objects
         
                 Raises:
        -            TypeError: if provided string does not result in a callable
        +            N/A
         
                 """
        -        if callable(session_pre_login_handler):
        -            return session_pre_login_handler
        -        if not validate_external_function(session_pre_login_handler):
        -            LOG.critical(f"Invalid comms_pre_login_handler: {session_pre_login_handler}")
        -            raise TypeError(
        -                f"{session_pre_login_handler} is an invalid session_pre_login_handler function "
        -                "or path to a function."
        -            )
        -        return get_external_function(session_pre_login_handler)
        +        self.acquire_priv(str(self.default_desired_priv))
        +        responses = self.channel.send_inputs_interact(inputs, hidden_response)
        +        return responses
         
        -    @staticmethod
        -    def _set_session_disable_paging(
        -        session_disable_paging: Union[Callable[..., Any], str]
        -    ) -> Union[Callable[..., Any], str]:
        +    def send_configs(
        +        self, configs: Union[str, List[str]], strip_prompt: bool = True
        +    ) -> List[Response]:
                 """
        -        Return session_disable_paging argument
        +        Send configuration(s)
         
                 Args:
        -            session_disable_paging: callable function, string representing a path to
        -                a callable, or a string to send to device to disable paging
        +            configs: string or list of strings to send to device in config mode
        +            strip_prompt: True/False strip prompt from returned output
         
                 Returns:
        -            session_disable_paging: callable or string to use to disable paging
        +            responses: List of Scrape Response objects
         
                 Raises:
        -            TypeError: if provided string does not result in a callable
        +            N/A
         
                 """
        -        if callable(session_disable_paging):
        -            return session_disable_paging
        -        if not validate_external_function(session_disable_paging):
        -            raise TypeError(
        -                f"{session_disable_paging} is an invalid session_disable_paging function or path "
        -                "to a function. Assuming this is string to send to disable paging."
        -            )
        -        return get_external_function(session_disable_paging)
        +        self.acquire_priv("configuration")
        +        responses = self.channel.send_inputs(configs, strip_prompt)
        +        self.acquire_priv(str(self.default_desired_priv))
        +        return responses
         
        -    def _transport_factory(self, transport: str) -> Tuple[Callable[..., Any], Dict[str, Any]]:
        +    def get_prompt(self) -> str:
                 """
        -        Private factory method to produce transport class
        +        Convenience method to get device prompt from Channel
         
                 Args:
        -            transport: string name of transport class to use
        +            N/A
         
                 Returns:
        -            Transport: initialized Transport class
        +            str: prompt received from channel.get_prompt
         
                 Raises:
        -            N/A  # noqa
        +            N/A
         
                 """
        -        transport_class = TRANSPORT_CLASS[transport]
        -        required_transport_args = TRANSPORT_ARGS[transport]
        -
        -        transport_args = {}
        -        for arg in required_transport_args:
        -            transport_args[arg] = getattr(self, arg)
        -        return transport_class, transport_args
        +        prompt: str = self.channel.get_prompt()
        +        return prompt
         
             def open(self) -> None:
        -        """
        -        Open Transport (socket/session) and establish channel
        -
        -        Args:
        -            N/A  # noqa
        -
        -        Returns:
        -            N/A  # noqa
        -
        -        Raises:
        -            N/A  # noqa
        -
        -        """
        -        self.transport = self.transport_class(**self.transport_args)
        -        self.transport.open()
        -        self.channel = Channel(self.transport, **self.channel_args)
        +        super().open()
        +        if self.session_pre_login_handler:
        +            self.session_pre_login_handler(self)
        +        # send disable paging if needed
        +        if self.session_disable_paging:
        +            if callable(self.session_disable_paging):
        +                self.session_disable_paging(self)
        +            else:
        +                self.channel.send_inputs(self.session_disable_paging)
         
             def close(self) -> None:
        -        """
        -        Close Transport (socket/session)
        -
        -        Args:
        -            N/A  # noqa
        -
        -        Returns:
        -            N/A  # noqa
        -
        -        Raises:
        -            N/A  # noqa
        -
        -        """
        -        self.transport.close()
        -
        -    def isalive(self) -> bool:
        -        """
        -        Check if underlying socket/channel is alive
        -
        -        Args:
        -            N/A  # noqa
        -
        -        Returns:
        -            alive: True/False if socket/channel is alive
        -
        -        Raises:
        -            N/A  # noqa
        -
        -        """
        -        try:
        -            alive = self.transport.isalive()
        -        except AttributeError:
        -            alive = False
        -        return alive
        + self.transport.write(f"{self.exit_command}{self.comms_return_char}") + super().close()
        +

        Ancestors

        +

        Subclasses

        Methods

        -
        -def close(self) +
        +def acquire_priv(self, desired_priv)
        -

        Close Transport (socket/session)

        +

        Acquire desired priv level

        Args

        -

        N/A -# noqa

        +
        +
        desired_priv
        +
        string name of desired privilege level +(see scrapli.driver..driver for levels)
        +

        Returns

        N/A -# noqa
        +# noqa: DAR202
         

        Raises

        -
        N/A -# noqa
        -
         
        +
        CouldNotAcquirePrivLevel
        +
        if requested priv level not attained
        Expand source code -
        def close(self) -> None:
        +
        def acquire_priv(self, desired_priv: str) -> None:
             """
        -    Close Transport (socket/session)
        +    Acquire desired priv level
         
             Args:
        -        N/A  # noqa
        +        desired_priv: string name of desired privilege level
        +            (see scrapli.driver.<driver_category.device_type>.driver for levels)
         
             Returns:
        -        N/A  # noqa
        +        N/A  # noqa: DAR202
         
             Raises:
        -        N/A  # noqa
        +        CouldNotAcquirePrivLevel: if requested priv level not attained
         
             """
        -    self.transport.close()
        + priv_attempt_counter = 0 + while True: + current_priv = self._determine_current_priv(self.channel.get_prompt()) + if current_priv == self.privs[desired_priv]: + return + if priv_attempt_counter > len(self.privs): + raise CouldNotAcquirePrivLevel( + f"Could not get to '{desired_priv}' privilege level." + ) + if current_priv.level > self.privs[desired_priv].level: + self._deescalate() + else: + self._escalate() + priv_attempt_counter += 1
        -
        -def isalive(self) +
        +def get_prompt(self)
        -

        Check if underlying socket/channel is alive

        +

        Convenience method to get device prompt from Channel

        Args

        -

        N/A -# noqa

        +

        N/A

        Returns

        -
        alive
        -
        True/False if socket/channel is alive
        +
        str
        +
        prompt received from channel.get_prompt

        Raises

        -
        N/A -# noqa
        +
        N/A
         
        Expand source code -
        def isalive(self) -> bool:
        +
        def get_prompt(self) -> str:
             """
        -    Check if underlying socket/channel is alive
        +    Convenience method to get device prompt from Channel
         
             Args:
        -        N/A  # noqa
        +        N/A
         
             Returns:
        -        alive: True/False if socket/channel is alive
        +        str: prompt received from channel.get_prompt
         
             Raises:
        -        N/A  # noqa
        +        N/A
         
             """
        -    try:
        -        alive = self.transport.isalive()
        -    except AttributeError:
        -        alive = False
        -    return alive
        + prompt: str = self.channel.get_prompt() + return prompt
        -
        -def open(self) +
        +def send_commands(self, commands, strip_prompt=True)
        -

        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
        +
        +
        +
        +def send_configs(self, configs, strip_prompt=True) +
        +
        +

        Send configuration(s)

        +

        Args

        +
        +
        configs
        +
        string or list of strings to send to device in config mode
        +
        strip_prompt
        +
        True/False strip prompt from returned output
        +
        +

        Returns

        +
        +
        responses
        +
        List of Scrape Response objects

        Raises

        -
        N/A -# noqa
        +
        N/A
         
        Expand source code -
        def open(self) -> None:
        +
        def send_configs(
        +    self, configs: Union[str, List[str]], strip_prompt: bool = True
        +) -> List[Response]:
             """
        -    Open Transport (socket/session) and establish channel
        +    Send configuration(s)
         
             Args:
        -        N/A  # noqa
        +        configs: string or list of strings to send to device in config mode
        +        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.transport = self.transport_class(**self.transport_args)
        -    self.transport.open()
        -    self.channel = Channel(self.transport, **self.channel_args)
        + self.acquire_priv("configuration") + responses = self.channel.send_inputs(configs, strip_prompt) + self.acquire_priv(str(self.default_desired_priv)) + return responses
        +
        +def send_interactive(self, inputs, hidden_response=False) +
        +
        +

        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
        +
        -
        -class NetworkDriver -(auth_secondary='', **kwargs) +
        +

        Inherited members

        + +
        +
        +class Scrape +(host='', port=22, auth_username='', auth_password='', auth_public_key='', auth_strict_key=True, timeout_socket=5, timeout_ssh=5000, timeout_ops=10, comms_prompt_pattern='^[a-z0-9.\\-@()/:]{1,32}[#>$]$', comms_return_char='\n', comms_ansi=False, session_pre_login_handler='', session_disable_paging='terminal length 0', ssh_config_file=True, transport='system')
        -

        BaseNetworkDriver Object

        +

        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

        -
        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).

        Returns

        N/A -# noqa
        +# noqa: DAR202
         

        Raises

        -
        N/A -# noqa
        -
         
        +
        TypeError
        +
        if auth_strict_key is not a bool
        +
        ValueError
        +
        if driver value is invalid
        Expand source code -
        class NetworkDriver(NSSH):
        -    def __init__(self, auth_secondary: str = "", **kwargs: Any):
        +
        class Scrape:
        +    def __init__(
        +        self,
        +        host: str = "",
        +        port: int = 22,
        +        auth_username: str = "",
        +        auth_password: str = "",
        +        auth_public_key: str = "",
        +        auth_strict_key: bool = True,
        +        timeout_socket: int = 5,
        +        timeout_ssh: int = 5000,
        +        timeout_ops: int = 10,
        +        comms_prompt_pattern: str = r"^[a-z0-9.\-@()/:]{1,32}[#>$]$",
        +        comms_return_char: str = "\n",
        +        comms_ansi: bool = False,
        +        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,
        +        transport: str = "system",
        +    ):
                 """
        -        BaseNetworkDriver Object
        +        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:
        -            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).
         
                 Returns:
        -            N/A  # noqa
        +            N/A  # noqa: DAR202
         
                 Raises:
        -            N/A  # noqa
        +            TypeError: if auth_strict_key is not a bool
        +            ValueError: if driver value is invalid
         
                 """
        -        super().__init__(**kwargs)
        -        self.auth_secondary = auth_secondary
        -        self.privs = PRIVS
        -        self.default_desired_priv: Optional[str] = None
        -        self.textfsm_platform: str = ""
        -        self.exit_command: str = "exit"
        +        self.host = host.strip()
        +        if not isinstance(port, int):
        +            raise TypeError(f"port should be int, got {type(port)}")
        +        self.port = port
         
        -    def _determine_current_priv(self, current_prompt: str) -> PrivilegeLevel:
        +        self.auth_username: str = ""
        +        self.auth_password: str = ""
        +        if not isinstance(auth_strict_key, bool):
        +            raise TypeError(f"auth_strict_key should be bool, got {type(auth_strict_key)}")
        +        self.auth_strict_key = auth_strict_key
        +        self._setup_auth(auth_username, auth_password, auth_public_key)
        +
        +        self.timeout_socket = int(timeout_socket)
        +        self.timeout_ssh = int(timeout_ssh)
        +        self.timeout_ops = int(timeout_ops)
        +
        +        self.comms_prompt_pattern: str = ""
        +        self.comms_return_char: str = ""
        +        self.comms_ansi: bool = False
        +        self._setup_comms(comms_prompt_pattern, comms_return_char, comms_ansi)
        +
        +        self.session_pre_login_handler: Optional[Callable[..., Any]] = None
        +        self.session_disable_paging: Union[str, Callable[..., Any]] = ""
        +        self._setup_session(session_pre_login_handler, session_disable_paging)
        +
        +        if not isinstance(ssh_config_file, (str, bool)):
        +            raise TypeError(f"ssh_config_file should be str or bool, got {type(ssh_config_file)}")
        +        self.ssh_config_file = ssh_config_file
        +
        +        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(transport)
        +
        +        self.channel: Channel
        +        self.channel_args = {}
        +        for arg in CHANNEL_ARGS:
        +            if arg == "transport":
        +                continue
        +            self.channel_args[arg] = getattr(self, arg)
        +
        +    def __enter__(self) -> "Scrape":
                 """
        -        Determine current privilege level from prompt string
        +        Enter method for context manager
         
                 Args:
        -            current_prompt: string of current prompt
        +            N/A
         
                 Returns:
        -            PrivilegeLevel: NamedTuple of current privilege level
        +            self: instance of self
         
                 Raises:
        -            UnknownPrivLevel: if privilege level cannot be determined
        +            N/A
         
                 """
        -        for priv_level in self.privs.values():
        -            prompt_pattern = get_prompt_pattern("", priv_level.pattern)
        -            if re.search(prompt_pattern, current_prompt.encode()):
        -                return priv_level
        -        raise UnknownPrivLevel
        +        self.open()
        +        return self
         
        -    def _escalate(self) -> None:
        +    def __exit__(
        +        self,
        +        exception_type: Optional[Type[BaseException]],
        +        exception_value: Optional[BaseException],
        +        traceback: Optional[TracebackType],
        +    ) -> None:
                 """
        -        Escalate to the next privilege level up
        +        Exit method to cleanup for context manager
         
                 Args:
        -            N/A  # noqa
        +            exception_type: exception type being raised
        +            exception_value: message from exception being raised
        +            traceback: traceback from exception being raised
         
                 Returns:
        -            N/A  # noqa
        +            N/A  # noqa: DAR202
         
                 Raises:
        -            N/A  # noqa
        +            N/A
         
                 """
        -        current_priv = self._determine_current_priv(self.channel.get_prompt())
        -        if current_priv.escalate:
        -            next_priv = self.privs.get(current_priv.escalate_priv, None)
        -            if next_priv is None:
        -                raise UnknownPrivLevel(
        -                    f"Could not get next priv level, current priv is {current_priv.name}"
        -                )
        -            next_prompt = next_priv.pattern
        -            if current_priv.escalate_auth:
        -                escalate_cmd: str = current_priv.escalate
        -                escalate_prompt: str = current_priv.escalate_prompt
        -                escalate_auth = self.auth_secondary
        -                if not isinstance(next_prompt, str):
        -                    raise TypeError(
        -                        f"got {type(next_prompt)} for {current_priv.name} escalate priv, "
        -                        "expected str"
        -                    )
        -                self.channel.send_inputs_interact(
        -                    (escalate_cmd, escalate_prompt, escalate_auth, next_prompt),
        -                    hidden_response=True,
        -                )
        -                self.channel.comms_prompt_pattern = next_priv.pattern
        -            else:
        -                self.channel.comms_prompt_pattern = next_priv.pattern
        -                self.channel.send_inputs(current_priv.escalate)
        +        self.close()
         
        -    def _deescalate(self) -> None:
        +    def __str__(self) -> str:
                 """
        -        Deescalate to the next privilege level down
        +        Magic str method for Scrape
         
                 Args:
        -            N/A  # noqa
        +            N/A
         
                 Returns:
        -            N/A  # noqa
        +            str: str representation of object
         
                 Raises:
        -            N/A  # noqa
        +            N/A
         
                 """
        -        current_priv = self._determine_current_priv(self.channel.get_prompt())
        -        if current_priv.deescalate:
        -            next_priv = self.privs.get(current_priv.deescalate_priv, None)
        -            if not next_priv:
        -                raise UnknownPrivLevel(
        -                    "NetworkDriver has no default priv levels, set them or use a network driver"
        -                )
        -            self.channel.comms_prompt_pattern = next_priv.pattern
        -            self.channel.send_inputs(current_priv.deescalate)
        +        return f"Scrape Object for host {self.host}"
         
        -    def acquire_priv(self, desired_priv: str) -> None:
        +    def __repr__(self) -> str:
                 """
        -        Acquire desired priv level
        +        Magic repr method for Scrape
         
                 Args:
        -            desired_priv: string name of desired privilege level
        -                (see nssh.driver.<driver_category.device_type>.driver for levels)
        +            N/A
         
                 Returns:
        -            N/A  # noqa
        +            str: repr for class object
         
                 Raises:
        -            CouldNotAcquirePrivLevel: if requested priv level not attained
        +            N/A
         
                 """
        -        priv_attempt_counter = 0
        -        while True:
        -            current_priv = self._determine_current_priv(self.channel.get_prompt())
        -            if current_priv == self.privs[desired_priv]:
        -                return
        -            if priv_attempt_counter > len(self.privs):
        -                raise CouldNotAcquirePrivLevel(
        -                    f"Could not get to '{desired_priv}' privilege level."
        +        class_dict = self.__dict__.copy()
        +        class_dict["auth_password"] = "********"
        +        return f"Scrape {class_dict}"
        +
        +    def _setup_auth(self, auth_username: str, auth_password: str, auth_public_key: str) -> None:
        +        """
        +        Parse and setup auth attributes
        +
        +        Args:
        +            auth_username: username to parse/set
        +            auth_password: password to parse/set
        +            auth_public_key: public key to parse/set
        +
        +        Returns:
        +            N/A  # noqa: DAR202
        +
        +        Raises:
        +            N/A
        +
        +        """
        +        self.auth_username = auth_username.strip()
        +        if auth_public_key:
        +            self.auth_public_key = os.path.expanduser(auth_public_key.strip().encode())
        +        else:
        +            self.auth_public_key = auth_public_key.encode()
        +        if auth_password:
        +            self.auth_password = auth_password.strip()
        +        else:
        +            self.auth_password = auth_password
        +
        +    def _setup_comms(
        +        self, comms_prompt_pattern: str, comms_return_char: str, comms_ansi: bool
        +    ) -> None:
        +        """
        +        Parse and setup auth attributes
        +
        +        Args:
        +            comms_prompt_pattern: prompt pattern to parse/set
        +            comms_return_char: return char to parse/set
        +            comms_ansi: ansi val to parse/set
        +
        +        Returns:
        +            N/A  # noqa: DAR202
        +
        +        Raises:
        +            TypeError: if invalid type args provided
        +
        +        """
        +        # try to compile prompt to raise TypeError before opening any connections
        +        re.compile(comms_prompt_pattern, flags=re.M | re.I)
        +        self.comms_prompt_pattern = comms_prompt_pattern
        +        if not isinstance(comms_return_char, str):
        +            raise TypeError(f"comms_return_char should be str, got {type(comms_return_char)}")
        +        self.comms_return_char = comms_return_char
        +        if not isinstance(comms_ansi, bool):
        +            raise TypeError(f"comms_ansi should be bool, got {type(comms_ansi)}")
        +        self.comms_ansi = comms_ansi
        +
        +    def _setup_session(
        +        self,
        +        session_pre_login_handler: Union[str, Callable[..., Any]],
        +        session_disable_paging: Union[str, Callable[..., Any]],
        +    ) -> None:
        +        """
        +        Parse and setup session attributes
        +
        +        Args:
        +            session_pre_login_handler: pre login handler to parse/set
        +            session_disable_paging: disable paging to parse/set
        +
        +        Returns:
        +            N/A  # noqa: DAR202
        +
        +        Raises:
        +            TypeError: if invalid type args provided
        +
        +        """
        +        if session_pre_login_handler:
        +            self.session_pre_login_handler = self._set_session_pre_login_handler(
        +                session_pre_login_handler
        +            )
        +        else:
        +            self.session_pre_login_handler = None
        +        if callable(session_disable_paging):
        +            self.session_disable_paging = session_disable_paging
        +        if not isinstance(session_disable_paging, str) and not callable(session_disable_paging):
        +            raise TypeError(
        +                "session_disable_paging should be str or callable, got "
        +                f"{type(session_disable_paging)}"
        +            )
        +        if session_disable_paging != "terminal length 0":
        +            try:
        +                self.session_disable_paging = self._set_session_disable_paging(
        +                    session_disable_paging
                         )
        -            if current_priv.level > self.privs[desired_priv].level:
        -                self._deescalate()
        -            else:
        -                self._escalate()
        -            priv_attempt_counter += 1
        +            except TypeError:
        +                self.session_disable_paging = session_disable_paging
        +        else:
        +            self.session_disable_paging = "terminal length 0"
         
        -    def send_commands(
        -        self, commands: Union[str, List[str]], strip_prompt: bool = True, textfsm: bool = False,
        -    ) -> List[Result]:
        +    @staticmethod
        +    def _set_session_pre_login_handler(
        +        session_pre_login_handler: Union[str, Callable[..., Any]]
        +    ) -> Optional[Callable[..., Any]]:
                 """
        -        Send command(s)
        +        Return session_pre_login_handler argument
         
                 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
        +            session_pre_login_handler: callable function, or string representing a path to
        +                a callable
         
                 Returns:
        -            results: list of SSH2NetResult objects
        +            session_pre_login_handler: callable or default empty string value
         
                 Raises:
        -            N/A  # noqa
        +            TypeError: if provided string does not result in a callable
         
                 """
        -        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
        +        if callable(session_pre_login_handler):
        +            return session_pre_login_handler
        +        if not validate_external_function(session_pre_login_handler):
        +            LOG.critical(f"Invalid comms_pre_login_handler: {session_pre_login_handler}")
        +            raise TypeError(
        +                f"{session_pre_login_handler} is an invalid session_pre_login_handler function "
        +                "or path to a function."
                     )
        -        return results
        +        return get_external_function(session_pre_login_handler)
         
        -    def send_interactive(
        -        self, inputs: Union[List[str], Tuple[str, str, str, str]], hidden_response: bool = False,
        -    ) -> List[Result]:
        +    @staticmethod
        +    def _set_session_disable_paging(
        +        session_disable_paging: Union[Callable[..., Any], str]
        +    ) -> Union[Callable[..., Any], str]:
                 """
        -        Send inputs in an interactive fashion; used to handle prompts
        +        Return session_disable_paging argument
         
        -        accepts inputs and looks for expected prompt;
        -        sends the appropriate response, then waits for the "finale"
        -        returns the results of the interaction
        +        Args:
        +            session_disable_paging: callable function, string representing a path to
        +                a callable, or a string to send to device to disable paging
         
        -        could be "chained" together to respond to more than a "single" staged prompt
        +        Returns:
        +            session_disable_paging: callable or string to use to disable paging
        +
        +        Raises:
        +            TypeError: if provided string does not result in a callable
        +
        +        """
        +        if callable(session_disable_paging):
        +            return session_disable_paging
        +        if not validate_external_function(session_disable_paging):
        +            raise TypeError(
        +                f"{session_disable_paging} is an invalid session_disable_paging function or path "
        +                "to a function. Assuming this is string to send to disable paging."
        +            )
        +        return get_external_function(session_disable_paging)
        +
        +    def _transport_factory(self, transport: str) -> Tuple[Callable[..., Any], Dict[str, Any]]:
        +        """
        +        Private factory method to produce transport class
         
                 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)
        +            transport: string name of transport class to use
         
                 Returns:
        -            N/A  # noqa
        +            Transport: initialized Transport class
         
                 Raises:
                     N/A  # noqa
         
                 """
        -        self.acquire_priv(str(self.default_desired_priv))
        -        results = self.channel.send_inputs_interact(inputs, hidden_response)
        -        return results
        +        transport_class = TRANSPORT_CLASS[transport]
        +        required_transport_args = TRANSPORT_ARGS[transport]
         
        -    def send_configs(
        -        self, configs: Union[str, List[str]], strip_prompt: bool = True
        -    ) -> List[Result]:
        +        transport_args = {}
        +        for arg in required_transport_args:
        +            transport_args[arg] = getattr(self, arg)
        +        return transport_class, transport_args
        +
        +    def open(self) -> None:
                 """
        -        Send configuration(s)
        +        Open Transport (socket/session) and establish channel
         
                 Args:
        -            configs: string or list of strings to send to device in config mode
        -            strip_prompt: True/False strip prompt from returned output
        +            N/A
         
                 Returns:
        -            N/A  # noqa
        +            N/A  # noqa: DAR202
         
                 Raises:
        -            N/A  # noqa
        +            N/A
         
                 """
        -        self.acquire_priv("configuration")
        -        result = self.channel.send_inputs(configs, strip_prompt)
        -        self.acquire_priv(str(self.default_desired_priv))
        -        return result
        +        self.transport = self.transport_class(**self.transport_args)
        +        self.transport.open()
        +        self.channel = Channel(self.transport, **self.channel_args)
         
        -    def textfsm_parse_output(
        -        self, command: str, output: str
        -    ) -> Union[List[Union[List[Any], Dict[str, Any]]], Dict[str, Any]]:
        +    def close(self) -> None:
                 """
        -        Parse output with TextFSM and ntc-templates
        -
        -        Always return a non-string value -- if parsing fails to produce list/dict, return empty dict
        +        Close Transport (socket/session)
         
                 Args:
        -            command: command used to get output
        -            output: output from command
        +            N/A
         
                 Returns:
        -            output: parsed output
        +            N/A  # noqa: DAR202
         
                 Raises:
        -            N/A  # noqa
        +            N/A
         
                 """
        -        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 {}
        +        self.transport.close()
         
        -    def get_prompt(self) -> str:
        +    def isalive(self) -> bool:
                 """
        -        Convenience method to get device prompt from Channel
        +        Check if underlying socket/channel is alive
         
                 Args:
        -            N/A  # noqa
        +            N/A
         
                 Returns:
        -            prompt: prompt received from channel.get_prompt
        +            bool: True/False if socket/channel is alive
         
                 Raises:
        -            N/A  # noqa
        +            N/A
         
                 """
        -        prompt: str = self.channel.get_prompt()
        -        return prompt
        -
        -    def open(self) -> None:
        -        super().open()
        -        if self.session_pre_login_handler:
        -            self.session_pre_login_handler(self)
        -        # send disable paging if needed
        -        if self.session_disable_paging:
        -            if callable(self.session_disable_paging):
        -                self.session_disable_paging(self)
        -            else:
        -                self.channel.send_inputs(self.session_disable_paging)
        -
        -    def close(self) -> None:
        -        self.transport.write(f"{self.exit_command}{self.comms_return_char}")
        -        super().close()
        + try: + alive = self.transport.isalive() + except AttributeError: + alive = False + return alive
        -

        Ancestors

        -

        Subclasses

        Methods

        -
        -def acquire_priv(self, desired_priv) -
        -
        -

        Acquire desired priv level

        -

        Args

        -
        -
        desired_priv
        -
        string name of desired privilege level -(see nssh.driver..driver for levels)
        -
        -

        Returns

        -
        -
        N/A -# noqa
        -
         
        -
        -

        Raises

        -
        -
        CouldNotAcquirePrivLevel
        -
        if requested priv level not attained
        -
        -
        - -Expand source code - -
        def acquire_priv(self, desired_priv: str) -> None:
        -    """
        -    Acquire desired priv level
        -
        -    Args:
        -        desired_priv: string name of desired privilege level
        -            (see nssh.driver.<driver_category.device_type>.driver for levels)
        -
        -    Returns:
        -        N/A  # noqa
        -
        -    Raises:
        -        CouldNotAcquirePrivLevel: if requested priv level not attained
        -
        -    """
        -    priv_attempt_counter = 0
        -    while True:
        -        current_priv = self._determine_current_priv(self.channel.get_prompt())
        -        if current_priv == self.privs[desired_priv]:
        -            return
        -        if priv_attempt_counter > len(self.privs):
        -            raise CouldNotAcquirePrivLevel(
        -                f"Could not get to '{desired_priv}' privilege level."
        -            )
        -        if current_priv.level > self.privs[desired_priv].level:
        -            self._deescalate()
        -        else:
        -            self._escalate()
        -        priv_attempt_counter += 1
        -
        -
        -
        -def get_prompt(self) +
        +def close(self)
        -

        Convenience method to get device prompt from Channel

        +

        Close Transport (socket/session)

        Args

        -

        N/A -# noqa

        +

        N/A

        Returns

        -
        prompt
        -
        prompt received from channel.get_prompt
        -
        -

        Raises

        -
        N/A -# noqa
        +# noqa: DAR202
         
        -
        -
        - -Expand source code - -
        def get_prompt(self) -> str:
        -    """
        -    Convenience method to get device prompt from Channel
        -
        -    Args:
        -        N/A  # noqa
        -
        -    Returns:
        -        prompt: prompt received from channel.get_prompt
        -
        -    Raises:
        -        N/A  # noqa
        -
        -    """
        -    prompt: str = self.channel.get_prompt()
        -    return prompt
        -
        -
        -
        -def send_commands(self, commands, strip_prompt=True, textfsm=False) -
        -
        -

        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

        Raises

        -
        N/A -# noqa
        +
        N/A
         
        Expand source code -
        def send_commands(
        -    self, commands: Union[str, List[str]], strip_prompt: bool = True, textfsm: bool = False,
        -) -> List[Result]:
        +
        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
        + self.transport.close()
        -
        -def send_configs(self, configs, strip_prompt=True) +
        +def isalive(self)
        -

        Send configuration(s)

        +

        Check if underlying socket/channel is alive

        Args

        -
        -
        configs
        -
        string or list of strings to send to device in config mode
        -
        strip_prompt
        -
        True/False strip prompt from returned output
        -
        +

        N/A

        Returns

        -
        N/A -# noqa
        -
         
        +
        bool
        +
        True/False if socket/channel is alive

        Raises

        -
        N/A -# noqa
        +
        N/A
         
        Expand source code -
        def send_configs(
        -    self, configs: Union[str, List[str]], strip_prompt: bool = True
        -) -> List[Result]:
        +
        def isalive(self) -> bool:
             """
        -    Send configuration(s)
        +    Check if underlying socket/channel is alive
         
             Args:
        -        configs: string or list of strings to send to device in config mode
        -        strip_prompt: True/False strip prompt from returned output
        +        N/A
         
             Returns:
        -        N/A  # noqa
        +        bool: True/False if socket/channel is alive
         
             Raises:
        -        N/A  # noqa
        +        N/A
         
             """
        -    self.acquire_priv("configuration")
        -    result = self.channel.send_inputs(configs, strip_prompt)
        -    self.acquire_priv(str(self.default_desired_priv))
        -    return result
        + try: + alive = self.transport.isalive() + except AttributeError: + alive = False + return alive
        -
        -def send_interactive(self, inputs, hidden_response=False) +
        +def open(self)
        -

        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
        -
        -
        -
        -def textfsm_parse_output(self, command, output) -
        -
        -

        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
        +
        N/A
         
        Expand source code -
        def textfsm_parse_output(
        -    self, command: str, output: str
        -) -> Union[List[Union[List[Any], Dict[str, Any]]], Dict[str, Any]]:
        +
        def open(self) -> None:
             """
        -    Parse output with TextFSM and ntc-templates
        -
        -    Always return a non-string value -- if parsing fails to produce list/dict, return empty dict
        +    Open Transport (socket/session) and establish channel
         
             Args:
        -        command: command used to get output
        -        output: output from command
        +        N/A
         
             Returns:
        -        output: parsed output
        +        N/A  # noqa: DAR202
         
             Raises:
        -        N/A  # noqa
        +        N/A
         
             """
        -    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 {}
        + self.transport = self.transport_class(**self.transport_args) + self.transport.open() + self.channel = Channel(self.transport, **self.channel_args)
        -

        Inherited members

        -
        @@ -1357,36 +1263,35 @@

        Index

        • Super-module

        • Sub-modules

        • Classes

          diff --git a/docs/nssh/driver/network_driver.html b/docs/scrapli/driver/network_driver.html similarity index 58% rename from docs/nssh/driver/network_driver.html rename to docs/scrapli/driver/network_driver.html index 5919fb4e..42a56dc9 100644 --- a/docs/nssh/driver/network_driver.html +++ b/docs/scrapli/driver/network_driver.html @@ -4,8 +4,8 @@ -nssh.driver.network_driver API documentation - +scrapli.driver.network_driver API documentation + @@ -17,26 +17,25 @@
          -

          Module nssh.driver.network_driver

          +

          Module scrapli.driver.network_driver

          -

          nssh.driver.network_driver

          +

          scrapli.driver.network_driver

          Expand source code -
          """nssh.driver.network_driver"""
          +
          """scrapli.driver.network_driver"""
           import logging
           import re
          -from dataclasses import dataclass
          -from io import TextIOWrapper
          +from collections import namedtuple
           from typing import Any, Callable, Dict, List, Optional, Tuple, Union
           
          -from nssh.driver.driver import NSSH
          -from nssh.exceptions import CouldNotAcquirePrivLevel, UnknownPrivLevel
          -from nssh.helper import _textfsm_get_template, get_prompt_pattern, textfsm_parse
          -from nssh.result import Result
          -from nssh.transport import (
          +from scrapli.driver.driver import Scrape
          +from scrapli.exceptions import CouldNotAcquirePrivLevel, UnknownPrivLevel
          +from scrapli.helper import get_prompt_pattern
          +from scrapli.response import Response
          +from scrapli.transport import (
               MIKO_TRANSPORT_ARGS,
               SSH2_TRANSPORT_ARGS,
               SYSTEM_SSH_TRANSPORT_ARGS,
          @@ -58,56 +57,27 @@ 

          Module nssh.driver.network_driver

          } -@dataclass -class PrivilegeLevel: - """ - Dataclass representing privilege levels of a device - - PrivilegeLevel contains the following fields: - - pattern: - name: - deescalate_priv: - deescalate: - escalate_priv: - escalate: - escalate_auth: - escalate_prompt: - requestable: - level: - - """ - - __slots__ = ( - "pattern", - "name", - "deescalate_priv", - "deescalate", - "escalate_priv", - "escalate", - "escalate_auth", - "escalate_prompt", - "requestable", - "level", - ) - pattern: str - name: str - deescalate_priv: str - deescalate: str - escalate_priv: str - escalate: str - escalate_auth: bool - escalate_prompt: str - requestable: bool - level: int +PrivilegeLevel = namedtuple( + "PrivilegeLevel", + "pattern " + "name " + "deescalate_priv " + "deescalate " + "escalate_priv " + "escalate " + "escalate_auth " + "escalate_prompt " + "requestable " + "level", +) PRIVS: Dict[str, PrivilegeLevel] = {} -LOG = logging.getLogger("nssh_base") +LOG = logging.getLogger("scrapli_base") -class NetworkDriver(NSSH): +class NetworkDriver(Scrape): def __init__(self, auth_secondary: str = "", **kwargs: Any): """ BaseNetworkDriver Object @@ -117,10 +87,10 @@

          Module nssh.driver.network_driver

          **kwargs: keyword args to pass to inherited class(es) Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ super().__init__(**kwargs) @@ -155,13 +125,14 @@

          Module nssh.driver.network_driver

          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 @@

          Module nssh.driver.network_driver

          Classes

          -
          +
          class NetworkDriver (auth_secondary='', **kwargs)
          @@ -409,20 +353,19 @@

          Args

          Returns

          N/A -# noqa
          +# noqa: DAR202
           

          Raises

          -
          N/A -# noqa
          +
          N/A
           
          Expand source code -
          class NetworkDriver(NSSH):
          +
          class NetworkDriver(Scrape):
               def __init__(self, auth_secondary: str = "", **kwargs: Any):
                   """
                   BaseNetworkDriver Object
          @@ -432,10 +375,10 @@ 

          Raises

          **kwargs: keyword args to pass to inherited class(es) Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ super().__init__(**kwargs) @@ -470,13 +413,14 @@

          Raises

          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 @@

          Raises

          Ancestors

          Subclasses

          Methods

          -
          +
          def acquire_priv(self, desired_priv)
          @@ -722,12 +639,12 @@

          Args

          desired_priv
          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
      -
      -def send_commands(self, commands, strip_prompt=True, textfsm=False) +
      +def send_commands(self, commands, strip_prompt=True)

      Send command(s)

      @@ -822,18 +737,15 @@

      Args

      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
      -
      +
      def send_configs(self, configs, strip_prompt=True)
      @@ -883,14 +794,12 @@

      Args

      Returns

      -
      N/A -# noqa
      -
       
      +
      responses
      +
      List of Scrape Response objects

      Raises

      -
      N/A -# noqa
      +
      N/A
       
      @@ -899,7 +808,7 @@

      Raises

      def send_configs(
           self, configs: Union[str, List[str]], strip_prompt: bool = True
      -) -> List[Result]:
      +) -> List[Response]:
           """
           Send configuration(s)
       
      @@ -908,19 +817,19 @@ 

      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
      + return responses
      -
      +
      def send_interactive(self, inputs, hidden_response=False)
      @@ -932,24 +841,22 @@

      Raises

      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 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
      -
      -
      -
      -def textfsm_parse_output(self, command, output) -
      -
      -

      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
      -
       
      -
      -
      - -Expand source code - -
      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 {}
      + responses = self.channel.send_inputs_interact(inputs, hidden_response) + return responses

      Inherited members

      -
      +
      class PrivilegeLevel (pattern, name, deescalate_priv, deescalate, escalate_priv, escalate, escalate_auth, escalate_prompt, requestable, level)
      -

      Dataclass representing privilege levels of a device

      -

      PrivilegeLevel contains the following fields:

      -

      pattern: -name: -deescalate_priv: -deescalate: -escalate_priv: -escalate: -escalate_auth: -escalate_prompt: -requestable: -level:

      -
      - -Expand source code - -
      class PrivilegeLevel:
      -    """
      -    Dataclass representing privilege levels of a device
      -
      -    PrivilegeLevel contains the following fields:
      -
      -    pattern:
      -    name:
      -    deescalate_priv:
      -    deescalate:
      -    escalate_priv:
      -    escalate:
      -    escalate_auth:
      -    escalate_prompt:
      -    requestable:
      -    level:
      -
      -    """
      -
      -    __slots__ = (
      -        "pattern",
      -        "name",
      -        "deescalate_priv",
      -        "deescalate",
      -        "escalate_priv",
      -        "escalate",
      -        "escalate_auth",
      -        "escalate_prompt",
      -        "requestable",
      -        "level",
      -    )
      -    pattern: str
      -    name: str
      -    deescalate_priv: str
      -    deescalate: str
      -    escalate_priv: str
      -    escalate: str
      -    escalate_auth: bool
      -    escalate_prompt: str
      -    requestable: bool
      -    level: int
      -
      +

      PrivilegeLevel(pattern, name, deescalate_priv, deescalate, escalate_priv, escalate, escalate_auth, escalate_prompt, requestable, level)

      +

      Ancestors

      +
        +
      • builtins.tuple
      • +

      Instance variables

      -
      var deescalate
      +
      var deescalate
      -

      Return an attribute of instance, which is of type owner.

      +

      Alias for field number 3

      -
      var deescalate_priv
      +
      var deescalate_priv
      -

      Return an attribute of instance, which is of type owner.

      +

      Alias for field number 2

      -
      var escalate
      +
      var escalate
      -

      Return an attribute of instance, which is of type owner.

      +

      Alias for field number 5

      -
      var escalate_auth
      +
      var escalate_auth
      -

      Return an attribute of instance, which is of type owner.

      +

      Alias for field number 6

      -
      var escalate_priv
      +
      var escalate_priv
      -

      Return an attribute of instance, which is of type owner.

      +

      Alias for field number 4

      -
      var escalate_prompt
      +
      var escalate_prompt
      -

      Return an attribute of instance, which is of type owner.

      +

      Alias for field number 7

      -
      var level
      +
      var level
      -

      Return an attribute of instance, which is of type owner.

      +

      Alias for field number 9

      -
      var name
      +
      var name
      -

      Return an attribute of instance, which is of type owner.

      +

      Alias for field number 1

      -
      var pattern
      +
      var pattern
      -

      Return an attribute of instance, which is of type owner.

      +

      Alias for field number 0

      -
      var requestable
      +
      var requestable
      -

      Return an attribute of instance, which is of type owner.

      +

      Alias for field number 8

      @@ -1173,35 +972,34 @@

      Index

      -
      +
      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"""

    Ancestors

      @@ -133,7 +133,7 @@

      Ancestors

    • builtins.BaseException
    -
    +
    class UnknownPrivLevel (...)
    @@ -163,25 +163,25 @@

    Index

    • Super-module

    • Classes

    • diff --git a/docs/nssh/helper.html b/docs/scrapli/helper.html similarity index 88% rename from docs/nssh/helper.html rename to docs/scrapli/helper.html index 0862a927..74a9f5a3 100644 --- a/docs/nssh/helper.html +++ b/docs/scrapli/helper.html @@ -4,8 +4,8 @@ -nssh.helper API documentation - +scrapli.helper API documentation + @@ -17,15 +17,15 @@
      -

      Module nssh.helper

      +

      Module scrapli.helper

      -

      nssh.helper

      +

      scrapli.helper

      Expand source code -
      """nssh.helper"""
      +
      """scrapli.helper"""
       import importlib
       import re
       import warnings
      @@ -49,7 +49,7 @@ 

      Module nssh.helper

      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 @@

      Module nssh.helper

      bool: True if provided string/callable is valid function, else False Raises: - N/A # noqa + N/A """ try: @@ -140,7 +140,7 @@

      Module nssh.helper

      ext_func: callable imported from external_function_path Raises: - N/A # noqa + N/A """ ext_func_path = external_function_path.split(".") @@ -162,6 +162,9 @@

      Module nssh.helper

      Returns: None or TextIO of opened template + Raises: + N/A + """ try: from textfsm.clitable import CliTable # pylint: disable=C0415 @@ -172,7 +175,7 @@

      Module nssh.helper

      f"To resolve this issue, install '{exc.name}'. You can do this in one of the following" " ways:\n" "1: 'pip install -r requirements-textfsm.txt'\n" - "2: 'pip install nssh[textfsm]'" + "2: 'pip install scrapli[textfsm]'" ) warning = "\n" + msg + "\n" + fix + "\n" + msg warnings.warn(warning) @@ -200,6 +203,9 @@

      Module nssh.helper

      Returns: output: structured data + Raises: + N/A + """ import textfsm # pylint: disable=C0415 @@ -223,7 +229,7 @@

      Module nssh.helper

      Functions

      -
      +
      def get_external_function(external_function_path)
      @@ -240,8 +246,7 @@

      Returns

      Raises

      -
      N/A -# noqa
      +
      N/A
       
      @@ -259,7 +264,7 @@

      Raises

      ext_func: callable imported from external_function_path Raises: - N/A # noqa + N/A """ ext_func_path = external_function_path.split(".") @@ -270,7 +275,7 @@

      Raises

      return ext_func
      -
      +
      def get_prompt_pattern(prompt, class_prompt)
      @@ -290,8 +295,7 @@

      Returns

      Raises

      -
      N/A -# noqa
      +
      N/A
       
      @@ -312,7 +316,7 @@

      Raises

      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 @@

      Raises

      return output
      -
      +
      def textfsm_parse(template, output)
      @@ -433,6 +435,11 @@

      Returns

      output
      structured data
      +
      +

      Raises

      +
      +
      N/A
      +
       
      @@ -451,6 +458,9 @@

      Returns

      Returns: output: structured data + Raises: + N/A + """ import textfsm # pylint: disable=C0415 @@ -467,7 +477,7 @@

      Returns

      return None
      -
      +
      def validate_external_function(possible_function)
      @@ -484,8 +494,7 @@

      Returns

      Raises

      -
      N/A -# noqa
      +
      N/A
       
      @@ -503,7 +512,7 @@

      Raises

      bool: True if provided string/callable is valid function, else False Raises: - N/A # noqa + N/A """ try: @@ -532,17 +541,17 @@

      Index

      diff --git a/docs/nssh/index.html b/docs/scrapli/index.html similarity index 80% rename from docs/nssh/index.html rename to docs/scrapli/index.html index a4195be7..cd7ec1c7 100644 --- a/docs/nssh/index.html +++ b/docs/scrapli/index.html @@ -4,8 +4,8 @@ -nssh API documentation - +scrapli API documentation + @@ -17,25 +17,25 @@
      -

      Module nssh

      +

      Module scrapli

      -

      nssh network ssh client library

      +

      scrapli network ssh client library

      Expand source code -
      """nssh network ssh client library"""
      +
      """scrapli network ssh client library"""
       import logging
       from logging import NullHandler
       from typing import Optional, Tuple
       
      -from nssh.driver import NSSH
      -from nssh.netmiko_compatability import connect_handler as ConnectHandler
      +from scrapli.driver import Scrape
      +from scrapli.netmiko_compatability import connect_handler as ConnectHandler
       
       __version__ = "2020.02.02"
       __all__ = (
      -    "NSSH",
      +    "Scrape",
           "ConnectHandler",
       )
       
      @@ -86,37 +86,37 @@ 

      Module nssh

      Sub-modules

      -
      nssh.channel
      +
      scrapli.channel
      -

      nssh.channel

      +

      scrapli.channel

      -
      nssh.decorators
      +
      scrapli.decorators
      -

      nssh.decorators

      +

      scrapli.decorators

      -
      nssh.driver
      +
      scrapli.driver
      -

      nssh.driver

      +

      scrapli.driver

      -
      nssh.exceptions
      +
      scrapli.exceptions
      -

      nssh.exceptions

      +

      scrapli.exceptions

      -
      nssh.helper
      +
      scrapli.helper
      -

      nssh.helper

      +

      scrapli.helper

      -
      nssh.netmiko_compatability
      +
      scrapli.netmiko_compatability
      -

      nssh.netmiko_compatibility

      +

      scrapli.netmiko_compatibility

      -
      nssh.result
      +
      scrapli.response
      -

      nssh.response

      +

      scrapli.response

      -
      nssh.transport
      +
      scrapli.transport
      -

      nssh.transport

      +

      scrapli.transport

      @@ -125,11 +125,11 @@

      Sub-modules

      Functions

      -
      +
      def ConnectHandler(auto_open=True, **kwargs)
      -

      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 @@

      Raises

      Classes

      -
      -class NSSH -(host='', port=22, auth_username='', auth_password='', auth_public_key='', auth_strict_key=True, timeout_socket=5, timeout_ssh=5000, timeout_ops=10, comms_prompt_pattern='^[a-z0-9.\\-@()/:]{1,32}[#>$]$', comms_return_char='\n', comms_ansi=False, session_pre_login_handler='', session_disable_paging='terminal length 0', ssh_config_file=True, driver='system') +
      +class Scrape +(host='', port=22, auth_username='', auth_password='', auth_public_key='', auth_strict_key=True, timeout_socket=5, timeout_ssh=5000, timeout_ops=10, comms_prompt_pattern='^[a-z0-9.\\-@()/:]{1,32}[#>$]$', comms_return_char='\n', comms_ansi=False, session_pre_login_handler='', session_disable_paging='terminal length 0', ssh_config_file=True, transport='system')
      -

      NSSH 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 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
      -
      nssh.driver
      -
      system|ssh2|paramiko – type of ssh driver to use +
      scrapli.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
      +# noqa: DAR202
       

      Raises

      @@ -277,7 +278,7 @@

      Raises

      Expand source code -
      class NSSH:
      +
      class Scrape:
           def __init__(
               self,
               host: str = "",
      @@ -295,13 +296,13 @@ 

      Raises

      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 @@

      Raises

      continue self.channel_args[arg] = getattr(self, arg) - def __enter__(self) -> "NSSH": + def __enter__(self) -> "Scrape": """ Enter method for context manager Args: - N/A # noqa + N/A Returns: self: instance of self Raises: - N/A # noqa + N/A """ self.open() @@ -419,47 +423,47 @@

      Raises

      traceback: traceback from exception being raised Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.close() def __str__(self) -> str: """ - Magic str method for NSSH + Magic str method for Scrape Args: - N/A # noqa + N/A Returns: - N/A # noqa + str: str representation of object Raises: - N/A # noqa + N/A """ - return f"NSSH Object for host {self.host}" + return f"Scrape Object for host {self.host}" def __repr__(self) -> str: """ - Magic repr method for NSSH + Magic repr method for Scrape Args: - N/A # noqa + N/A Returns: - repr: repr for class object + str: repr for class object Raises: - N/A # noqa + N/A """ class_dict = self.__dict__.copy() class_dict["auth_password"] = "********" - return f"NSSH {class_dict}" + return f"Scrape {class_dict}" def _setup_auth(self, auth_username: str, auth_password: str, auth_public_key: str) -> None: """ @@ -471,10 +475,10 @@

      Raises

      auth_public_key: public key to parse/set Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.auth_username = auth_username.strip() @@ -499,10 +503,10 @@

      Raises

      comms_ansi: ansi val to parse/set Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + TypeError: if invalid type args provided """ # try to compile prompt to raise TypeError before opening any connections @@ -528,10 +532,10 @@

      Raises

      session_disable_paging: disable paging to parse/set Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + TypeError: if invalid type args provided """ if session_pre_login_handler: @@ -639,13 +643,13 @@

      Raises

      Open Transport (socket/session) and establish channel Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.transport = self.transport_class(**self.transport_args) @@ -657,13 +661,13 @@

      Raises

      Close Transport (socket/session) Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.transport.close() @@ -673,13 +677,13 @@

      Raises

      Check if underlying socket/channel is alive Args: - N/A # noqa + N/A Returns: - alive: True/False if socket/channel is alive + bool: True/False if socket/channel is alive Raises: - N/A # noqa + N/A """ try: @@ -690,28 +694,26 @@

      Raises

      Subclasses

      Methods

      -
      +
      def close(self)

      Close Transport (socket/session)

      Args

      -

      N/A -# noqa

      +

      N/A

      Returns

      N/A -# noqa
      +# noqa: DAR202
       

      Raises

      -
      N/A -# noqa
      +
      N/A
       
      @@ -723,35 +725,33 @@

      Raises

      Close Transport (socket/session) Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.transport.close()
      -
      +
      def isalive(self)

      Check if underlying socket/channel is alive

      Args

      -

      N/A -# noqa

      +

      N/A

      Returns

      -
      alive
      +
      bool
      True/False if socket/channel is alive

      Raises

      -
      N/A -# noqa
      +
      N/A
       
      @@ -763,13 +763,13 @@

      Raises

      Check if underlying socket/channel is alive Args: - N/A # noqa + N/A Returns: - alive: True/False if socket/channel is alive + bool: True/False if socket/channel is alive Raises: - N/A # noqa + N/A """ try: @@ -779,24 +779,22 @@

      Raises

      return alive
      -
      +
      def open(self)

      Open Transport (socket/session) and establish channel

      Args

      -

      N/A -# noqa

      +

      N/A

      Returns

      N/A -# noqa
      +# noqa: DAR202
       

      Raises

      -
      N/A -# noqa
      +
      N/A
       
      @@ -808,13 +806,13 @@

      Raises

      Open Transport (socket/session) and establish channel Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.transport = self.transport_class(**self.transport_args) @@ -835,29 +833,29 @@

      Index

      • Sub-modules

      • Functions

      • Classes

        diff --git a/docs/nssh/netmiko_compatability.html b/docs/scrapli/netmiko_compatability.html similarity index 74% rename from docs/nssh/netmiko_compatability.html rename to docs/scrapli/netmiko_compatability.html index 5373c85c..ad0b4899 100644 --- a/docs/nssh/netmiko_compatability.html +++ b/docs/scrapli/netmiko_compatability.html @@ -4,8 +4,8 @@ -nssh.netmiko_compatability API documentation - +scrapli.netmiko_compatability API documentation + @@ -17,29 +17,29 @@
        -

        Module nssh.netmiko_compatability

        +

        Module scrapli.netmiko_compatability

        -

        nssh.netmiko_compatibility

        +

        scrapli.netmiko_compatibility

        Expand source code -
        """nssh.netmiko_compatibility"""
        +
        """scrapli.netmiko_compatibility"""
         import types
         import warnings
         from io import TextIOWrapper
         from typing import Any, Dict, List, Union
         
        -from nssh.driver import NetworkDriver
        -from nssh.driver.core.arista_eos.driver import EOS_ARG_MAPPER, EOSDriver
        -from nssh.driver.core.cisco_iosxe.driver import IOSXE_ARG_MAPPER, IOSXEDriver
        -from nssh.driver.core.cisco_iosxr.driver import IOSXR_ARG_MAPPER, IOSXRDriver
        -from nssh.driver.core.cisco_nxos.driver import NXOS_ARG_MAPPER, NXOSDriver
        -from nssh.driver.core.juniper_junos.driver import JUNOS_ARG_MAPPER, JunosDriver
        -from nssh.helper import _textfsm_get_template, textfsm_parse
        +from scrapli.driver import NetworkDriver
        +from scrapli.driver.core.arista_eos.driver import EOS_ARG_MAPPER, EOSDriver
        +from scrapli.driver.core.cisco_iosxe.driver import IOSXE_ARG_MAPPER, IOSXEDriver
        +from scrapli.driver.core.cisco_iosxr.driver import IOSXR_ARG_MAPPER, IOSXRDriver
        +from scrapli.driver.core.cisco_nxos.driver import NXOS_ARG_MAPPER, NXOSDriver
        +from scrapli.driver.core.juniper_junos.driver import JUNOS_ARG_MAPPER, JunosDriver
        +from scrapli.helper import _textfsm_get_template, textfsm_parse
         
        -VALID_NSSH_KWARGS = {
        +VALID_SCRAPLI_KWARGS = {
             "host",
             "port",
             "auth_username",
        @@ -78,32 +78,32 @@ 

        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 - 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 @@

        Module nssh.netmiko_compatability

        def transform_netmiko_kwargs(kwargs: Dict[str, Any]) -> Dict[str, Any]: """ - 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 + kwargs: netmiko-style ConnectHandler kwargs to transform to scrapli style Returns: transformed_kwargs: converted keyword arguments Raises: - N/A # noqa + N/A """ host = kwargs.pop("host", None) @@ -175,23 +175,24 @@

        Module nssh.netmiko_compatability

        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

        Args

        auto_open
        @@ -349,8 +356,8 @@

        Args

        Returns

        -
        NetmikoNetworkDriver
        -
        NSSH connection object for specified device-type
        +
        NetmikoNetworkDriver
        +
        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()
      -
      +
      def netmiko_send_command(self, command_string, **kwargs)

      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
      @@ -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 @@

      Raises

      return result
      -
      +
      def netmiko_send_command_timing(self, command_string, **kwargs)

      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)
      -
      +
      def netmiko_send_config_set(self, config_commands, **kwargs)

      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
      @@ -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

      Returns

      @@ -684,8 +706,7 @@

      Returns

      Raises

      -
      N/A -# noqa
      +
      N/A
       
      @@ -694,16 +715,16 @@

      Raises

      def transform_netmiko_kwargs(kwargs: Dict[str, Any]) -> Dict[str, Any]:
           """
      -    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
      +        kwargs: netmiko-style ConnectHandler kwargs to transform to scrapli style
       
           Returns:
               transformed_kwargs: converted keyword arguments
       
           Raises:
      -        N/A  # noqa
      +        N/A
       
           """
           host = kwargs.pop("host", None)
      @@ -729,7 +750,7 @@ 

      Raises

      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 """

      Ancestors

      Methods

      -
      +
      def send_command(self, command_string, **kwargs)

      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 @@

      Raises

      Inherited members

      @@ -883,25 +901,25 @@

      Index

      • Super-module

      • Functions

      • Classes

        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 @@

        Module nssh.result

        self.elapsed_time: Optional[float] = None self.channel_input = channel_input + self.textfsm_platform = textfsm_platform self.expectation = expectation - self.response = response + self.channel_response = channel_response self.finale = finale self.raw_result: str = "" self.result: str = "" @@ -86,13 +93,13 @@

        Module nssh.result

        Magic bool method based on channel_input being failed or not Args: - N/A # noqa + N/A Returns: bool: True/False if channel_input failed Raises: - N/A # noqa + N/A """ return self.failed @@ -102,34 +109,34 @@

        Module nssh.result

        Magic repr method for SSH2NetResponse class 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"SSH2NetResponse <Success: {str(not self.failed)}>" + return f"Scrape <Success: {str(not self.failed)}>" def __str__(self) -> str: """ Magic str method for SSH2NetResponse class Args: - N/A # noqa + N/A Returns: - N/A # noqa + str: str for class object Raises: - N/A # noqa + N/A """ - return f"SSH2NetResponse <Success: {str(not self.failed)}>" + return f"Scrape <Success: {str(not self.failed)}>" - def record_result(self, result: str) -> None: + def record_response(self, result: str) -> None: """ Record channel_input results and elapsed time of channel input/reading output @@ -137,17 +144,37 @@

        Module nssh.result

        result: string result of channel_input Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.finish_time = datetime.now() self.elapsed_time = (self.finish_time - self.start_time).total_seconds() self.result = result # update failed to false after recording results - self.failed = False
        + self.failed = False + + def textfsm_parse_output(self) -> None: + """ + Parse results with textfsm, assign result to `structured_result` + + Args: + N/A + + Returns: + N/A # noqa: DAR202 + + Raises: + N/A + + """ + template = _textfsm_get_template(self.textfsm_platform, self.channel_input) + if isinstance(template, TextIOWrapper): + self.structured_result = textfsm_parse(template, self.result) + else: + self.structured_result = []
        @@ -159,12 +186,12 @@

        Module nssh.result

        Classes

        -
        -class Result -(host, channel_input, expectation=None, response=None, finale=None, failed_when_contains=None) +
        +class Response +(host, channel_input, textfsm_platform='', expectation=None, channel_response=None, finale=None, failed_when_contains=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.

        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 @@

        Raises

        self.elapsed_time: Optional[float] = None self.channel_input = channel_input + self.textfsm_platform = textfsm_platform self.expectation = expectation - self.response = response + self.channel_response = channel_response self.finale = finale self.raw_result: str = "" self.result: str = "" @@ -256,13 +290,13 @@

        Raises

        Magic bool method based on channel_input being failed or not Args: - N/A # noqa + N/A Returns: bool: True/False if channel_input failed Raises: - N/A # noqa + N/A """ return self.failed @@ -272,34 +306,34 @@

        Raises

        Magic repr method for SSH2NetResponse class 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"SSH2NetResponse <Success: {str(not self.failed)}>" + return f"Scrape <Success: {str(not self.failed)}>" def __str__(self) -> str: """ Magic str method for SSH2NetResponse class Args: - N/A # noqa + N/A Returns: - N/A # noqa + str: str for class object Raises: - N/A # noqa + N/A """ - return f"SSH2NetResponse <Success: {str(not self.failed)}>" + return f"Scrape <Success: {str(not self.failed)}>" - def record_result(self, result: str) -> None: + def record_response(self, result: str) -> None: """ Record channel_input results and elapsed time of channel input/reading output @@ -307,22 +341,42 @@

        Raises

        result: string result of channel_input Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.finish_time = datetime.now() self.elapsed_time = (self.finish_time - self.start_time).total_seconds() self.result = result # update failed to false after recording results - self.failed = False
        + self.failed = False + + def textfsm_parse_output(self) -> None: + """ + Parse results with textfsm, assign result to `structured_result` + + Args: + N/A + + Returns: + N/A # noqa: DAR202 + + Raises: + N/A + + """ + template = _textfsm_get_template(self.textfsm_platform, self.channel_input) + if isinstance(template, TextIOWrapper): + self.structured_result = textfsm_parse(template, self.result) + else: + self.structured_result = []

      Methods

      -
      -def record_result(self, result) +
      +def record_response(self, result)

      Record channel_input results and elapsed time of channel input/reading output

      @@ -334,20 +388,19 @@

      Args

      Returns

      N/A -# noqa
      +# noqa: DAR202
       

      Raises

      -
      N/A -# noqa
      +
      N/A
       
      Expand source code -
      def record_result(self, result: str) -> None:
      +
      def record_response(self, result: str) -> None:
           """
           Record channel_input results and elapsed time of channel input/reading output
       
      @@ -355,10 +408,10 @@ 

      Raises

      result: string result of channel_input Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.finish_time = datetime.now() @@ -368,6 +421,49 @@

      Raises

      self.failed = False
      +
      +def textfsm_parse_output(self) +
      +
      +

      Parse results with textfsm, assign result to structured_result

      +

      Args

      +

      N/A

      +

      Returns

      +
      +
      N/A +# noqa: DAR202
      +
       
      +
      +

      Raises

      +
      +
      N/A
      +
       
      +
      +
      + +Expand source code + +
      def textfsm_parse_output(self) -> None:
      +    """
      +    Parse results with textfsm, assign result to `structured_result`
      +
      +    Args:
      +        N/A
      +
      +    Returns:
      +        N/A  # noqa: DAR202
      +
      +    Raises:
      +        N/A
      +
      +    """
      +    template = _textfsm_get_template(self.textfsm_platform, self.channel_input)
      +    if isinstance(template, TextIOWrapper):
      +        self.structured_result = textfsm_parse(template, self.result)
      +    else:
      +        self.structured_result = []
      +
      +
      @@ -381,15 +477,16 @@

      Index

      • Super-module

      • Classes

        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 @@

        Module nssh.transport.cssh2

        Check if session is authenticated Args: - N/A # noqa + N/A Returns: - authenticated: True if authenticated, else False + bool: True if authenticated, else False Raises: - N/A # noqa + N/A """ authenticated: bool = self.session.userauth_authenticated() @@ -294,13 +293,13 @@

        Module nssh.transport.cssh2

        Open channel, acquire pty, request interactive shell Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.channel = self.session.open_session() @@ -313,13 +312,13 @@

        Module nssh.transport.cssh2

        Close session and socket Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.session_lock.acquire() @@ -333,13 +332,13 @@

        Module nssh.transport.cssh2

        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 @@

        Module nssh.transport.cssh2

        channel_input: string to send to channel Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.channel.write(channel_input) - def flush(self) -> None: - """ - Flush channel stdout stream - - Args: - N/A # noqa - - Returns: - N/A # noqa - - Raises: - N/A # noqa - - """ - self.channel.flush() - def set_timeout(self, timeout: Optional[int] = None) -> None: """ Set session timeout @@ -405,10 +387,10 @@

        Module nssh.transport.cssh2

        timeout: timeout in seconds Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ if isinstance(timeout, int): @@ -425,10 +407,10 @@

        Module nssh.transport.cssh2

        blocking: True/False set session to blocking Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.session.set_blocking(blocking)
        @@ -443,7 +425,7 @@

        Module nssh.transport.cssh2

        Classes

        -
        +
        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 @@

        Raises

        Check if session is authenticated Args: - N/A # noqa + N/A Returns: - authenticated: True if authenticated, else False + bool: True if authenticated, else False Raises: - N/A # noqa + N/A """ authenticated: bool = self.session.userauth_authenticated() @@ -733,13 +712,13 @@

        Raises

        Open channel, acquire pty, request interactive shell Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.channel = self.session.open_session() @@ -752,13 +731,13 @@

        Raises

        Close session and socket Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.session_lock.acquire() @@ -772,13 +751,13 @@

        Raises

        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 @@

        Raises

        channel_input: string to send to channel Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.channel.write(channel_input) - def flush(self) -> None: - """ - Flush channel stdout stream - - Args: - N/A # noqa - - Returns: - N/A # noqa - - Raises: - N/A # noqa - - """ - self.channel.flush() - def set_timeout(self, timeout: Optional[int] = None) -> None: """ Set session timeout @@ -844,10 +806,10 @@

        Raises

        timeout: timeout in seconds Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ if isinstance(timeout, int): @@ -864,40 +826,38 @@

        Raises

        blocking: True/False set session to blocking Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.session.set_blocking(blocking)

        Ancestors

        Methods

        -
        +
        def authenticate(self)

        Parent method to try all means of authentication

        Args

        -

        N/A -# noqa

        +

        N/A

        Returns

        N/A -# noqa
        +# noqa: DAR202
         

        Raises

        -
        N/A -# noqa
        +
        N/A
         
        @@ -909,13 +869,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: @@ -935,23 +895,61 @@

        Raises

        return
        -
        +
        +def isalive(self) +
        +
        +

        Check if socket is alive and session is authenticated

        +

        Args

        +

        N/A

        +

        Returns

        +
        +
        bool
        +
        True if socket is alive and session authenticated, else False
        +
        +

        Raises

        +
        +
        N/A
        +
         
        +
        +
        + +Expand source code + +
        def isalive(self) -> bool:
        +    """
        +    Check if socket is alive and session is authenticated
        +
        +    Args:
        +        N/A
        +
        +    Returns:
        +        bool: True if socket is alive and session authenticated, else False
        +
        +    Raises:
        +        N/A
        +
        +    """
        +    if self.socket_isalive() and not self.channel.eof() and self.isauthenticated():
        +        return True
        +    return False
        +
        +
        +
        def isauthenticated(self)

        Check if session is authenticated

        Args

        -

        N/A -# noqa

        +

        N/A

        Returns

        -
        authenticated
        +
        bool
        True if authenticated, else False

        Raises

        -
        N/A -# noqa
        +
        N/A
         
        @@ -963,38 +961,37 @@

        Raises

        Check if session is authenticated Args: - N/A # noqa + N/A Returns: - authenticated: True if authenticated, else False + bool: True if authenticated, else False Raises: - N/A # noqa + N/A """ authenticated: bool = self.session.userauth_authenticated() return authenticated
        -
        +
        def open(self)

        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 @@

        Raises

        Inherited members

        @@ -1116,18 +1106,19 @@

        Index

        • Super-module

        • Classes

          diff --git a/docs/nssh/transport/index.html b/docs/scrapli/transport/index.html similarity index 62% rename from docs/nssh/transport/index.html rename to docs/scrapli/transport/index.html index 7c552a77..5954d0f6 100644 --- a/docs/nssh/transport/index.html +++ b/docs/scrapli/transport/index.html @@ -4,8 +4,8 @@ -nssh.transport API documentation - +scrapli.transport API documentation + @@ -17,19 +17,20 @@
          -

          Module nssh.transport

          +

          Module scrapli.transport

          -

          nssh.transport

          +

          scrapli.transport

          Expand source code -
          """nssh.transport"""
          -from nssh.transport.cssh2 import SSH2_TRANSPORT_ARGS, SSH2Transport
          -from nssh.transport.miko import MIKO_TRANSPORT_ARGS, MikoTransport
          -from nssh.transport.systemssh import SYSTEM_SSH_TRANSPORT_ARGS, SystemSSHTransport
          -from nssh.transport.transport import Transport
          +
          """scrapli.transport"""
          +from scrapli.transport.cssh2 import SSH2_TRANSPORT_ARGS, SSH2Transport
          +from scrapli.transport.miko import MIKO_TRANSPORT_ARGS, MikoTransport
          +from scrapli.transport.systemssh import SYSTEM_SSH_TRANSPORT_ARGS, SystemSSHTransport
          +from scrapli.transport.telnet import TELNET_TRANSPORT_ARGS, TelnetTransport
          +from scrapli.transport.transport import Transport
           
           __all__ = (
               "Transport",
          @@ -39,36 +40,42 @@ 

          Module nssh.transport

          "SSH2_TRANSPORT_ARGS", "SystemSSHTransport", "SYSTEM_SSH_TRANSPORT_ARGS", + "TELNET_TRANSPORT_ARGS", + "TelnetTransport", )

          Sub-modules

          -
          nssh.transport.cssh2
          +
          scrapli.transport.cssh2
          -

          nssh.transport.cssh2

          +

          scrapli.transport.cssh2

          -
          nssh.transport.miko
          +
          scrapli.transport.miko
          -

          nssh.transport.miko

          +

          scrapli.transport.miko

          -
          nssh.transport.ptyprocess
          +
          scrapli.transport.ptyprocess

          Ptyprocess is under the ISC license, as code derived from Pexpect. http://opensource.org/licenses/ISC …

          -
          nssh.transport.socket
          +
          scrapli.transport.socket
          -

          nssh.transport.socket

          +

          scrapli.transport.socket

          -
          nssh.transport.systemssh
          +
          scrapli.transport.systemssh
          -

          nssh.transport.systemssh

          +

          scrapli.transport.systemssh

          -
          nssh.transport.transport
          +
          scrapli.transport.telnet
          -

          nssh.transport.transport

          +

          scrapli.transport.telnet

          +
          +
          scrapli.transport.transport
          +
          +

          scrapli.transport.transport

          @@ -79,7 +86,7 @@

          Sub-modules

          Classes

          -
          +
          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 @@

          Raises

          Check if session is authenticated Args: - N/A # noqa + N/A Returns: - authenticated: True if authenticated, else False + bool: True if authenticated, else False Raises: - N/A # noqa + N/A """ authenticated: bool = self.session.is_authenticated() @@ -336,13 +340,13 @@

          Raises

          Open channel, acquire pty, request interactive shell Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.channel = self.session.open_session() @@ -356,13 +360,13 @@

          Raises

          Close session and socket Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.session_lock.acquire() @@ -376,13 +380,13 @@

          Raises

          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 @@

          Raises

          channel_input: string to send to channel Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.channel.send(channel_input) - def flush(self) -> None: - """ - Flush channel stdout stream - - Args: - N/A # noqa - - Returns: - N/A # noqa - - Raises: - N/A # noqa - - """ - while True: - time.sleep(0.1) - if self.channel.recv_ready(): - self.read() - else: - return - def set_timeout(self, timeout: Optional[int] = None) -> None: """ Set session timeout @@ -452,10 +434,10 @@

          Raises

          timeout: timeout in seconds Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ if isinstance(timeout, int): @@ -472,10 +454,10 @@

          Raises

          blocking: True/False set session to blocking Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.channel.setblocking(blocking) @@ -483,30 +465,28 @@

          Raises

          Ancestors

          Methods

          -
          +
          def authenticate(self)

          Parent method to try all means of authentication

          Args

          -

          N/A -# noqa

          +

          N/A

          Returns

          N/A -# noqa
          +# noqa: DAR202
           

          Raises

          -
          N/A -# noqa
          +
          N/A
           
          @@ -518,13 +498,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: @@ -540,23 +520,61 @@

          Raises

          return
          -
          +
          +def isalive(self) +
          +
          +

          Check if socket is alive and session is authenticated

          +

          Args

          +

          N/A

          +

          Returns

          +
          +
          bool
          +
          True if socket is alive and session authenticated, else False
          +
          +

          Raises

          +
          +
          N/A
          +
           
          +
          +
          + +Expand source code + +
          def isalive(self) -> bool:
          +    """
          +    Check if socket is alive and session is authenticated
          +
          +    Args:
          +        N/A
          +
          +    Returns:
          +        bool: True if socket is alive and session authenticated, else False
          +
          +    Raises:
          +        N/A
          +
          +    """
          +    if self.socket_isalive() and self.session.is_alive() and self.isauthenticated():
          +        return True
          +    return False
          +
          +
          +
          def isauthenticated(self)

          Check if session is authenticated

          Args

          -

          N/A -# noqa

          +

          N/A

          Returns

          -
          authenticated
          +
          bool
          True if authenticated, else False

          Raises

          -
          N/A -# noqa
          +
          N/A
           
          @@ -568,38 +586,37 @@

          Raises

          Check if session is authenticated Args: - N/A # noqa + N/A Returns: - authenticated: True if authenticated, else False + bool: True if authenticated, else False Raises: - N/A # noqa + N/A """ authenticated: bool = self.session.is_authenticated() return authenticated
          -
          +
          def open(self)

          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 @@

          Raises

          Inherited members

          -
          +
          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 @@

        Raises

        Check if session is authenticated Args: - N/A # noqa + N/A Returns: - authenticated: True if authenticated, else False + bool: True if authenticated, else False Raises: - N/A # noqa + N/A """ authenticated: bool = self.session.userauth_authenticated() @@ -998,13 +1005,13 @@

        Raises

        Open channel, acquire pty, request interactive shell Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.channel = self.session.open_session() @@ -1017,13 +1024,13 @@

        Raises

        Close session and socket Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.session_lock.acquire() @@ -1037,13 +1044,13 @@

        Raises

        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 @@

        Raises

        channel_input: string to send to channel Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.channel.write(channel_input) - def flush(self) -> None: - """ - Flush channel stdout stream - - Args: - N/A # noqa - - Returns: - N/A # noqa - - Raises: - N/A # noqa - - """ - self.channel.flush() - def set_timeout(self, timeout: Optional[int] = None) -> None: """ Set session timeout @@ -1109,10 +1099,10 @@

        Raises

        timeout: timeout in seconds Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ if isinstance(timeout, int): @@ -1129,40 +1119,38 @@

        Raises

        blocking: True/False set session to blocking Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.session.set_blocking(blocking)

        Ancestors

        Methods

        -
        +
        def authenticate(self)

        Parent method to try all means of authentication

        Args

        -

        N/A -# noqa

        +

        N/A

        Returns

        N/A -# noqa
        +# noqa: DAR202
         

        Raises

        -
        N/A -# noqa
        +
        N/A
         
        @@ -1174,13 +1162,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: @@ -1200,23 +1188,61 @@

        Raises

        return
        -
        +
        +def isalive(self) +
        +
        +

        Check if socket is alive and session is authenticated

        +

        Args

        +

        N/A

        +

        Returns

        +
        +
        bool
        +
        True if socket is alive and session authenticated, else False
        +
        +

        Raises

        +
        +
        N/A
        +
         
        +
        +
        + +Expand source code + +
        def isalive(self) -> bool:
        +    """
        +    Check if socket is alive and session is authenticated
        +
        +    Args:
        +        N/A
        +
        +    Returns:
        +        bool: True if socket is alive and session authenticated, else False
        +
        +    Raises:
        +        N/A
        +
        +    """
        +    if self.socket_isalive() and not self.channel.eof() and self.isauthenticated():
        +        return True
        +    return False
        +
        +
        +
        def isauthenticated(self)

        Check if session is authenticated

        Args

        -

        N/A -# noqa

        +

        N/A

        Returns

        -
        authenticated
        +
        bool
        True if authenticated, else False

        Raises

        -
        N/A -# noqa
        +
        N/A
         
        @@ -1228,38 +1254,37 @@

        Raises

        Check if session is authenticated Args: - N/A # noqa + N/A Returns: - authenticated: True if authenticated, else False + bool: True if authenticated, else False Raises: - N/A # noqa + N/A """ authenticated: bool = self.session.userauth_authenticated() return authenticated
        -
        +
        def open(self)

        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 @@

        Raises

        Inherited members

        -
        +
        class SystemSSHTransport (host, port=22, auth_username='', auth_public_key='', auth_password='', auth_strict_key=True, timeout_socket=5, timeout_ssh=5000, comms_prompt_pattern='^[a-z0-9.\\-@()/:]{1,32}[#>$]$', comms_return_char='\n', ssh_config_file=False)
        @@ -1402,13 +1420,13 @@

        Args

        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 @@

        Raises

        self.ssh_config_file: Union[str, bool] = ssh_config_file self.session: Union[Popen[bytes], PtyProcess] # pylint: disable=E1136 - self.lib_auth_exception = NSSHAuthenticationFailed + self.lib_auth_exception = ScrapliAuthenticationFailed self._isauthenticated = False self.open_cmd = ["ssh", self.host] @@ -1504,13 +1522,13 @@

        Raises

        Method to craft command to open ssh session Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.open_cmd.extend(["-p", str(self.port)]) @@ -1529,13 +1547,13 @@

        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: - AuthenticationFailed: if all authentication means fail + ScrapliAuthenticationFailed: if all authentication means fail """ self.session_lock.acquire() @@ -1550,20 +1568,20 @@

        Raises

        if not self._open_pty(): msg = f"Authentication to host {self.host} failed" LOG.critical(msg) - raise NSSHAuthenticationFailed(msg) + raise ScrapliAuthenticationFailed(msg) def _open_pipes(self) -> bool: """ Private method to open session with subprocess.Popen Args: - N/A # noqa + N/A Returns: bool: True/False session was opened and authenticated Raises: - N/A # noqa + N/A """ self.open_cmd.append("-v") @@ -1594,7 +1612,7 @@

        Raises

        bool: True/False session was authenticated Raises: - N/A # noqa + N/A """ output = b"" @@ -1609,13 +1627,13 @@

        Raises

        Private method to open session with PtyProcess Args: - N/A # noqa + N/A Returns: bool: True/False session was opened and authenticated Raises: - N/A # noqa + N/A """ pty_session = PtyProcess.spawn(self.open_cmd) @@ -1636,10 +1654,11 @@

        Raises

        pty_session: PtyProcess session object Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + exc: if unknown (i.e. not auth failed) exception occurs + lib_auth_exception: if known auth exception occurs """ self.session_lock.acquire() @@ -1649,7 +1668,7 @@

        Raises

        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 @@

        Raises

        Close session and socket Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.session_lock.acquire() @@ -1729,13 +1748,13 @@

        Raises

        Check if session is alive and session is authenticated Args: - N/A # noqa + N/A Returns: bool: True if session is alive and session authenticated, else False Raises: - N/A # noqa + N/A """ if isinstance(self.session, Popen): @@ -1751,14 +1770,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 """ read_bytes = 65535 @@ -1776,10 +1794,10 @@

        Raises

        channel_input: string to send to channel Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ if isinstance(self.session, Popen): @@ -1787,26 +1805,6 @@

        Raises

        elif isinstance(self.session, PtyProcess): self.session.write(channel_input.encode()) - def flush(self) -> None: - """ - Flush channel stdout stream - - Args: - N/A # noqa - - Returns: - N/A # noqa - - Raises: - N/A # noqa - - """ - if isinstance(self.session, Popen): - # flush seems to be unnecessary for Popen sessions - pass - elif isinstance(self.session, PtyProcess): - self.session.flush() - def set_timeout(self, timeout: Optional[int] = None) -> None: """ Set session timeout @@ -1815,10 +1813,10 @@

        Raises

        timeout: timeout in seconds Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -1832,28 +1830,27 @@

        Raises

        blocking: True/False set session to blocking Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """

        Ancestors

        Methods

        -
        +
        def isalive(self)

        Check if session is alive and session is authenticated

        Args

        -

        N/A -# noqa

        +

        N/A

        Returns

        bool
        @@ -1861,8 +1858,7 @@

        Returns

        Raises

        -
        N/A -# noqa
        +
        N/A
         
        @@ -1874,13 +1870,13 @@

        Raises

        Check if session is alive and session is authenticated Args: - N/A # noqa + N/A Returns: bool: True if session is alive and session authenticated, else False Raises: - N/A # noqa + N/A """ if isinstance(self.session, Popen): @@ -1892,23 +1888,22 @@

        Raises

        return False
        -
        +
        def open(self)

        Parent method to open session, authenticate and acquire shell

        Args

        -

        N/A -# noqa

        +

        N/A

        Returns

        N/A -# noqa
        +# noqa: DAR202
         

        Raises

        -
        AuthenticationFailed
        +
        ScrapliAuthenticationFailed
        if all authentication means fail
        @@ -1920,13 +1915,13 @@

        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: - AuthenticationFailed: if all authentication means fail + ScrapliAuthenticationFailed: if all authentication means fail """ self.session_lock.acquire() @@ -1941,28 +1936,24 @@

        Raises

        if not self._open_pty(): msg = f"Authentication to host {self.host} failed" LOG.critical(msg) - raise NSSHAuthenticationFailed(msg)
        + raise ScrapliAuthenticationFailed(msg)
        -
        +
        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
         
        @@ -1974,14 +1965,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 """ read_bytes = 65535 @@ -1992,7 +1982,7 @@

        Raises

        return b""
        -
        +
        def set_blocking(self, blocking=False)
        @@ -2006,13 +1996,12 @@

        Args

        Returns

        N/A -# noqa
        +# noqa: DAR202
         

        Raises

        -
        N/A -# noqa
        +
        N/A
         
        @@ -2029,10 +2018,10 @@

        Raises

        blocking: True/False set session to blocking Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """
        @@ -2040,17 +2029,450 @@

        Raises

        Inherited members

        -
        +
        +class TelnetTransport +(host, port=23, auth_username='', auth_public_key='', auth_password='', timeout_ssh=5000, timeout_socket=5, comms_prompt_pattern='^[a-z0-9.\\-@()/:]{1,32}[#>$]$', comms_return_char='\n') +
        +
        +

        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
        +
        +        """
        +
        +

        Ancestors

        + +

        Methods

        +
        +
        +def open(self) +
        +
        +

        Open channel, acquire pty, request interactive shell

        +

        Args

        +

        N/A

        +

        Returns

        +
        +
        N/A +# noqa: DAR202
        +
         
        +
        +

        Raises

        +
        +
        ScrapliAuthenticationFailed
        +
        if cant successfully authenticate
        +
        +
        + +Expand source code + +
        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
        +
        +
        +
        +

        Inherited members

        + +
        +
        class Transport (*args, **kwargs)
        @@ -2072,13 +2494,13 @@

        Inherited members

        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() @@ -2088,13 +2510,13 @@

        Inherited members

        Magic str method for Transport Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ return f"Transport Object for host {self.host}" @@ -2104,13 +2526,13 @@

        Inherited members

        Magic repr method for Transport Args: - N/A # noqa + N/A Returns: - repr: repr for class object + str: repr for class object Raises: - N/A # noqa + N/A """ class_dict = self.__dict__.copy() @@ -2123,13 +2545,13 @@

        Inherited members

        Open channel, acquire pty, request interactive shell Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -2139,13 +2561,13 @@

        Inherited members

        Close session and socket Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -2155,13 +2577,13 @@

        Inherited members

        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 + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -2171,13 +2593,13 @@

        Inherited members

        Read data from the channel Args: - N/A # noqa + N/A Returns: - output: bytes output as read from channel + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -2190,26 +2612,10 @@

        Inherited members

        channel_input: string to send to channel Returns: - N/A # noqa - - Raises: - N/A # noqa - - """ - - @abstractmethod - def flush(self) -> None: - """ - Flush channel stdout stream - - Args: - N/A # noqa - - Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -2222,10 +2628,10 @@

        Inherited members

        timeout: timeout in seconds Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -2238,10 +2644,10 @@

        Inherited members

        blocking: True/False set session to blocking Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """
        @@ -2251,30 +2657,29 @@

        Ancestors

      Subclasses

      Methods

      -
      +
      def close(self)

      Close session and socket

      Args

      -

      N/A -# noqa

      +

      N/A

      Returns

      N/A -# noqa
      +# noqa: DAR202
       

      Raises

      -
      N/A -# noqa
      +
      N/A
       
      @@ -2287,75 +2692,33 @@

      Raises

      Close session and socket Args: - N/A # noqa - - Returns: - N/A # noqa - - Raises: - N/A # noqa - - """
      -
      -
      -
      -def flush(self) -
      -
      -

      Flush channel stdout stream

      -

      Args

      -

      N/A -# noqa

      -

      Returns

      -
      -
      N/A -# noqa
      -
       
      -
      -

      Raises

      -
      -
      N/A -# noqa
      -
       
      -
      -
      - -Expand source code - -
      @abstractmethod
      -def flush(self) -> None:
      -    """
      -    Flush channel stdout stream
      -
      -    Args:
      -        N/A  # noqa
      +        N/A
       
           Returns:
      -        N/A  # noqa
      +        N/A  # noqa: DAR202
       
           Raises:
      -        N/A  # noqa
      +        N/A
       
           """
      -
      +
      def isalive(self)

      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
      +
      N/A +# noqa: DAR202
      +
       

      Raises

      -
      N/A -# noqa
      +
      N/A
       
      @@ -2368,35 +2731,33 @@

      Raises

      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 + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """
      -
      +
      def open(self)

      Open channel, acquire pty, request interactive shell

      Args

      -

      N/A -# noqa

      +

      N/A

      Returns

      N/A -# noqa
      +# noqa: DAR202
       

      Raises

      -
      N/A -# noqa
      +
      N/A
       
      @@ -2409,34 +2770,33 @@

      Raises

      Open channel, acquire pty, request interactive shell Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """
      -
      +
      def read(self)

      Read data from the channel

      Args

      -

      N/A -# noqa

      +

      N/A

      Returns

      -
      output
      -
      bytes output as read from channel
      +
      N/A +# noqa: DAR202
      +
       

      Raises

      -
      N/A -# noqa
      +
      N/A
       
      @@ -2449,18 +2809,18 @@

      Raises

      Read data from the channel Args: - N/A # noqa + N/A Returns: - output: bytes output as read from channel + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """
      -
      +
      def set_blocking(self, blocking=False)
      @@ -2473,13 +2833,12 @@

      Args

      Returns

      N/A -# noqa
      +# noqa: DAR202
       

      Raises

      -
      N/A -# noqa
      +
      N/A
       
      @@ -2495,15 +2854,15 @@

      Raises

      blocking: True/False set session to blocking Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """
      -
      +
      def set_timeout(self, timeout=None)
      @@ -2516,13 +2875,12 @@

      Args

      Returns

      N/A -# noqa
      +# noqa: DAR202
       

      Raises

      -
      N/A -# noqa
      +
      N/A
       
      @@ -2538,15 +2896,15 @@

      Raises

      timeout: timeout in seconds Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """
      -
      +
      def write(self, channel_input)
      @@ -2559,13 +2917,12 @@

      Args

      Returns

      N/A -# noqa
      +# noqa: DAR202
       

      Raises

      -
      N/A -# noqa
      +
      N/A
       
      @@ -2581,10 +2938,10 @@

      Raises

      channel_input: string to send to channel Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """
      @@ -2602,59 +2959,67 @@

      Index

      • Super-module

      • Sub-modules

      • Classes

        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 @@

        Module nssh.transport.miko

        Check if session is authenticated Args: - N/A # noqa + N/A Returns: - authenticated: True if authenticated, else False + bool: True if authenticated, else False Raises: - N/A # noqa + N/A """ authenticated: bool = self.session.is_authenticated() @@ -261,13 +259,13 @@

        Module nssh.transport.miko

        Open channel, acquire pty, request interactive shell Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.channel = self.session.open_session() @@ -281,13 +279,13 @@

        Module nssh.transport.miko

        Close session and socket Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.session_lock.acquire() @@ -301,13 +299,13 @@

        Module nssh.transport.miko

        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 @@

        Module nssh.transport.miko

        channel_input: string to send to channel Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.channel.send(channel_input) - def flush(self) -> None: - """ - Flush channel stdout stream - - Args: - N/A # noqa - - Returns: - N/A # noqa - - Raises: - N/A # noqa - - """ - while True: - time.sleep(0.1) - if self.channel.recv_ready(): - self.read() - else: - return - def set_timeout(self, timeout: Optional[int] = None) -> None: """ Set session timeout @@ -377,10 +353,10 @@

        Module nssh.transport.miko

        timeout: timeout in seconds Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ if isinstance(timeout, int): @@ -397,10 +373,10 @@

        Module nssh.transport.miko

        blocking: True/False set session to blocking Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.channel.setblocking(blocking) @@ -416,7 +392,7 @@

        Module nssh.transport.miko

        Classes

        -
        +
        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 @@

        Raises

        Check if session is authenticated Args: - N/A # noqa + N/A Returns: - authenticated: True if authenticated, else False + bool: True if authenticated, else False Raises: - N/A # noqa + N/A """ authenticated: bool = self.session.is_authenticated() @@ -673,13 +646,13 @@

        Raises

        Open channel, acquire pty, request interactive shell Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.channel = self.session.open_session() @@ -693,13 +666,13 @@

        Raises

        Close session and socket Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.session_lock.acquire() @@ -713,13 +686,13 @@

        Raises

        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 @@

        Raises

        channel_input: string to send to channel Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.channel.send(channel_input) - def flush(self) -> None: - """ - Flush channel stdout stream - - Args: - N/A # noqa - - Returns: - N/A # noqa - - Raises: - N/A # noqa - - """ - while True: - time.sleep(0.1) - if self.channel.recv_ready(): - self.read() - else: - return - def set_timeout(self, timeout: Optional[int] = None) -> None: """ Set session timeout @@ -789,10 +740,10 @@

        Raises

        timeout: timeout in seconds Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ if isinstance(timeout, int): @@ -809,10 +760,10 @@

        Raises

        blocking: True/False set session to blocking Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.channel.setblocking(blocking) @@ -820,30 +771,28 @@

        Raises

        Ancestors

        Methods

        -
        +
        def authenticate(self)

        Parent method to try all means of authentication

        Args

        -

        N/A -# noqa

        +

        N/A

        Returns

        N/A -# noqa
        +# noqa: DAR202
         

        Raises

        -
        N/A -# noqa
        +
        N/A
         
        @@ -855,13 +804,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: @@ -877,23 +826,61 @@

        Raises

        return
      -
      +
      +def isalive(self) +
      +
      +

      Check if socket is alive and session is authenticated

      +

      Args

      +

      N/A

      +

      Returns

      +
      +
      bool
      +
      True if socket is alive and session authenticated, else False
      +
      +

      Raises

      +
      +
      N/A
      +
       
      +
      +
      + +Expand source code + +
      def isalive(self) -> bool:
      +    """
      +    Check if socket is alive and session is authenticated
      +
      +    Args:
      +        N/A
      +
      +    Returns:
      +        bool: True if socket is alive and session authenticated, else False
      +
      +    Raises:
      +        N/A
      +
      +    """
      +    if self.socket_isalive() and self.session.is_alive() and self.isauthenticated():
      +        return True
      +    return False
      +
      +
      +
      def isauthenticated(self)

      Check if session is authenticated

      Args

      -

      N/A -# noqa

      +

      N/A

      Returns

      -
      authenticated
      +
      bool
      True if authenticated, else False

      Raises

      -
      N/A -# noqa
      +
      N/A
       
      @@ -905,38 +892,37 @@

      Raises

      Check if session is authenticated Args: - N/A # noqa + N/A Returns: - authenticated: True if authenticated, else False + bool: True if authenticated, else False Raises: - N/A # noqa + N/A """ authenticated: bool = self.session.is_authenticated() return authenticated
      -
      +
      def open(self)

      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 @@

      Raises

      Inherited members

      @@ -1056,18 +1035,19 @@

      Index

      • Super-module

      • Classes

        diff --git a/docs/nssh/transport/ptyprocess.html b/docs/scrapli/transport/ptyprocess.html similarity index 93% rename from docs/nssh/transport/ptyprocess.html rename to docs/scrapli/transport/ptyprocess.html index aba0f731..1e1e19af 100644 --- a/docs/nssh/transport/ptyprocess.html +++ b/docs/scrapli/transport/ptyprocess.html @@ -4,7 +4,7 @@ -nssh.transport.ptyprocess API documentation +scrapli.transport.ptyprocess API documentation @@ -18,7 +18,7 @@
        -

        Module nssh.transport.ptyprocess

        +

        Module scrapli.transport.ptyprocess

        Ptyprocess is under the ISC license, as code derived from Pexpect. @@ -853,7 +853,7 @@

        Module nssh.transport.ptyprocess

        Classes

        -
        +
        class PtyProcess (pid, fd)
        @@ -1538,11 +1538,11 @@

        Classes

        Class variables

        -
        var argv
        +
        var argv
        -
        var crlf
        +
        var crlf

        bytes(iterable_of_ints) -> bytes bytes(string, encoding[, errors]) -> bytes @@ -1555,19 +1555,19 @@

        Class variables

        - any object implementing the buffer API. - an integer

        -
        var encoding
        +
        var encoding
        -
        var env
        +
        var env
        -
        var launch_dir
        +
        var launch_dir
        -
        var linesep
        +
        var linesep

        bytes(iterable_of_ints) -> bytes bytes(string, encoding[, errors]) -> bytes @@ -1580,7 +1580,7 @@

        Class variables

        - any object implementing the buffer API. - an integer

        -
        var string_type
        +
        var string_type

        bytes(iterable_of_ints) -> bytes bytes(string, encoding[, errors]) -> bytes @@ -1596,7 +1596,7 @@

        Class variables

        Static methods

        -
        +
        def spawn(argv, cwd=None, env=None, echo=True, preexec_fn=None, dimensions=(24, 80), pass_fds=())
        @@ -1785,7 +1785,7 @@

        Static methods

        return inst
        -
        +
        def write_to_stdout(b)
        @@ -1806,7 +1806,7 @@

        Static methods

        Methods

        -
        +
        def close(self, force=True)
        @@ -1837,7 +1837,7 @@

        Methods

        self.closed = True
        -
        +
        def eof(self)
        @@ -1853,7 +1853,7 @@

        Methods

        return self.flag_eof
        -
        +
        def fileno(self)
        @@ -1868,7 +1868,7 @@

        Methods

        return self.fd
        -
        +
        def flush(self)
        @@ -1885,7 +1885,7 @@

        Methods

        pass
        -
        +
        def getecho(self)
        @@ -1916,7 +1916,7 @@

        Methods

        return self.echo
        -
        +
        def isalive(self)
        @@ -2013,7 +2013,7 @@

        Methods

        return False
        -
        +
        def isatty(self)
        @@ -2040,7 +2040,7 @@

        Methods

        return os.isatty(self.fd)
        -
        +
        def kill(self, sig)
        @@ -2065,7 +2065,7 @@

        Methods

        os.kill(self.pid, sig)
        -
        +
        def read(self, size=1024)
        @@ -2109,7 +2109,7 @@

        Methods

        return s
        -
        +
        def readline(self)
        @@ -2142,7 +2142,7 @@

        Methods

        return s
        -
        +
        def sendcontrol(self, char)
        @@ -2193,7 +2193,7 @@

        Methods

        return self._writeb(byte), byte
        -
        +
        def sendeof(self)
        @@ -2222,7 +2222,7 @@

        Methods

        return self._writeb(_EOF), _EOF
        -
        +
        def sendintr(self)
        @@ -2239,7 +2239,7 @@

        Methods

        return self._writeb(_INTR), _INTR
        -
        +
        def setecho(self, state)
        @@ -2311,7 +2311,7 @@

        Methods

        self.echo = state
        -
        +
        def setwinsize(self, rows, cols)
        @@ -2335,7 +2335,7 @@

        Methods

        return _setwinsize(self.fd, rows, cols)
        -
        +
        def terminate(self, force=False)
        @@ -2388,7 +2388,7 @@

        Methods

        return False
        -
        +
        def wait(self)
        @@ -2433,7 +2433,7 @@

        Methods

        return self.exitstatus
        -
        +
        def waitnoecho(self, timeout=None)
        @@ -2481,7 +2481,7 @@

        Methods

        time.sleep(0.1)
        -
        +
        def write(self, s, flush=True)
        @@ -2501,7 +2501,7 @@

        Methods

        -
        +
        class PtyProcessError (...)
        @@ -2531,46 +2531,46 @@

        Index

        • Super-module

        • Classes

        • diff --git a/docs/nssh/transport/socket.html b/docs/scrapli/transport/socket.html similarity index 82% rename from docs/nssh/transport/socket.html rename to docs/scrapli/transport/socket.html index ac7cde41..384760ce 100644 --- a/docs/nssh/transport/socket.html +++ b/docs/scrapli/transport/socket.html @@ -4,8 +4,8 @@ -nssh.transport.socket API documentation - +scrapli.transport.socket API documentation + @@ -17,20 +17,20 @@
          -

          Module nssh.transport.socket

          +

          Module scrapli.transport.socket

          -

          nssh.transport.socket

          +

          scrapli.transport.socket

          Expand source code -
          """nssh.transport.socket"""
          +
          """scrapli.transport.socket"""
           import logging
           import socket
           from typing import Optional
           
          -from nssh.exceptions import NSSHTimeout
          +from scrapli.exceptions import ScrapliTimeout
           
           LOG = logging.getLogger("transport")
           
          @@ -47,13 +47,13 @@ 

          Module nssh.transport.socket

          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.socket_isalive() @@ -63,13 +63,13 @@

          Module nssh.transport.socket

          Magic str method for Socket Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ return f"Socket Object for host {self.host}" @@ -79,13 +79,13 @@

          Module nssh.transport.socket

          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 @@

          Module nssh.transport.socket

          Close socket Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ if self.socket_isalive() and isinstance(self.sock, socket.socket): @@ -146,13 +147,13 @@

          Module nssh.transport.socket

          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: @@ -174,7 +175,7 @@

          Module nssh.transport.socket

          Classes

          -
          +
          class Socket (host, port, timeout)
          @@ -196,13 +197,13 @@

          Classes

          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.socket_isalive() @@ -212,13 +213,13 @@

          Classes

          Magic str method for Socket Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ return f"Socket Object for host {self.host}" @@ -228,13 +229,13 @@

          Classes

          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 @@

          Classes

          Close socket Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ if self.socket_isalive() and isinstance(self.sock, socket.socket): @@ -295,13 +297,13 @@

          Classes

          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: @@ -315,29 +317,27 @@

          Classes

          Subclasses

          Methods

          -
          +
          def socket_close(self)

          Close socket

          Args

          -

          N/A -# noqa

          +

          N/A

          Returns

          N/A -# noqa
          +# noqa: DAR202
           

          Raises

          -
          N/A -# noqa
          +
          N/A
           
          @@ -349,13 +349,13 @@

          Raises

          Close socket Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ if self.socket_isalive() and isinstance(self.sock, socket.socket): @@ -363,14 +363,13 @@

          Raises

          LOG.debug(f"Socket to host {self.host} closed")
          -
          +
          def socket_isalive(self)

          Check if socket is alive

          Args

          -

          N/A -# noqa

          +

          N/A

          Returns

          bool True/False if socket is alive
          @@ -378,8 +377,7 @@

          Returns

          Raises

          -
          N/A -# noqa
          +
          N/A
           
          @@ -391,13 +389,13 @@

          Raises

          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 @@

          Index

          • Super-module

          • Classes

            diff --git a/docs/nssh/transport/systemssh.html b/docs/scrapli/transport/systemssh.html similarity index 83% rename from docs/nssh/transport/systemssh.html rename to docs/scrapli/transport/systemssh.html index de19e56c..1cb05d00 100644 --- a/docs/nssh/transport/systemssh.html +++ b/docs/scrapli/transport/systemssh.html @@ -4,8 +4,8 @@ -nssh.transport.systemssh API documentation - +scrapli.transport.systemssh API documentation + @@ -17,15 +17,15 @@
            -

            Module nssh.transport.systemssh

            +

            Module scrapli.transport.systemssh

            -

            nssh.transport.systemssh

            +

            scrapli.transport.systemssh

            Expand source code -
            """nssh.transport.systemssh"""
            +
            """scrapli.transport.systemssh"""
             import re
             from logging import getLogger
             from select import select
            @@ -33,11 +33,11 @@ 

            Module nssh.transport.systemssh

            from threading import Lock from typing import TYPE_CHECKING, Optional, Union -from nssh.decorators import operation_timeout -from nssh.exceptions import NSSHAuthenticationFailed -from nssh.helper import get_prompt_pattern -from nssh.transport.ptyprocess import PtyProcess -from nssh.transport.transport import Transport +from scrapli.decorators import operation_timeout +from scrapli.exceptions import ScrapliAuthenticationFailed +from scrapli.helper import get_prompt_pattern +from scrapli.transport.ptyprocess import PtyProcess +from scrapli.transport.transport import Transport if TYPE_CHECKING: PopenBytes = Popen[bytes] # pylint: disable=E1136 @@ -55,6 +55,7 @@

            Module nssh.transport.systemssh

            "auth_public_key", "auth_password", "auth_strict_key", + "comms_prompt_pattern", "comms_return_char", "ssh_config_file", ) @@ -93,21 +94,21 @@

            Module nssh.transport.systemssh

            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 @@

            Module nssh.transport.systemssh

            self.ssh_config_file: Union[str, bool] = ssh_config_file self.session: Union[Popen[bytes], PtyProcess] # pylint: disable=E1136 - self.lib_auth_exception = NSSHAuthenticationFailed + self.lib_auth_exception = ScrapliAuthenticationFailed self._isauthenticated = False self.open_cmd = ["ssh", self.host] @@ -135,13 +136,13 @@

            Module nssh.transport.systemssh

            Method to craft command to open ssh session Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.open_cmd.extend(["-p", str(self.port)]) @@ -160,13 +161,13 @@

            Module nssh.transport.systemssh

            Parent method to open session, authenticate and acquire shell Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - AuthenticationFailed: if all authentication means fail + ScrapliAuthenticationFailed: if all authentication means fail """ self.session_lock.acquire() @@ -181,20 +182,20 @@

            Module nssh.transport.systemssh

            if not self._open_pty(): msg = f"Authentication to host {self.host} failed" LOG.critical(msg) - raise NSSHAuthenticationFailed(msg) + raise ScrapliAuthenticationFailed(msg) def _open_pipes(self) -> bool: """ Private method to open session with subprocess.Popen Args: - N/A # noqa + N/A Returns: bool: True/False session was opened and authenticated Raises: - N/A # noqa + N/A """ self.open_cmd.append("-v") @@ -225,7 +226,7 @@

            Module nssh.transport.systemssh

            bool: True/False session was authenticated Raises: - N/A # noqa + N/A """ output = b"" @@ -240,13 +241,13 @@

            Module nssh.transport.systemssh

            Private method to open session with PtyProcess Args: - N/A # noqa + N/A Returns: bool: True/False session was opened and authenticated Raises: - N/A # noqa + N/A """ pty_session = PtyProcess.spawn(self.open_cmd) @@ -267,10 +268,11 @@

            Module nssh.transport.systemssh

            pty_session: PtyProcess session object Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + exc: if unknown (i.e. not auth failed) exception occurs + lib_auth_exception: if known auth exception occurs """ self.session_lock.acquire() @@ -280,7 +282,7 @@

            Module nssh.transport.systemssh

            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 @@

            Module nssh.transport.systemssh

            Close session and socket Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.session_lock.acquire() @@ -360,13 +362,13 @@

            Module nssh.transport.systemssh

            Check if session is alive and session is authenticated Args: - N/A # noqa + N/A Returns: bool: True if session is alive and session authenticated, else False Raises: - N/A # noqa + N/A """ if isinstance(self.session, Popen): @@ -382,14 +384,13 @@

            Module nssh.transport.systemssh

            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 """ read_bytes = 65535 @@ -407,10 +408,10 @@

            Module nssh.transport.systemssh

            channel_input: string to send to channel Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ if isinstance(self.session, Popen): @@ -418,26 +419,6 @@

            Module nssh.transport.systemssh

            elif isinstance(self.session, PtyProcess): self.session.write(channel_input.encode()) - def flush(self) -> None: - """ - Flush channel stdout stream - - Args: - N/A # noqa - - Returns: - N/A # noqa - - Raises: - N/A # noqa - - """ - if isinstance(self.session, Popen): - # flush seems to be unnecessary for Popen sessions - pass - elif isinstance(self.session, PtyProcess): - self.session.flush() - def set_timeout(self, timeout: Optional[int] = None) -> None: """ Set session timeout @@ -446,10 +427,10 @@

            Module nssh.transport.systemssh

            timeout: timeout in seconds Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -463,10 +444,10 @@

            Module nssh.transport.systemssh

            blocking: True/False set session to blocking Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """
            @@ -480,7 +461,7 @@

            Module nssh.transport.systemssh

            Classes

            -
            +
            class SystemSSHTransport (host, port=22, auth_username='', auth_public_key='', auth_password='', auth_strict_key=True, timeout_socket=5, timeout_ssh=5000, comms_prompt_pattern='^[a-z0-9.\\-@()/:]{1,32}[#>$]$', comms_return_char='\n', ssh_config_file=False)
            @@ -512,13 +493,13 @@

            Args

            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 @@

          Raises

          self.ssh_config_file: Union[str, bool] = ssh_config_file self.session: Union[Popen[bytes], PtyProcess] # pylint: disable=E1136 - self.lib_auth_exception = NSSHAuthenticationFailed + self.lib_auth_exception = ScrapliAuthenticationFailed self._isauthenticated = False self.open_cmd = ["ssh", self.host] @@ -614,13 +595,13 @@

          Raises

          Method to craft command to open ssh session Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.open_cmd.extend(["-p", str(self.port)]) @@ -639,13 +620,13 @@

          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: - AuthenticationFailed: if all authentication means fail + ScrapliAuthenticationFailed: if all authentication means fail """ self.session_lock.acquire() @@ -660,20 +641,20 @@

          Raises

          if not self._open_pty(): msg = f"Authentication to host {self.host} failed" LOG.critical(msg) - raise NSSHAuthenticationFailed(msg) + raise ScrapliAuthenticationFailed(msg) def _open_pipes(self) -> bool: """ Private method to open session with subprocess.Popen Args: - N/A # noqa + N/A Returns: bool: True/False session was opened and authenticated Raises: - N/A # noqa + N/A """ self.open_cmd.append("-v") @@ -704,7 +685,7 @@

          Raises

          bool: True/False session was authenticated Raises: - N/A # noqa + N/A """ output = b"" @@ -719,13 +700,13 @@

          Raises

          Private method to open session with PtyProcess Args: - N/A # noqa + N/A Returns: bool: True/False session was opened and authenticated Raises: - N/A # noqa + N/A """ pty_session = PtyProcess.spawn(self.open_cmd) @@ -746,10 +727,11 @@

          Raises

          pty_session: PtyProcess session object Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + exc: if unknown (i.e. not auth failed) exception occurs + lib_auth_exception: if known auth exception occurs """ self.session_lock.acquire() @@ -759,7 +741,7 @@

          Raises

          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 @@

          Raises

          Close session and socket Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.session_lock.acquire() @@ -839,13 +821,13 @@

          Raises

          Check if session is alive and session is authenticated Args: - N/A # noqa + N/A Returns: bool: True if session is alive and session authenticated, else False Raises: - N/A # noqa + N/A """ if isinstance(self.session, Popen): @@ -861,14 +843,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 """ read_bytes = 65535 @@ -886,10 +867,10 @@

          Raises

          channel_input: string to send to channel Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ if isinstance(self.session, Popen): @@ -897,26 +878,6 @@

          Raises

          elif isinstance(self.session, PtyProcess): self.session.write(channel_input.encode()) - def flush(self) -> None: - """ - Flush channel stdout stream - - Args: - N/A # noqa - - Returns: - N/A # noqa - - Raises: - N/A # noqa - - """ - if isinstance(self.session, Popen): - # flush seems to be unnecessary for Popen sessions - pass - elif isinstance(self.session, PtyProcess): - self.session.flush() - def set_timeout(self, timeout: Optional[int] = None) -> None: """ Set session timeout @@ -925,10 +886,10 @@

          Raises

          timeout: timeout in seconds Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -942,28 +903,27 @@

          Raises

          blocking: True/False set session to blocking Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """

          Ancestors

          Methods

          -
          +
          def isalive(self)

          Check if session is alive and session is authenticated

          Args

          -

          N/A -# noqa

          +

          N/A

          Returns

          bool
          @@ -971,8 +931,7 @@

          Returns

          Raises

          -
          N/A -# noqa
          +
          N/A
           
          @@ -984,13 +943,13 @@

          Raises

          Check if session is alive and session is authenticated Args: - N/A # noqa + N/A Returns: bool: True if session is alive and session authenticated, else False Raises: - N/A # noqa + N/A """ if isinstance(self.session, Popen): @@ -1002,23 +961,22 @@

          Raises

          return False
          -
          +
          def open(self)

          Parent method to open session, authenticate and acquire shell

          Args

          -

          N/A -# noqa

          +

          N/A

          Returns

          N/A -# noqa
          +# noqa: DAR202
           

          Raises

          -
          AuthenticationFailed
          +
          ScrapliAuthenticationFailed
          if all authentication means fail
          @@ -1030,13 +988,13 @@

          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: - AuthenticationFailed: if all authentication means fail + ScrapliAuthenticationFailed: if all authentication means fail """ self.session_lock.acquire() @@ -1051,28 +1009,24 @@

          Raises

          if not self._open_pty(): msg = f"Authentication to host {self.host} failed" LOG.critical(msg) - raise NSSHAuthenticationFailed(msg)
          + raise ScrapliAuthenticationFailed(msg)
          -
          +
          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
           
          @@ -1084,14 +1038,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 """ read_bytes = 65535 @@ -1102,7 +1055,7 @@

          Raises

          return b""
          -
          +
          def set_blocking(self, blocking=False)
          @@ -1116,13 +1069,12 @@

          Args

          Returns

          N/A -# noqa
          +# noqa: DAR202
           

          Raises

          -
          N/A -# noqa
          +
          N/A
           
        @@ -1139,10 +1091,10 @@

        Raises

        blocking: True/False set session to blocking Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """
        @@ -1150,12 +1102,11 @@

        Raises

        Inherited members

        @@ -1171,18 +1122,18 @@

        Index

        • Super-module

        • Classes

          diff --git a/docs/scrapli/transport/telnet.html b/docs/scrapli/transport/telnet.html new file mode 100644 index 00000000..c38861d6 --- /dev/null +++ b/docs/scrapli/transport/telnet.html @@ -0,0 +1,914 @@ + + + + + + +scrapli.transport.telnet API documentation + + + + + + + + + +
          +
          +
          +

          Module scrapli.transport.telnet

          +
          +
          +

          scrapli.transport.telnet

          +
          + +Expand source code + +
          """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.

          +
          + +Expand source code + +
          class ScrapliTelnet(Telnet):
          +    def __init__(self, host: str, port: int) -> None:
          +        self.eof: bool
          +        super().__init__(host, port)
          +
          +

          Ancestors

          +
            +
          • telnetlib.Telnet
          • +
          +
          +
          +class TelnetTransport +(host, port=23, auth_username='', auth_public_key='', auth_password='', timeout_ssh=5000, timeout_socket=5, comms_prompt_pattern='^[a-z0-9.\\-@()/:]{1,32}[#>$]$', comms_return_char='\n') +
          +
          +

          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
          +
          +        """
          +
          +

          Ancestors

          + +

          Methods

          +
          +
          +def open(self) +
          +
          +

          Open channel, acquire pty, request interactive shell

          +

          Args

          +

          N/A

          +

          Returns

          +
          +
          N/A +# noqa: DAR202
          +
           
          +
          +

          Raises

          +
          +
          ScrapliAuthenticationFailed
          +
          if cant successfully authenticate
          +
          +
          + +Expand source code + +
          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
          +
          +
          +
          +

          Inherited members

          + +
          +
          +
          +
          + +
          + + + + + \ 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 @@

          Module nssh.transport.transport

          Magic str method for Transport Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ return f"Transport Object for host {self.host}" @@ -74,13 +74,13 @@

          Module nssh.transport.transport

          Magic repr method for Transport Args: - N/A # noqa + N/A Returns: - repr: repr for class object + str: repr for class object Raises: - N/A # noqa + N/A """ class_dict = self.__dict__.copy() @@ -93,13 +93,13 @@

          Module nssh.transport.transport

          Open channel, acquire pty, request interactive shell Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -109,13 +109,13 @@

          Module nssh.transport.transport

          Close session and socket Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -125,13 +125,13 @@

          Module nssh.transport.transport

          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 + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -141,13 +141,13 @@

          Module nssh.transport.transport

          Read data from the channel Args: - N/A # noqa + N/A Returns: - output: bytes output as read from channel + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -160,26 +160,10 @@

          Module nssh.transport.transport

          channel_input: string to send to channel Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa - - """ - - @abstractmethod - def flush(self) -> None: - """ - Flush channel stdout stream - - Args: - N/A # noqa - - Returns: - N/A # noqa - - Raises: - N/A # noqa + N/A """ @@ -192,10 +176,10 @@

          Module nssh.transport.transport

          timeout: timeout in seconds Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -208,10 +192,10 @@

          Module nssh.transport.transport

          blocking: True/False set session to blocking Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """
          @@ -225,7 +209,7 @@

          Module nssh.transport.transport

          Classes

          -
          +
          class Transport (*args, **kwargs)
          @@ -247,13 +231,13 @@

          Classes

          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() @@ -263,13 +247,13 @@

          Classes

          Magic str method for Transport Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ return f"Transport Object for host {self.host}" @@ -279,13 +263,13 @@

          Classes

          Magic repr method for Transport Args: - N/A # noqa + N/A Returns: - repr: repr for class object + str: repr for class object Raises: - N/A # noqa + N/A """ class_dict = self.__dict__.copy() @@ -298,13 +282,13 @@

          Classes

          Open channel, acquire pty, request interactive shell Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -314,13 +298,13 @@

          Classes

          Close session and socket Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -330,13 +314,13 @@

          Classes

          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 + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -346,13 +330,13 @@

          Classes

          Read data from the channel Args: - N/A # noqa + N/A Returns: - output: bytes output as read from channel + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -365,26 +349,10 @@

          Classes

          channel_input: string to send to channel Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa - - """ - - @abstractmethod - def flush(self) -> None: - """ - Flush channel stdout stream - - Args: - N/A # noqa - - Returns: - N/A # noqa - - Raises: - N/A # noqa + N/A """ @@ -397,10 +365,10 @@

          Classes

          timeout: timeout in seconds Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -413,10 +381,10 @@

          Classes

          blocking: True/False set session to blocking Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """
          @@ -426,30 +394,29 @@

          Ancestors

        Subclasses

        Methods

        -
        +
        def close(self)

        Close session and socket

        Args

        -

        N/A -# noqa

        +

        N/A

        Returns

        N/A -# noqa
        +# noqa: DAR202
         

        Raises

        -
        N/A -# noqa
        +
        N/A
         
        @@ -462,75 +429,33 @@

        Raises

        Close session and socket Args: - N/A # noqa - - Returns: - N/A # noqa - - Raises: - N/A # noqa - - """
        -
        -
        -
        -def flush(self) -
        -
        -

        Flush channel stdout stream

        -

        Args

        -

        N/A -# noqa

        -

        Returns

        -
        -
        N/A -# noqa
        -
         
        -
        -

        Raises

        -
        -
        N/A -# noqa
        -
         
        -
        -
        - -Expand source code - -
        @abstractmethod
        -def flush(self) -> None:
        -    """
        -    Flush channel stdout stream
        -
        -    Args:
        -        N/A  # noqa
        +        N/A
         
             Returns:
        -        N/A  # noqa
        +        N/A  # noqa: DAR202
         
             Raises:
        -        N/A  # noqa
        +        N/A
         
             """
        -
        +
        def isalive(self)

        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
        +
        N/A +# noqa: DAR202
        +
         

        Raises

        -
        N/A -# noqa
        +
        N/A
         
        @@ -543,35 +468,33 @@

        Raises

        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 + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """
        -
        +
        def open(self)

        Open channel, acquire pty, request interactive shell

        Args

        -

        N/A -# noqa

        +

        N/A

        Returns

        N/A -# noqa
        +# noqa: DAR202
         

        Raises

        -
        N/A -# noqa
        +
        N/A
         
        @@ -584,34 +507,33 @@

        Raises

        Open channel, acquire pty, request interactive shell Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """
        -
        +
        def read(self)

        Read data from the channel

        Args

        -

        N/A -# noqa

        +

        N/A

        Returns

        -
        output
        -
        bytes output as read from channel
        +
        N/A +# noqa: DAR202
        +
         

        Raises

        -
        N/A -# noqa
        +
        N/A
         
        @@ -624,18 +546,18 @@

        Raises

        Read data from the channel Args: - N/A # noqa + N/A Returns: - output: bytes output as read from channel + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """
        -
        +
        def set_blocking(self, blocking=False)
        @@ -648,13 +570,12 @@

        Args

        Returns

        N/A -# noqa
        +# noqa: DAR202
         

        Raises

        -
        N/A -# noqa
        +
        N/A
         
        @@ -670,15 +591,15 @@

        Raises

        blocking: True/False set session to blocking Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """
        -
        +
        def set_timeout(self, timeout=None)
        @@ -691,13 +612,12 @@

        Args

        Returns

        N/A -# noqa
        +# noqa: DAR202
         

        Raises

        -
        N/A -# noqa
        +
        N/A
         
        @@ -713,15 +633,15 @@

        Raises

        timeout: timeout in seconds Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """
        -
        +
        def write(self, channel_input)
        @@ -734,13 +654,12 @@

        Args

        Returns

        N/A -# noqa
        +# noqa: DAR202
         

        Raises

        -
        N/A -# noqa
        +
        N/A
         
        @@ -756,10 +675,10 @@

        Raises

        channel_input: string to send to channel Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """
        @@ -777,22 +696,21 @@

        Index

        • Super-module

        • Classes

          diff --git a/examples/basic_usage/iosxe_driver.py b/examples/basic_usage/iosxe_driver.py index dc31e69e..73ad9856 100644 --- a/examples/basic_usage/iosxe_driver.py +++ b/examples/basic_usage/iosxe_driver.py @@ -1,4 +1,4 @@ -from nssh.driver.core import IOSXEDriver +from scrapli.driver.core import IOSXEDriver args = {"host": "172.18.0.11", "auth_username": "vrnetlab", "auth_password": "VR-netlab9"} diff --git a/examples/basic_usage/nssh_driver.py b/examples/basic_usage/scrapli_driver.py similarity index 70% rename from examples/basic_usage/nssh_driver.py rename to examples/basic_usage/scrapli_driver.py index b47955db..e556996f 100644 --- a/examples/basic_usage/nssh_driver.py +++ b/examples/basic_usage/scrapli_driver.py @@ -1,20 +1,20 @@ -from nssh import NSSH +from scrapli import Scrape args = {"host": "172.18.0.11", "auth_username": "vrnetlab", "auth_password": "VR-netlab9"} -conn = NSSH(**args) +conn = Scrape(**args) conn.open() print(conn.channel.get_prompt()) print(conn.channel.send_inputs("show run | i hostname")[0].result) -# paging is NOT disabled w/ nssh driver! +# paging is NOT disabled w/ scrapli driver! conn.channel.send_inputs("terminal length 0") print(conn.channel.send_inputs("show run")[0].result) conn.close() -# Context manager is a great way to use nssh: -with NSSH(**args) as conn: +# Context manager is a great way to use scrapli: +with Scrape(**args) as conn: result = conn.channel.send_inputs("show run | i hostname") print(result[0].result) diff --git a/examples/logging/basic_logging.py b/examples/logging/basic_logging.py index c4ab2419..a40d1469 100644 --- a/examples/logging/basic_logging.py +++ b/examples/logging/basic_logging.py @@ -1,13 +1,13 @@ import logging -from nssh import NSSH +from scrapli import Scrape -logging.basicConfig(filename="nssh.log", level=logging.DEBUG) -logger = logging.getLogger("nssh") +logging.basicConfig(filename="scrapli.log", level=logging.DEBUG) +logger = logging.getLogger("scrapli") args = {"host": "172.18.0.11", "auth_username": "vrnetlab", "auth_password": "VR-netlab9"} -conn = NSSH(**args) +conn = Scrape(**args) conn.open() print(conn.channel.get_prompt()) diff --git a/examples/ssh_keys/ssh_keys.py b/examples/ssh_keys/ssh_keys.py index b5a76e5f..6968f75d 100644 --- a/examples/ssh_keys/ssh_keys.py +++ b/examples/ssh_keys/ssh_keys.py @@ -1,4 +1,4 @@ -from nssh.driver.core import IOSXEDriver +from scrapli.driver.core import IOSXEDriver args = {"host": "172.18.0.11", "auth_username": "vrnetlab", "auth_public_key": "/path/to/your/key"} diff --git a/nssh/channel/__init__.py b/nssh/channel/__init__.py deleted file mode 100644 index 1e139109..00000000 --- a/nssh/channel/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -"""nssh.channel""" -from nssh.channel.channel import CHANNEL_ARGS, Channel - -__all__ = ("Channel", "CHANNEL_ARGS") diff --git a/nssh/driver/__init__.py b/nssh/driver/__init__.py deleted file mode 100644 index 38ba5eb7..00000000 --- a/nssh/driver/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""nssh.driver""" -from nssh.driver.driver import NSSH -from nssh.driver.network_driver import NetworkDriver - -__all__ = ("NSSH", "NetworkDriver") diff --git a/nssh/driver/community/__init__.py b/nssh/driver/community/__init__.py deleted file mode 100644 index 39f9257d..00000000 --- a/nssh/driver/community/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -"""nssh.driver.community""" - -__all__ = () diff --git a/nssh/driver/core/__init__.py b/nssh/driver/core/__init__.py deleted file mode 100644 index 8975a9f2..00000000 --- a/nssh/driver/core/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -"""nssh.driver.core""" -from nssh.driver.core.arista_eos.driver import EOSDriver -from nssh.driver.core.cisco_iosxe.driver import IOSXEDriver -from nssh.driver.core.cisco_iosxr.driver import IOSXRDriver -from nssh.driver.core.cisco_nxos.driver import NXOSDriver -from nssh.driver.core.juniper_junos.driver import JunosDriver - -__all__ = ( - "EOSDriver", - "IOSXEDriver", - "IOSXRDriver", - "NXOSDriver", - "JunosDriver", -) diff --git a/nssh/driver/core/arista_eos/__init__.py b/nssh/driver/core/arista_eos/__init__.py deleted file mode 100644 index e8e11d36..00000000 --- a/nssh/driver/core/arista_eos/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -"""nssh.driver.core.arista_eos""" -from nssh.driver.core.arista_eos.driver import EOSDriver - -__all__ = ("EOSDriver",) diff --git a/nssh/driver/core/cisco_iosxe/__init__.py b/nssh/driver/core/cisco_iosxe/__init__.py deleted file mode 100644 index 0cfc58f2..00000000 --- a/nssh/driver/core/cisco_iosxe/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -"""nssh.driver.core.cisco_iosxe""" -from nssh.driver.core.cisco_iosxe.driver import IOSXEDriver - -__all__ = ("IOSXEDriver",) diff --git a/nssh/driver/core/cisco_iosxr/__init__.py b/nssh/driver/core/cisco_iosxr/__init__.py deleted file mode 100644 index 8bdcfdaf..00000000 --- a/nssh/driver/core/cisco_iosxr/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -"""nssh.driver.core.cisco_iosxr""" -from nssh.driver.core.cisco_iosxr.driver import IOSXRDriver - -__all__ = ("IOSXRDriver",) diff --git a/nssh/driver/core/cisco_nxos/__init__.py b/nssh/driver/core/cisco_nxos/__init__.py deleted file mode 100644 index 2e70e507..00000000 --- a/nssh/driver/core/cisco_nxos/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -"""nssh.driver.core.cisco_nxos""" -from nssh.driver.core.cisco_nxos.driver import NXOSDriver - -__all__ = ("NXOSDriver",) diff --git a/nssh/driver/core/juniper_junos/__init__.py b/nssh/driver/core/juniper_junos/__init__.py deleted file mode 100644 index 7b67bce0..00000000 --- a/nssh/driver/core/juniper_junos/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -"""nssh.driver.core.juniper_junos""" -from nssh.driver.core.juniper_junos.driver import JunosDriver - -__all__ = ("JunosDriver",) diff --git a/nssh/transport/__init__.py b/nssh/transport/__init__.py deleted file mode 100644 index ee6dde73..00000000 --- a/nssh/transport/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -"""nssh.transport""" -from nssh.transport.cssh2 import SSH2_TRANSPORT_ARGS, SSH2Transport -from nssh.transport.miko import MIKO_TRANSPORT_ARGS, MikoTransport -from nssh.transport.systemssh import SYSTEM_SSH_TRANSPORT_ARGS, SystemSSHTransport -from nssh.transport.transport import Transport - -__all__ = ( - "Transport", - "MikoTransport", - "MIKO_TRANSPORT_ARGS", - "SSH2Transport", - "SSH2_TRANSPORT_ARGS", - "SystemSSHTransport", - "SYSTEM_SSH_TRANSPORT_ARGS", -) diff --git a/nssh/__init__.py b/scrapli/__init__.py similarity index 89% rename from nssh/__init__.py rename to scrapli/__init__.py index a065199c..1f540230 100644 --- a/nssh/__init__.py +++ b/scrapli/__init__.py @@ -1,14 +1,14 @@ -"""nssh network ssh client library""" +"""scrapli network ssh client library""" import logging from logging import NullHandler from typing import Optional, Tuple -from nssh.driver import NSSH -from nssh.netmiko_compatability import connect_handler as ConnectHandler +from scrapli.driver import Scrape +from scrapli.netmiko_compatability import connect_handler as ConnectHandler __version__ = "2020.02.02" __all__ = ( - "NSSH", + "Scrape", "ConnectHandler", ) diff --git a/scrapli/channel/__init__.py b/scrapli/channel/__init__.py new file mode 100644 index 00000000..81689f8c --- /dev/null +++ b/scrapli/channel/__init__.py @@ -0,0 +1,4 @@ +"""scrapli.channel""" +from scrapli.channel.channel import CHANNEL_ARGS, Channel + +__all__ = ("Channel", "CHANNEL_ARGS") diff --git a/nssh/channel/channel.py b/scrapli/channel/channel.py similarity index 75% rename from nssh/channel/channel.py rename to scrapli/channel/channel.py index bb21d9bb..c0f0ece2 100644 --- a/nssh/channel/channel.py +++ b/scrapli/channel/channel.py @@ -1,12 +1,12 @@ -"""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") @@ -37,15 +37,16 @@ def __init__( 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 @@ -59,34 +60,34 @@ def __str__(self) -> str: Magic str method for Channel Args: - N/A # noqa + N/A Returns: - N/A # noqa + str: str for class object Raises: - N/A # noqa + N/A """ - return "nssh Channel Object" + return "scrapli Channel Object" def __repr__(self) -> str: """ Magic repr method for Channel Args: - N/A # noqa + N/A Returns: - repr: repr for class object + str: repr for class object Raises: - N/A # noqa + N/A """ class_dict = self.__dict__.copy() class_dict.pop("transport") - return f"nssh Channel {class_dict}" + return f"scrapli Channel {class_dict}" def _restructure_output(self, output: bytes, strip_prompt: bool = False) -> bytes: """ @@ -97,10 +98,10 @@ def _restructure_output(self, output: bytes, strip_prompt: bool = False) -> byte strip_prompt: bool True/False whether to strip prompt or not Returns: - output: bytes of joined output lines optionally with prompt removed + bytes: output of joined output lines optionally with prompt removed Raises: - N/A # noqa + N/A """ output = normalize_lines(output) @@ -118,13 +119,13 @@ def _read_chunk(self) -> bytes: Private method to read chunk and strip comms_ansi if needed Args: - N/A # noqa + N/A Returns: - output: output read from channel + bytes: output read from channel Raises: - N/A # noqa + N/A """ new_output = self.transport.read() @@ -141,10 +142,10 @@ def _read_until_input(self, channel_input: bytes) -> bytes: channel_input: string to write to channel Returns: - output: output read from channel + bytes: output read from channel Raises: - N/A # noqa + N/A """ output = b"" @@ -161,10 +162,10 @@ def _read_until_prompt(self, output: bytes = b"", prompt: str = "") -> bytes: prompt: prompt to look for if not looking for base prompt (self.comms_prompt_pattern) Returns: - output: output read from channel + bytes: output read from channel Raises: - N/A # noqa + N/A """ prompt_pattern = get_prompt_pattern(prompt, self.comms_prompt_pattern) @@ -175,7 +176,7 @@ def _read_until_prompt(self, output: bytes = b"", prompt: str = "") -> bytes: while True: output += self._read_chunk() - output = re.sub(b"\r", b"", output.strip()) + output = re.sub(b"\r", b"", output) channel_match = re.search(prompt_pattern, output) if channel_match: self.transport.set_blocking(True) @@ -187,18 +188,17 @@ def get_prompt(self) -> str: Get current channel prompt Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ prompt_pattern = get_prompt_pattern("", self.comms_prompt_pattern) self.transport.set_timeout(1000) - self.transport.flush() self.transport.write(self.comms_return_char) LOG.debug(f"Write (sending return character): {repr(self.comms_return_char)}") while True: @@ -212,7 +212,7 @@ def get_prompt(self) -> str: def send_inputs( self, inputs: Union[str, List[str], Tuple[str]], strip_prompt: bool = True - ) -> List[Result]: + ) -> List[Response]: """ Primary entry point to send data to devices in shell mode; accept inputs and return results @@ -221,24 +221,24 @@ def send_inputs( strip_prompt: strip prompt or not, defaults to True (yes, strip the prompt) Returns: - results: list of Result object(s) + responses: list of Response object(s) Raises: - N/A # noqa + N/A """ if isinstance(inputs, (list, tuple)): raw_inputs = tuple(inputs) else: raw_inputs = (inputs,) - results = [] + responses = [] for channel_input in raw_inputs: - result = Result(self.transport.host, channel_input) + response = Response(self.transport.host, channel_input) raw_result, processed_result = self._send_input(channel_input, strip_prompt) - 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(self, channel_input: str, strip_prompt: bool) -> Tuple[bytes, bytes]: @@ -253,12 +253,11 @@ def _send_input(self, channel_input: str, strip_prompt: bool) -> Tuple[bytes, by result: 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: {channel_input}; strip_prompt: {strip_prompt}") self.transport.write(channel_input) LOG.debug(f"Write: {repr(channel_input)}") @@ -271,78 +270,74 @@ def _send_input(self, channel_input: str, strip_prompt: bool) -> Tuple[bytes, by 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}" ) @@ -353,11 +348,11 @@ def _send_input_interact( 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)}") @@ -371,15 +366,14 @@ def _send_return(self) -> None: Send return char to device Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ - self.transport.flush() self.transport.write(self.comms_return_char) LOG.debug(f"Write (sending return character): {repr(self.comms_return_char)}") diff --git a/nssh/decorators.py b/scrapli/decorators.py similarity index 95% rename from nssh/decorators.py rename to scrapli/decorators.py index 24d5628d..4941e8a5 100644 --- a/nssh/decorators.py +++ b/scrapli/decorators.py @@ -1,8 +1,8 @@ -"""nssh.decorators""" +"""scrapli.decorators""" import logging from typing import Any, Callable, Dict, Union -LOG = logging.getLogger("nssh") +LOG = logging.getLogger("scrapli") def operation_timeout(attribute: str) -> Callable[..., Any]: diff --git a/scrapli/driver/__init__.py b/scrapli/driver/__init__.py new file mode 100644 index 00000000..d767fa46 --- /dev/null +++ b/scrapli/driver/__init__.py @@ -0,0 +1,5 @@ +"""scrapli.driver""" +from scrapli.driver.driver import Scrape +from scrapli.driver.network_driver import NetworkDriver + +__all__ = ("Scrape", "NetworkDriver") diff --git a/scrapli/driver/community/__init__.py b/scrapli/driver/community/__init__.py new file mode 100644 index 00000000..dd1806c3 --- /dev/null +++ b/scrapli/driver/community/__init__.py @@ -0,0 +1 @@ +"""scrapli.driver.community""" diff --git a/scrapli/driver/core/__init__.py b/scrapli/driver/core/__init__.py new file mode 100644 index 00000000..d23e6456 --- /dev/null +++ b/scrapli/driver/core/__init__.py @@ -0,0 +1,14 @@ +"""scrapli.driver.core""" +from scrapli.driver.core.arista_eos.driver import EOSDriver +from scrapli.driver.core.cisco_iosxe.driver import IOSXEDriver +from scrapli.driver.core.cisco_iosxr.driver import IOSXRDriver +from scrapli.driver.core.cisco_nxos.driver import NXOSDriver +from scrapli.driver.core.juniper_junos.driver import JunosDriver + +__all__ = ( + "EOSDriver", + "IOSXEDriver", + "IOSXRDriver", + "NXOSDriver", + "JunosDriver", +) diff --git a/scrapli/driver/core/arista_eos/__init__.py b/scrapli/driver/core/arista_eos/__init__.py new file mode 100644 index 00000000..e225307a --- /dev/null +++ b/scrapli/driver/core/arista_eos/__init__.py @@ -0,0 +1,4 @@ +"""scrapli.driver.core.arista_eos""" +from scrapli.driver.core.arista_eos.driver import EOSDriver + +__all__ = ("EOSDriver",) diff --git a/nssh/driver/core/arista_eos/driver.py b/scrapli/driver/core/arista_eos/driver.py similarity index 91% rename from nssh/driver/core/arista_eos/driver.py rename to scrapli/driver/core/arista_eos/driver.py index 6c582b22..8d81a4ba 100644 --- a/nssh/driver/core/arista_eos/driver.py +++ b/scrapli/driver/core/arista_eos/driver.py @@ -1,8 +1,8 @@ -"""nssh.driver.core.arista_eos.driver""" +"""scrapli.driver.core.arista_eos.driver""" from typing import Any, Dict -from nssh.driver import NetworkDriver -from nssh.driver.network_driver import PrivilegeLevel +from scrapli.driver import NetworkDriver +from scrapli.driver.network_driver import PrivilegeLevel EOS_ARG_MAPPER = { "comms_prompt_regex": r"^[a-z0-9.\-@()/:]{1,32}[#>$]$", @@ -81,10 +81,10 @@ def __init__(self, auth_secondary: str = "", **kwargs: Dict[str, Any]): **kwargs: keyword args to pass to inherited class(es) Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ super().__init__(auth_secondary, **kwargs) self.privs = PRIVS diff --git a/scrapli/driver/core/cisco_iosxe/__init__.py b/scrapli/driver/core/cisco_iosxe/__init__.py new file mode 100644 index 00000000..44ccc076 --- /dev/null +++ b/scrapli/driver/core/cisco_iosxe/__init__.py @@ -0,0 +1,4 @@ +"""scrapli.driver.core.cisco_iosxe""" +from scrapli.driver.core.cisco_iosxe.driver import IOSXEDriver + +__all__ = ("IOSXEDriver",) diff --git a/nssh/driver/core/cisco_iosxe/driver.py b/scrapli/driver/core/cisco_iosxe/driver.py similarity index 91% rename from nssh/driver/core/cisco_iosxe/driver.py rename to scrapli/driver/core/cisco_iosxe/driver.py index 9ddad219..9d9b6466 100644 --- a/nssh/driver/core/cisco_iosxe/driver.py +++ b/scrapli/driver/core/cisco_iosxe/driver.py @@ -1,8 +1,8 @@ -"""nssh.driver.core.cisco_iosxe.driver""" +"""scrapli.driver.core.cisco_iosxe.driver""" from typing import Any, Dict -from nssh.driver import NetworkDriver -from nssh.driver.network_driver import PrivilegeLevel +from scrapli.driver import NetworkDriver +from scrapli.driver.network_driver import PrivilegeLevel IOSXE_ARG_MAPPER = { "comms_prompt_pattern": r"^[a-z0-9.\-@()/:]{1,32}[#>$]$", @@ -81,10 +81,10 @@ def __init__(self, auth_secondary: str = "", **kwargs: Dict[str, Any]): **kwargs: keyword args to pass to inherited class(es) Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ super().__init__(auth_secondary, **kwargs) self.privs = PRIVS diff --git a/scrapli/driver/core/cisco_iosxr/__init__.py b/scrapli/driver/core/cisco_iosxr/__init__.py new file mode 100644 index 00000000..aada8dba --- /dev/null +++ b/scrapli/driver/core/cisco_iosxr/__init__.py @@ -0,0 +1,4 @@ +"""scrapli.driver.core.cisco_iosxr""" +from scrapli.driver.core.cisco_iosxr.driver import IOSXRDriver + +__all__ = ("IOSXRDriver",) diff --git a/nssh/driver/core/cisco_iosxr/driver.py b/scrapli/driver/core/cisco_iosxr/driver.py similarity index 85% rename from nssh/driver/core/cisco_iosxr/driver.py rename to scrapli/driver/core/cisco_iosxr/driver.py index 9c2e58ec..8d38d485 100644 --- a/nssh/driver/core/cisco_iosxr/driver.py +++ b/scrapli/driver/core/cisco_iosxr/driver.py @@ -1,13 +1,13 @@ -"""nssh.driver.core.cisco_iosxr.driver""" +"""scrapli.driver.core.cisco_iosxr.driver""" from typing import Any, Dict -from nssh.driver import NetworkDriver -from nssh.driver.network_driver import PrivilegeLevel +from scrapli.driver import NetworkDriver +from scrapli.driver.network_driver import PrivilegeLevel IOSXR_ARG_MAPPER = { "comms_prompt_regex": r"^[a-z0-9.\-@()/:]{1,32}[#>$]$", "comms_return_char": "\n", - "comms_pre_login_handler": "nssh.driver.core.cisco_iosxr.helper.comms_pre_login_handler", + "comms_pre_login_handler": "scrapli.driver.core.cisco_iosxr.helper.comms_pre_login_handler", "comms_disable_paging": "terminal length 0", } @@ -67,10 +67,10 @@ def __init__(self, auth_secondary: str = "", **kwargs: Dict[str, Any]): **kwargs: keyword args to pass to inherited class(es) Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ super().__init__(auth_secondary, **kwargs) self.privs = PRIVS diff --git a/nssh/driver/core/cisco_iosxr/helper.py b/scrapli/driver/core/cisco_iosxr/helper.py similarity index 71% rename from nssh/driver/core/cisco_iosxr/helper.py rename to scrapli/driver/core/cisco_iosxr/helper.py index d387c1c8..0a9c2173 100644 --- a/nssh/driver/core/cisco_iosxr/helper.py +++ b/scrapli/driver/core/cisco_iosxr/helper.py @@ -1,7 +1,7 @@ -"""nssh.driver.core.cisco_iosxr.helper""" +"""scrapli.driver.core.cisco_iosxr.helper""" import time -from nssh.driver import NetworkDriver +from scrapli.driver import NetworkDriver def comms_pre_login_handler(cls: NetworkDriver) -> None: # pylint: disable=W0613 @@ -12,10 +12,10 @@ def comms_pre_login_handler(cls: NetworkDriver) -> None: # pylint: disable=W061 cls: IOSXRDriver object Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ # sleep for session to establish; without this we never find base prompt diff --git a/scrapli/driver/core/cisco_nxos/__init__.py b/scrapli/driver/core/cisco_nxos/__init__.py new file mode 100644 index 00000000..9a14225f --- /dev/null +++ b/scrapli/driver/core/cisco_nxos/__init__.py @@ -0,0 +1,4 @@ +"""scrapli.driver.core.cisco_nxos""" +from scrapli.driver.core.cisco_nxos.driver import NXOSDriver + +__all__ = ("NXOSDriver",) diff --git a/nssh/driver/core/cisco_nxos/driver.py b/scrapli/driver/core/cisco_nxos/driver.py similarity index 91% rename from nssh/driver/core/cisco_nxos/driver.py rename to scrapli/driver/core/cisco_nxos/driver.py index 348de9e3..f49534ed 100644 --- a/nssh/driver/core/cisco_nxos/driver.py +++ b/scrapli/driver/core/cisco_nxos/driver.py @@ -1,8 +1,8 @@ -"""nssh.driver.core.cisco_nxos.driver""" +"""scrapli.driver.core.cisco_nxos.driver""" from typing import Any, Dict -from nssh.driver import NetworkDriver -from nssh.driver.network_driver import PrivilegeLevel +from scrapli.driver import NetworkDriver +from scrapli.driver.network_driver import PrivilegeLevel NXOS_ARG_MAPPER = { "comms_prompt_regex": r"^[a-z0-9.\-@()/:]{1,32}[#>$]$", @@ -81,10 +81,10 @@ def __init__(self, auth_secondary: str = "", **kwargs: Dict[str, Any]): **kwargs: keyword args to pass to inherited class(es) Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ super().__init__(auth_secondary, **kwargs) self.privs = PRIVS diff --git a/scrapli/driver/core/juniper_junos/__init__.py b/scrapli/driver/core/juniper_junos/__init__.py new file mode 100644 index 00000000..f84e0353 --- /dev/null +++ b/scrapli/driver/core/juniper_junos/__init__.py @@ -0,0 +1,4 @@ +"""scrapli.driver.core.juniper_junos""" +from scrapli.driver.core.juniper_junos.driver import JunosDriver + +__all__ = ("JunosDriver",) diff --git a/nssh/driver/core/juniper_junos/driver.py b/scrapli/driver/core/juniper_junos/driver.py similarity index 82% rename from nssh/driver/core/juniper_junos/driver.py rename to scrapli/driver/core/juniper_junos/driver.py index cebd8648..f9811381 100644 --- a/nssh/driver/core/juniper_junos/driver.py +++ b/scrapli/driver/core/juniper_junos/driver.py @@ -1,14 +1,14 @@ -"""nssh.driver.core.juniper_junos.driver""" +"""scrapli.driver.core.juniper_junos.driver""" from typing import Any, Dict -from nssh.driver import NetworkDriver -from nssh.driver.network_driver import PrivilegeLevel +from scrapli.driver import NetworkDriver +from scrapli.driver.network_driver import PrivilegeLevel JUNOS_ARG_MAPPER = { "comms_prompt_regex": r"^[a-z0-9.\-@()/:]{1,32}[#>$]$", "comms_return_char": "\n", "comms_pre_login_handler": "", - "comms_disable_paging": "nssh.driver.core.juniper_junos.helper.disable_paging", + "comms_disable_paging": "scrapli.driver.core.juniper_junos.helper.disable_paging", } PRIVS = { @@ -53,10 +53,10 @@ def __init__(self, auth_secondary: str = "", **kwargs: Dict[str, Any]): **kwargs: keyword args to pass to inherited class(es) Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ super().__init__(auth_secondary, **kwargs) self.privs = PRIVS diff --git a/nssh/driver/core/juniper_junos/helper.py b/scrapli/driver/core/juniper_junos/helper.py similarity index 70% rename from nssh/driver/core/juniper_junos/helper.py rename to scrapli/driver/core/juniper_junos/helper.py index 91e7096d..c1dd285a 100644 --- a/nssh/driver/core/juniper_junos/helper.py +++ b/scrapli/driver/core/juniper_junos/helper.py @@ -1,5 +1,5 @@ -"""nssh.driver.core.juniper_junos.helper""" -from nssh.driver import NetworkDriver +"""scrapli.driver.core.juniper_junos.helper""" +from scrapli.driver import NetworkDriver def disable_paging(cls: NetworkDriver) -> None: @@ -10,10 +10,10 @@ def disable_paging(cls: NetworkDriver) -> None: cls: SSH2NetSocket connection object Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ cls.channel.send_inputs("set cli screen-length 0") cls.channel.send_inputs("set cli screen-width 511") diff --git a/nssh/driver/driver.py b/scrapli/driver/driver.py similarity index 86% rename from nssh/driver/driver.py rename to scrapli/driver/driver.py index 6aeaacc2..6499a119 100644 --- a/nssh/driver/driver.py +++ b/scrapli/driver/driver.py @@ -1,19 +1,21 @@ -"""nssh.driver.driver""" +"""scrapli.driver.driver""" import logging import os import re from types import TracebackType from typing import Any, Callable, Dict, Optional, Tuple, Type, Union -from nssh.channel import CHANNEL_ARGS, Channel -from nssh.helper import get_external_function, validate_external_function -from nssh.transport import ( +from scrapli.channel import CHANNEL_ARGS, Channel +from scrapli.helper import get_external_function, validate_external_function +from scrapli.transport import ( MIKO_TRANSPORT_ARGS, SSH2_TRANSPORT_ARGS, SYSTEM_SSH_TRANSPORT_ARGS, + TELNET_TRANSPORT_ARGS, MikoTransport, SSH2Transport, SystemSSHTransport, + TelnetTransport, Transport, ) @@ -21,17 +23,19 @@ "system": SystemSSHTransport, "ssh2": SSH2Transport, "paramiko": MikoTransport, + "telnet": TelnetTransport, } TRANSPORT_ARGS: Dict[str, Tuple[str, ...]] = { "system": SYSTEM_SSH_TRANSPORT_ARGS, "ssh2": SSH2_TRANSPORT_ARGS, "paramiko": MIKO_TRANSPORT_ARGS, + "telnet": TELNET_TRANSPORT_ARGS, } -LOG = logging.getLogger("nssh_base") +LOG = logging.getLogger("scrapli_base") -class NSSH: +class Scrape: def __init__( self, host: str = "", @@ -49,13 +53,13 @@ def __init__( 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: @@ -70,7 +74,7 @@ def __init__( 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. @@ -82,18 +86,19 @@ def __init__( 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 @@ -129,10 +134,12 @@ def __init__( 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 = {} @@ -141,18 +148,18 @@ def __init__( continue self.channel_args[arg] = getattr(self, arg) - def __enter__(self) -> "NSSH": + def __enter__(self) -> "Scrape": """ Enter method for context manager Args: - N/A # noqa + N/A Returns: self: instance of self Raises: - N/A # noqa + N/A """ self.open() @@ -173,47 +180,47 @@ def __exit__( traceback: traceback from exception being raised Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.close() def __str__(self) -> str: """ - Magic str method for NSSH + Magic str method for Scrape Args: - N/A # noqa + N/A Returns: - N/A # noqa + str: str representation of object Raises: - N/A # noqa + N/A """ - return f"NSSH Object for host {self.host}" + return f"Scrape Object for host {self.host}" def __repr__(self) -> str: """ - Magic repr method for NSSH + Magic repr method for Scrape Args: - N/A # noqa + N/A Returns: - repr: repr for class object + str: repr for class object Raises: - N/A # noqa + N/A """ class_dict = self.__dict__.copy() class_dict["auth_password"] = "********" - return f"NSSH {class_dict}" + return f"Scrape {class_dict}" def _setup_auth(self, auth_username: str, auth_password: str, auth_public_key: str) -> None: """ @@ -225,10 +232,10 @@ def _setup_auth(self, auth_username: str, auth_password: str, auth_public_key: s auth_public_key: public key to parse/set Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.auth_username = auth_username.strip() @@ -253,10 +260,10 @@ def _setup_comms( comms_ansi: ansi val to parse/set Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + TypeError: if invalid type args provided """ # try to compile prompt to raise TypeError before opening any connections @@ -282,10 +289,10 @@ def _setup_session( session_disable_paging: disable paging to parse/set Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + TypeError: if invalid type args provided """ if session_pre_login_handler: @@ -393,13 +400,13 @@ def open(self) -> None: Open Transport (socket/session) and establish channel Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.transport = self.transport_class(**self.transport_args) @@ -411,13 +418,13 @@ def close(self) -> None: Close Transport (socket/session) Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.transport.close() @@ -427,13 +434,13 @@ def isalive(self) -> bool: Check if underlying socket/channel is alive Args: - N/A # noqa + N/A Returns: - alive: True/False if socket/channel is alive + bool: True/False if socket/channel is alive Raises: - N/A # noqa + N/A """ try: diff --git a/nssh/driver/network_driver.py b/scrapli/driver/network_driver.py similarity index 67% rename from nssh/driver/network_driver.py rename to scrapli/driver/network_driver.py index c1d458f7..d0561f37 100644 --- a/nssh/driver/network_driver.py +++ b/scrapli/driver/network_driver.py @@ -1,15 +1,14 @@ -"""nssh.driver.network_driver""" +"""scrapli.driver.network_driver""" import logging import re -from dataclasses import dataclass -from io import TextIOWrapper +from collections import namedtuple from typing import Any, Callable, Dict, List, Optional, Tuple, Union -from nssh.driver.driver import NSSH -from nssh.exceptions import CouldNotAcquirePrivLevel, UnknownPrivLevel -from nssh.helper import _textfsm_get_template, get_prompt_pattern, textfsm_parse -from nssh.result import Result -from nssh.transport import ( +from scrapli.driver.driver import Scrape +from scrapli.exceptions import CouldNotAcquirePrivLevel, UnknownPrivLevel +from scrapli.helper import get_prompt_pattern +from scrapli.response import Response +from scrapli.transport import ( MIKO_TRANSPORT_ARGS, SSH2_TRANSPORT_ARGS, SYSTEM_SSH_TRANSPORT_ARGS, @@ -31,56 +30,27 @@ } -@dataclass -class PrivilegeLevel: - """ - Dataclass representing privilege levels of a device - - PrivilegeLevel contains the following fields: - - pattern: - name: - deescalate_priv: - deescalate: - escalate_priv: - escalate: - escalate_auth: - escalate_prompt: - requestable: - level: - - """ - - __slots__ = ( - "pattern", - "name", - "deescalate_priv", - "deescalate", - "escalate_priv", - "escalate", - "escalate_auth", - "escalate_prompt", - "requestable", - "level", - ) - pattern: str - name: str - deescalate_priv: str - deescalate: str - escalate_priv: str - escalate: str - escalate_auth: bool - escalate_prompt: str - requestable: bool - level: int +PrivilegeLevel = namedtuple( + "PrivilegeLevel", + "pattern " + "name " + "deescalate_priv " + "deescalate " + "escalate_priv " + "escalate " + "escalate_auth " + "escalate_prompt " + "requestable " + "level", +) PRIVS: Dict[str, PrivilegeLevel] = {} -LOG = logging.getLogger("nssh_base") +LOG = logging.getLogger("scrapli_base") -class NetworkDriver(NSSH): +class NetworkDriver(Scrape): def __init__(self, auth_secondary: str = "", **kwargs: Any): """ BaseNetworkDriver Object @@ -90,10 +60,10 @@ def __init__(self, auth_secondary: str = "", **kwargs: Any): **kwargs: keyword args to pass to inherited class(es) Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ super().__init__(**kwargs) @@ -128,13 +98,14 @@ def _escalate(self) -> None: 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()) @@ -168,13 +139,13 @@ def _deescalate(self) -> None: 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()) @@ -193,10 +164,10 @@ def acquire_priv(self, desired_priv: str) -> None: Args: desired_priv: string name of desired privilege level - (see nssh.driver..driver for levels) + (see scrapli.driver..driver for levels) Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: CouldNotAcquirePrivLevel: if requested priv level not attained @@ -218,36 +189,35 @@ def acquire_priv(self, desired_priv: str) -> None: 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 @@ -258,27 +228,27 @@ def send_interactive( 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) @@ -287,55 +257,29 @@ def send_configs( 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() diff --git a/nssh/exceptions.py b/scrapli/exceptions.py similarity index 61% rename from nssh/exceptions.py rename to scrapli/exceptions.py index a026128f..30f25c31 100644 --- a/nssh/exceptions.py +++ b/scrapli/exceptions.py @@ -1,16 +1,16 @@ -"""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): diff --git a/nssh/helper.py b/scrapli/helper.py similarity index 94% rename from nssh/helper.py rename to scrapli/helper.py index a26fe97f..d05ee57c 100644 --- a/nssh/helper.py +++ b/scrapli/helper.py @@ -1,4 +1,4 @@ -"""nssh.helper""" +"""scrapli.helper""" import importlib import re import warnings @@ -22,7 +22,7 @@ def get_prompt_pattern(prompt: str, class_prompt: str) -> Pattern[bytes]: output: bytes string each line right stripped Raises: - N/A # noqa + N/A """ check_prompt = prompt or class_prompt @@ -45,10 +45,10 @@ def normalize_lines(output: bytes) -> bytes: 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()]) @@ -64,10 +64,10 @@ def strip_ansi(output: bytes) -> bytes: 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-?]*[ -/]*[@-~])") @@ -86,7 +86,7 @@ def validate_external_function(possible_function: Union[Callable[..., Any], str] bool: True if provided string/callable is valid function, else False Raises: - N/A # noqa + N/A """ try: @@ -113,7 +113,7 @@ def get_external_function(external_function_path: str) -> Callable[..., Any]: ext_func: callable imported from external_function_path Raises: - N/A # noqa + N/A """ ext_func_path = external_function_path.split(".") @@ -135,6 +135,9 @@ def _textfsm_get_template(platform: str, command: str) -> Optional[TextIO]: Returns: None or TextIO of opened template + Raises: + N/A + """ try: from textfsm.clitable import CliTable # pylint: disable=C0415 @@ -145,7 +148,7 @@ def _textfsm_get_template(platform: str, command: str) -> Optional[TextIO]: f"To resolve this issue, install '{exc.name}'. You can do this in one of the following" " ways:\n" "1: 'pip install -r requirements-textfsm.txt'\n" - "2: 'pip install nssh[textfsm]'" + "2: 'pip install scrapli[textfsm]'" ) warning = "\n" + msg + "\n" + fix + "\n" + msg warnings.warn(warning) @@ -173,6 +176,9 @@ def textfsm_parse( Returns: output: structured data + Raises: + N/A + """ import textfsm # pylint: disable=C0415 diff --git a/nssh/netmiko_compatability.py b/scrapli/netmiko_compatability.py similarity index 78% rename from nssh/netmiko_compatability.py rename to scrapli/netmiko_compatability.py index 8c28d43c..bc974f18 100644 --- a/nssh/netmiko_compatability.py +++ b/scrapli/netmiko_compatability.py @@ -1,18 +1,18 @@ -"""nssh.netmiko_compatibility""" +"""scrapli.netmiko_compatibility""" import types import warnings from io import TextIOWrapper from typing import Any, Dict, List, Union -from nssh.driver import NetworkDriver -from nssh.driver.core.arista_eos.driver import EOS_ARG_MAPPER, EOSDriver -from nssh.driver.core.cisco_iosxe.driver import IOSXE_ARG_MAPPER, IOSXEDriver -from nssh.driver.core.cisco_iosxr.driver import IOSXR_ARG_MAPPER, IOSXRDriver -from nssh.driver.core.cisco_nxos.driver import NXOS_ARG_MAPPER, NXOSDriver -from nssh.driver.core.juniper_junos.driver import JUNOS_ARG_MAPPER, JunosDriver -from nssh.helper import _textfsm_get_template, textfsm_parse +from scrapli.driver import NetworkDriver +from scrapli.driver.core.arista_eos.driver import EOS_ARG_MAPPER, EOSDriver +from scrapli.driver.core.cisco_iosxe.driver import IOSXE_ARG_MAPPER, IOSXEDriver +from scrapli.driver.core.cisco_iosxr.driver import IOSXR_ARG_MAPPER, IOSXRDriver +from scrapli.driver.core.cisco_nxos.driver import NXOS_ARG_MAPPER, NXOSDriver +from scrapli.driver.core.juniper_junos.driver import JUNOS_ARG_MAPPER, JunosDriver +from scrapli.helper import _textfsm_get_template, textfsm_parse -VALID_NSSH_KWARGS = { +VALID_SCRAPLI_KWARGS = { "host", "port", "auth_username", @@ -51,32 +51,32 @@ def send_command( 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 @@ -87,7 +87,7 @@ def connect_handler(auto_open: bool = True, **kwargs: Dict[str, Any]) -> Netmiko 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") @@ -101,7 +101,7 @@ def connect_handler(auto_open: bool = True, **kwargs: Dict[str, Any]) -> Netmiko 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) @@ -113,16 +113,16 @@ def connect_handler(auto_open: bool = True, **kwargs: Dict[str, Any]) -> Netmiko def transform_netmiko_kwargs(kwargs: Dict[str, Any]) -> Dict[str, Any]: """ - 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 + kwargs: netmiko-style ConnectHandler kwargs to transform to scrapli style Returns: transformed_kwargs: converted keyword arguments Raises: - N/A # noqa + N/A """ host = kwargs.pop("host", None) @@ -148,23 +148,24 @@ def transform_netmiko_kwargs(kwargs: Dict[str, Any]) -> Dict[str, Any]: 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() @@ -177,17 +178,19 @@ def netmiko_send_command( 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) @@ -200,7 +203,7 @@ def netmiko_send_command( 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` " @@ -244,18 +247,20 @@ def netmiko_send_command_timing( """ 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) @@ -267,18 +272,20 @@ def netmiko_send_config_set( """ 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) @@ -296,6 +303,6 @@ def netmiko_send_config_set( 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 diff --git a/nssh/result.py b/scrapli/response.py similarity index 65% rename from nssh/result.py rename to scrapli/response.py index ea0ee501..62019835 100644 --- a/nssh/result.py +++ b/scrapli/response.py @@ -1,20 +1,24 @@ -"""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. @@ -22,15 +26,17 @@ def __init__( 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 @@ -42,8 +48,9 @@ def __init__( self.elapsed_time: Optional[float] = None self.channel_input = channel_input + self.textfsm_platform = textfsm_platform self.expectation = expectation - self.response = response + self.channel_response = channel_response self.finale = finale self.raw_result: str = "" self.result: str = "" @@ -59,13 +66,13 @@ def __bool__(self) -> bool: Magic bool method based on channel_input being failed or not Args: - N/A # noqa + N/A Returns: bool: True/False if channel_input failed Raises: - N/A # noqa + N/A """ return self.failed @@ -75,34 +82,34 @@ def __repr__(self) -> str: Magic repr method for SSH2NetResponse class 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"SSH2NetResponse " + return f"Scrape " def __str__(self) -> str: """ Magic str method for SSH2NetResponse class Args: - N/A # noqa + N/A Returns: - N/A # noqa + str: str for class object Raises: - N/A # noqa + N/A """ - return f"SSH2NetResponse " + return f"Scrape " - def record_result(self, result: str) -> None: + def record_response(self, result: str) -> None: """ Record channel_input results and elapsed time of channel input/reading output @@ -110,10 +117,10 @@ def record_result(self, result: str) -> None: result: string result of channel_input Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.finish_time = datetime.now() @@ -121,3 +128,23 @@ def record_result(self, result: str) -> None: self.result = result # update failed to false after recording results self.failed = False + + def textfsm_parse_output(self) -> None: + """ + Parse results with textfsm, assign result to `structured_result` + + Args: + N/A + + Returns: + N/A # noqa: DAR202 + + Raises: + N/A + + """ + template = _textfsm_get_template(self.textfsm_platform, self.channel_input) + if isinstance(template, TextIOWrapper): + self.structured_result = textfsm_parse(template, self.result) + else: + self.structured_result = [] diff --git a/scrapli/transport/__init__.py b/scrapli/transport/__init__.py new file mode 100644 index 00000000..a6824e8c --- /dev/null +++ b/scrapli/transport/__init__.py @@ -0,0 +1,18 @@ +"""scrapli.transport""" +from scrapli.transport.cssh2 import SSH2_TRANSPORT_ARGS, SSH2Transport +from scrapli.transport.miko import MIKO_TRANSPORT_ARGS, MikoTransport +from scrapli.transport.systemssh import SYSTEM_SSH_TRANSPORT_ARGS, SystemSSHTransport +from scrapli.transport.telnet import TELNET_TRANSPORT_ARGS, TelnetTransport +from scrapli.transport.transport import Transport + +__all__ = ( + "Transport", + "MikoTransport", + "MIKO_TRANSPORT_ARGS", + "SSH2Transport", + "SSH2_TRANSPORT_ARGS", + "SystemSSHTransport", + "SYSTEM_SSH_TRANSPORT_ARGS", + "TELNET_TRANSPORT_ARGS", + "TelnetTransport", +) diff --git a/nssh/transport/cssh2.py b/scrapli/transport/cssh2.py similarity index 83% rename from nssh/transport/cssh2.py rename to scrapli/transport/cssh2.py index d1a47b1b..4c3aea1e 100644 --- a/nssh/transport/cssh2.py +++ b/scrapli/transport/cssh2.py @@ -1,12 +1,12 @@ -"""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") @@ -50,11 +50,10 @@ def __init__( 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 @@ -83,7 +82,7 @@ def __init__( 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) @@ -97,14 +96,14 @@ def open(self) -> None: 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(): @@ -124,7 +123,7 @@ def open(self) -> None: 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() @@ -133,13 +132,13 @@ def authenticate(self) -> None: 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: @@ -163,13 +162,13 @@ def _authenticate_public_key(self) -> None: 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: @@ -190,13 +189,13 @@ def _authenticate_password(self) -> None: 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: @@ -215,13 +214,13 @@ def _authenticate_keyboard_interactive(self) -> None: 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: @@ -250,13 +249,13 @@ def isauthenticated(self) -> bool: Check if session is authenticated Args: - N/A # noqa + N/A Returns: - authenticated: True if authenticated, else False + bool: True if authenticated, else False Raises: - N/A # noqa + N/A """ authenticated: bool = self.session.userauth_authenticated() @@ -267,13 +266,13 @@ def _open_channel(self) -> None: Open channel, acquire pty, request interactive shell Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.channel = self.session.open_session() @@ -286,13 +285,13 @@ def close(self) -> None: Close session and socket Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.session_lock.acquire() @@ -306,13 +305,13 @@ def isalive(self) -> bool: 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(): @@ -324,14 +323,13 @@ def read(self) -> bytes: 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 @@ -346,30 +344,14 @@ def write(self, channel_input: str) -> None: channel_input: string to send to channel Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.channel.write(channel_input) - def flush(self) -> None: - """ - Flush channel stdout stream - - Args: - N/A # noqa - - Returns: - N/A # noqa - - Raises: - N/A # noqa - - """ - self.channel.flush() - def set_timeout(self, timeout: Optional[int] = None) -> None: """ Set session timeout @@ -378,10 +360,10 @@ def set_timeout(self, timeout: Optional[int] = None) -> None: timeout: timeout in seconds Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ if isinstance(timeout, int): @@ -398,10 +380,10 @@ def set_blocking(self, blocking: bool = False) -> None: blocking: True/False set session to blocking Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.session.set_blocking(blocking) diff --git a/nssh/transport/miko.py b/scrapli/transport/miko.py similarity index 81% rename from nssh/transport/miko.py rename to scrapli/transport/miko.py index b83d4413..5d81daed 100644 --- a/nssh/transport/miko.py +++ b/scrapli/transport/miko.py @@ -1,13 +1,12 @@ -"""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") @@ -50,11 +49,10 @@ def __init__( 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 @@ -86,7 +84,7 @@ def __init__( 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) @@ -100,14 +98,14 @@ def open(self) -> None: 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(): @@ -126,7 +124,7 @@ def open(self) -> None: 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() @@ -135,13 +133,13 @@ def authenticate(self) -> None: 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: @@ -161,13 +159,13 @@ def _authenticate_public_key(self) -> None: 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: @@ -188,13 +186,13 @@ def _authenticate_password(self) -> None: 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: @@ -217,13 +215,13 @@ def isauthenticated(self) -> bool: Check if session is authenticated Args: - N/A # noqa + N/A Returns: - authenticated: True if authenticated, else False + bool: True if authenticated, else False Raises: - N/A # noqa + N/A """ authenticated: bool = self.session.is_authenticated() @@ -234,13 +232,13 @@ def _open_channel(self) -> None: Open channel, acquire pty, request interactive shell Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.channel = self.session.open_session() @@ -254,13 +252,13 @@ def close(self) -> None: Close session and socket Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.session_lock.acquire() @@ -274,13 +272,13 @@ def isalive(self) -> bool: 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(): @@ -292,14 +290,13 @@ def read(self) -> bytes: 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) @@ -313,35 +310,14 @@ def write(self, channel_input: str) -> None: channel_input: string to send to channel Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.channel.send(channel_input) - def flush(self) -> None: - """ - Flush channel stdout stream - - Args: - N/A # noqa - - Returns: - N/A # noqa - - Raises: - N/A # noqa - - """ - while True: - time.sleep(0.1) - if self.channel.recv_ready(): - self.read() - else: - return - def set_timeout(self, timeout: Optional[int] = None) -> None: """ Set session timeout @@ -350,10 +326,10 @@ def set_timeout(self, timeout: Optional[int] = None) -> None: timeout: timeout in seconds Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ if isinstance(timeout, int): @@ -370,10 +346,10 @@ def set_blocking(self, blocking: bool = False) -> None: blocking: True/False set session to blocking Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.channel.setblocking(blocking) diff --git a/nssh/transport/ptyprocess.py b/scrapli/transport/ptyprocess.py similarity index 100% rename from nssh/transport/ptyprocess.py rename to scrapli/transport/ptyprocess.py diff --git a/nssh/transport/socket.py b/scrapli/transport/socket.py similarity index 83% rename from nssh/transport/socket.py rename to scrapli/transport/socket.py index ec2fdef4..44010178 100644 --- a/nssh/transport/socket.py +++ b/scrapli/transport/socket.py @@ -1,9 +1,9 @@ -"""nssh.transport.socket""" +"""scrapli.transport.socket""" import logging import socket from typing import Optional -from nssh.exceptions import NSSHTimeout +from scrapli.exceptions import ScrapliTimeout LOG = logging.getLogger("transport") @@ -20,13 +20,13 @@ def __bool__(self) -> bool: 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.socket_isalive() @@ -36,13 +36,13 @@ def __str__(self) -> str: Magic str method for Socket Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ return f"Socket Object for host {self.host}" @@ -52,13 +52,13 @@ def __repr__(self) -> str: 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__}" @@ -68,13 +68,14 @@ def socket_open(self) -> None: 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(): @@ -91,7 +92,7 @@ def socket_open(self) -> None: ) 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") @@ -101,13 +102,13 @@ def socket_close(self) -> None: Close socket Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ if self.socket_isalive() and isinstance(self.sock, socket.socket): @@ -119,13 +120,13 @@ def socket_isalive(self) -> bool: 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: diff --git a/nssh/transport/systemssh.py b/scrapli/transport/systemssh.py similarity index 85% rename from nssh/transport/systemssh.py rename to scrapli/transport/systemssh.py index 732ffd37..625d8a1c 100644 --- a/nssh/transport/systemssh.py +++ b/scrapli/transport/systemssh.py @@ -1,4 +1,4 @@ -"""nssh.transport.systemssh""" +"""scrapli.transport.systemssh""" import re from logging import getLogger from select import select @@ -6,11 +6,11 @@ from threading import Lock from typing import TYPE_CHECKING, Optional, Union -from nssh.decorators import operation_timeout -from nssh.exceptions import NSSHAuthenticationFailed -from nssh.helper import get_prompt_pattern -from nssh.transport.ptyprocess import PtyProcess -from nssh.transport.transport import Transport +from scrapli.decorators import operation_timeout +from scrapli.exceptions import ScrapliAuthenticationFailed +from scrapli.helper import get_prompt_pattern +from scrapli.transport.ptyprocess import PtyProcess +from scrapli.transport.transport import Transport if TYPE_CHECKING: PopenBytes = Popen[bytes] # pylint: disable=E1136 @@ -28,6 +28,7 @@ "auth_public_key", "auth_password", "auth_strict_key", + "comms_prompt_pattern", "comms_return_char", "ssh_config_file", ) @@ -66,21 +67,21 @@ def __init__( 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 @@ -97,7 +98,7 @@ def __init__( self.ssh_config_file: Union[str, bool] = ssh_config_file self.session: Union[Popen[bytes], PtyProcess] # pylint: disable=E1136 - self.lib_auth_exception = NSSHAuthenticationFailed + self.lib_auth_exception = ScrapliAuthenticationFailed self._isauthenticated = False self.open_cmd = ["ssh", self.host] @@ -108,13 +109,13 @@ def _build_open_cmd(self) -> None: Method to craft command to open ssh session Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.open_cmd.extend(["-p", str(self.port)]) @@ -133,13 +134,13 @@ def open(self) -> None: Parent method to open session, authenticate and acquire shell Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - AuthenticationFailed: if all authentication means fail + ScrapliAuthenticationFailed: if all authentication means fail """ self.session_lock.acquire() @@ -154,20 +155,20 @@ def open(self) -> None: if not self._open_pty(): msg = f"Authentication to host {self.host} failed" LOG.critical(msg) - raise NSSHAuthenticationFailed(msg) + raise ScrapliAuthenticationFailed(msg) def _open_pipes(self) -> bool: """ Private method to open session with subprocess.Popen Args: - N/A # noqa + N/A Returns: bool: True/False session was opened and authenticated Raises: - N/A # noqa + N/A """ self.open_cmd.append("-v") @@ -198,7 +199,7 @@ def _pipes_isauthenticated(self, pipes_session: PopenBytes) -> bool: bool: True/False session was authenticated Raises: - N/A # noqa + N/A """ output = b"" @@ -213,13 +214,13 @@ def _open_pty(self) -> bool: Private method to open session with PtyProcess Args: - N/A # noqa + N/A Returns: bool: True/False session was opened and authenticated Raises: - N/A # noqa + N/A """ pty_session = PtyProcess.spawn(self.open_cmd) @@ -240,10 +241,11 @@ def _pty_authenticate(self, pty_session: PtyProcess) -> None: pty_session: PtyProcess session object Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + exc: if unknown (i.e. not auth failed) exception occurs + lib_auth_exception: if known auth exception occurs """ self.session_lock.acquire() @@ -253,7 +255,7 @@ def _pty_authenticate(self, pty_session: PtyProcess) -> None: 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: @@ -277,13 +279,13 @@ def _pty_isauthenticated(self, pty_session: PtyProcess) -> bool: 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(): @@ -311,13 +313,13 @@ def close(self) -> None: Close session and socket Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ self.session_lock.acquire() @@ -333,13 +335,13 @@ def isalive(self) -> bool: Check if session is alive and session is authenticated Args: - N/A # noqa + N/A Returns: bool: True if session is alive and session authenticated, else False Raises: - N/A # noqa + N/A """ if isinstance(self.session, Popen): @@ -355,14 +357,13 @@ def read(self) -> bytes: 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 """ read_bytes = 65535 @@ -380,10 +381,10 @@ def write(self, channel_input: str) -> None: channel_input: string to send to channel Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ if isinstance(self.session, Popen): @@ -391,26 +392,6 @@ def write(self, channel_input: str) -> None: elif isinstance(self.session, PtyProcess): self.session.write(channel_input.encode()) - def flush(self) -> None: - """ - Flush channel stdout stream - - Args: - N/A # noqa - - Returns: - N/A # noqa - - Raises: - N/A # noqa - - """ - if isinstance(self.session, Popen): - # flush seems to be unnecessary for Popen sessions - pass - elif isinstance(self.session, PtyProcess): - self.session.flush() - def set_timeout(self, timeout: Optional[int] = None) -> None: """ Set session timeout @@ -419,10 +400,10 @@ def set_timeout(self, timeout: Optional[int] = None) -> None: timeout: timeout in seconds Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -436,9 +417,9 @@ def set_blocking(self, blocking: bool = False) -> None: blocking: True/False set session to blocking Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ diff --git a/scrapli/transport/telnet.py b/scrapli/transport/telnet.py new file mode 100644 index 00000000..fe1c7ee1 --- /dev/null +++ b/scrapli/transport/telnet.py @@ -0,0 +1,340 @@ +"""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 + + """ diff --git a/nssh/transport/transport.py b/scrapli/transport/transport.py similarity index 71% rename from nssh/transport/transport.py rename to scrapli/transport/transport.py index 384fc4c5..734dc344 100644 --- a/nssh/transport/transport.py +++ b/scrapli/transport/transport.py @@ -1,4 +1,4 @@ -"""nssh.transport.transport""" +"""scrapli.transport.transport""" from abc import ABC, abstractmethod from threading import Lock from typing import Dict, Optional, Union @@ -15,13 +15,13 @@ def __bool__(self) -> bool: 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() @@ -31,13 +31,13 @@ def __str__(self) -> str: Magic str method for Transport Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ return f"Transport Object for host {self.host}" @@ -47,13 +47,13 @@ def __repr__(self) -> str: Magic repr method for Transport Args: - N/A # noqa + N/A Returns: - repr: repr for class object + str: repr for class object Raises: - N/A # noqa + N/A """ class_dict = self.__dict__.copy() @@ -66,13 +66,13 @@ def open(self) -> None: Open channel, acquire pty, request interactive shell Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -82,13 +82,13 @@ def close(self) -> None: Close session and socket Args: - N/A # noqa + N/A Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -98,13 +98,13 @@ def isalive(self) -> bool: 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 + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -114,13 +114,13 @@ def read(self) -> bytes: Read data from the channel Args: - N/A # noqa + N/A Returns: - output: bytes output as read from channel + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -133,26 +133,10 @@ def write(self, channel_input: str) -> None: channel_input: string to send to channel Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa - - """ - - @abstractmethod - def flush(self) -> None: - """ - Flush channel stdout stream - - Args: - N/A # noqa - - Returns: - N/A # noqa - - Raises: - N/A # noqa + N/A """ @@ -165,10 +149,10 @@ def set_timeout(self, timeout: Optional[int] = None) -> None: timeout: timeout in seconds Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ @@ -181,9 +165,9 @@ def set_blocking(self, blocking: bool = False) -> None: blocking: True/False set session to blocking Returns: - N/A # noqa + N/A # noqa: DAR202 Raises: - N/A # noqa + N/A """ diff --git a/setup.cfg b/setup.cfg index 900dcabe..eb336314 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,9 +1,14 @@ [coverage:run] -omit = nssh/transport/ptyprocess.py +omit = + scrapli/transport/ptyprocess.py + scrapli/driver/core/arista_eos/*.py + scrapli/driver/core/cisco_nxos/*.py + scrapli/driver/core/cisco_iosxr/*.py + scrapli/driver/core/juniper_junos/*.py [pylama] linters = mccabe,pycodestyle,pylint -skip = tests/*,.tox/*,venv/*,build/*,private/*,examples/*,nssh/transport/ptyprocess.py +skip = tests/*,.tox/*,venv/*,build/*,private/*,examples/*,scrapli/transport/ptyprocess.py [pylama:pycodestyle] max_line_length = 100 @@ -13,7 +18,7 @@ rcfile = .pylintrc [pydocstyle] ignore = D101,D202,D203,D212,D400,D406,D407,D408,D409,D415 -match-dir = ^nssh/* +match-dir = ^scrapli/* [isort] line_length = 100 @@ -32,5 +37,5 @@ warn_redundant_casts = True warn_unused_configs = True strict_optional = True -[mypy-nssh.transport.ptyprocess] +[mypy-scrapli.transport.ptyprocess] ignore_errors = True \ No newline at end of file diff --git a/setup.py b/setup.py index 40e7c79e..f05dac2b 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -"""nssh (network) ssh client library""" +"""scrapli - screen scraping client library""" import setuptools __author__ = "Carl Montanari" @@ -8,14 +8,14 @@ README = f.read() setuptools.setup( - name="nssh", - version="2020.02.02", + name="scrapli", + version="2020.02.09", author=__author__, author_email="carl.r.montanari@gmail.com", - description="SSH client focused on network devices", + description="Screen scraping client focused on network devices", long_description=README, long_description_content_type="text/markdown", - url="https://github.com/carlmontanari/nssh", + url="https://github.com/carlmontanari/scrapli", packages=setuptools.find_packages(), install_requires=[], extras_require={ @@ -32,5 +32,5 @@ "Operating System :: POSIX :: Linux", "Operating System :: MacOS", ], - python_requires=">=3.7", + python_requires=">=3.6", ) diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py index 14830db0..141178b1 100644 --- a/tests/functional/conftest.py +++ b/tests/functional/conftest.py @@ -1,8 +1,8 @@ import pytest -from nssh import NSSH -from nssh.driver import NetworkDriver -from nssh.driver.core import IOSXEDriver +from scrapli import Scrape +from scrapli.driver import NetworkDriver +from scrapli.driver.core import IOSXEDriver @pytest.fixture(scope="module") @@ -20,9 +20,9 @@ def _base_driver( comms_ansi=False, session_pre_login_handler="", session_disable_paging="terminal length 0", - driver="system", + transport="system", ): - conn = NSSH( + conn = Scrape( host=host, port=port, auth_username=auth_username, @@ -35,7 +35,7 @@ def _base_driver( comms_ansi=comms_ansi, session_pre_login_handler=session_pre_login_handler, session_disable_paging=session_disable_paging, - driver=driver, + transport=transport, ) conn.open() return conn @@ -58,7 +58,7 @@ def _network_driver( comms_ansi=False, session_pre_login_handler="", session_disable_paging="terminal length 0", - driver="system", + transport="system", ): conn = NetworkDriver( host=host, @@ -73,7 +73,7 @@ def _network_driver( comms_ansi=comms_ansi, session_pre_login_handler=session_pre_login_handler, session_disable_paging=session_disable_paging, - driver=driver, + transport=transport, ) conn.open() return conn @@ -96,7 +96,7 @@ def _cisco_iosxe_driver( comms_ansi=False, session_pre_login_handler="", session_disable_paging="terminal length 0", - driver="system", + transport="system", ): conn = IOSXEDriver( host=host, @@ -111,7 +111,7 @@ def _cisco_iosxe_driver( comms_ansi=comms_ansi, session_pre_login_handler=session_pre_login_handler, session_disable_paging=session_disable_paging, - driver=driver, + transport=transport, ) conn.open() return conn diff --git a/tests/functional/driver/core/cisco_iosxe/test_driver.py b/tests/functional/driver/core/cisco_iosxe/test_driver.py index 69ca123c..a9f197c7 100644 --- a/tests/functional/driver/core/cisco_iosxe/test_driver.py +++ b/tests/functional/driver/core/cisco_iosxe/test_driver.py @@ -4,11 +4,11 @@ import pytest -import nssh +import scrapli from .helper import clean_output_data -TEST_DATA_PATH = f"{Path(nssh.__file__).parents[1]}/tests/functional/test_data" +TEST_DATA_PATH = f"{Path(scrapli.__file__).parents[1]}/tests/functional/test_data" with open(f"{TEST_DATA_PATH}/devices/cisco_iosxe.json", "r") as f: CISCO_IOSXE_DEVICE = json.load(f) with open(f"{TEST_DATA_PATH}/test_cases/cisco_iosxe.json", "r") as f: @@ -24,16 +24,18 @@ ids=[n["name"] for n in CISCO_IOSXE_TEST_CASES["send_commands"]["tests"]], ) @pytest.mark.parametrize( - "driver", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] + "transport", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] ) -def test_send_commands(cisco_iosxe_driver, driver, test): - conn = cisco_iosxe_driver(**CISCO_IOSXE_DEVICE, driver=driver) +def test_send_commands(cisco_iosxe_driver, transport, test): + conn = cisco_iosxe_driver(**CISCO_IOSXE_DEVICE, transport=transport) + try_textfsm = test["kwargs"].pop("textfsm", None) results = conn.send_commands(test["inputs"], **test["kwargs"]) conn.close() for index, result in enumerate(results): cleaned_result = clean_output_data(test, result.result) assert cleaned_result == test["outputs"][index] - if test.get("textfsm", None): + if try_textfsm: + result.textfsm_parse_output() assert isinstance(result.structured_result, (list, dict)) @@ -43,11 +45,12 @@ def test_send_commands(cisco_iosxe_driver, driver, test): ids=[n["name"] for n in CISCO_IOSXE_TEST_CASES["send_configs"]["tests"]], ) @pytest.mark.parametrize( - "driver", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] + "transport", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] ) -def test_send_configs(cisco_iosxe_driver, driver, test): - conn = cisco_iosxe_driver(**CISCO_IOSXE_DEVICE, driver=driver) +def test_send_configs(cisco_iosxe_driver, transport, test): + conn = cisco_iosxe_driver(**CISCO_IOSXE_DEVICE, transport=transport) conn.send_configs(test["setup"], **test["kwargs"]) + conn.channel.get_prompt() verification_results = conn.send_commands(test["inputs"], **test["kwargs"]) conn.send_configs(test["teardown"], **test["kwargs"]) conn.close() @@ -59,10 +62,10 @@ def test_send_configs(cisco_iosxe_driver, driver, test): @pytest.mark.parametrize( - "driver", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] + "transport", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] ) -def test__acquire_priv_escalate(cisco_iosxe_driver, driver): - conn = cisco_iosxe_driver(**CISCO_IOSXE_DEVICE, driver=driver) +def test__acquire_priv_escalate(cisco_iosxe_driver, transport): + conn = cisco_iosxe_driver(**CISCO_IOSXE_DEVICE, transport=transport) conn.acquire_priv("configuration") current_priv = conn._determine_current_priv(conn.get_prompt()) conn.close() @@ -70,14 +73,14 @@ def test__acquire_priv_escalate(cisco_iosxe_driver, driver): @pytest.mark.parametrize( - "driver", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] + "transport", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] ) -def test__acquire_priv_deescalate(cisco_iosxe_driver, driver): - if driver == "ssh2": +def test__acquire_priv_deescalate(cisco_iosxe_driver, transport): + if transport == "ssh2": # seems to need a tiny bit of time so sockets arent tied up between tests?? # it only seemed to happen here... could be something to do w/ how many tests ran before? time.sleep(0.5) - conn = cisco_iosxe_driver(**CISCO_IOSXE_DEVICE, driver=driver) + conn = cisco_iosxe_driver(**CISCO_IOSXE_DEVICE, transport=transport) conn.acquire_priv("exec") current_priv = conn._determine_current_priv(conn.get_prompt()) conn.close() diff --git a/tests/functional/driver/test_driver.py b/tests/functional/driver/test_driver.py index 9afdde3c..abd4b29b 100644 --- a/tests/functional/driver/test_driver.py +++ b/tests/functional/driver/test_driver.py @@ -3,11 +3,11 @@ import pytest -import nssh +import scrapli from .core.cisco_iosxe.helper import clean_output_data -TEST_DATA_PATH = f"{Path(nssh.__file__).parents[1]}/tests/functional/test_data" +TEST_DATA_PATH = f"{Path(scrapli.__file__).parents[1]}/tests/functional/test_data" with open(f"{TEST_DATA_PATH}/devices/cisco_iosxe.json", "r") as f: CISCO_IOSXE_DEVICE = json.load(f) with open(f"{TEST_DATA_PATH}/test_cases/cisco_iosxe.json", "r") as f: @@ -18,10 +18,10 @@ @pytest.mark.parametrize( - "driver", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] + "transport", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] ) -def test_get_prompt(base_driver, driver): - conn = base_driver(**CISCO_IOSXE_DEVICE, driver=driver) +def test_get_prompt(base_driver, transport): + conn = base_driver(**CISCO_IOSXE_DEVICE, transport=transport) result = conn.channel.get_prompt() assert result == "csr1000v#" conn.close() @@ -33,10 +33,10 @@ def test_get_prompt(base_driver, driver): ids=[n["name"] for n in CISCO_IOSXE_TEST_CASES["channel.send_inputs"]["tests"]], ) @pytest.mark.parametrize( - "driver", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] + "transport", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] ) -def test_channel_send_inputs(base_driver, driver, test): - conn = base_driver(**CISCO_IOSXE_DEVICE, driver=driver) +def test_channel_send_inputs(base_driver, transport, test): + conn = base_driver(**CISCO_IOSXE_DEVICE, transport=transport) results = conn.channel.send_inputs(test["inputs"], **test["kwargs"]) for index, result in enumerate(results): cleaned_result = clean_output_data(test, result.result) @@ -50,10 +50,10 @@ def test_channel_send_inputs(base_driver, driver, test): ids=[n["name"] for n in CISCO_IOSXE_TEST_CASES["channel.send_inputs_interact"]["tests"]], ) @pytest.mark.parametrize( - "driver", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] + "transport", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] ) -def test_channel_send_inputs_interact(base_driver, driver, test): - conn = base_driver(**CISCO_IOSXE_DEVICE, driver=driver) +def test_channel_send_inputs_interact(base_driver, transport, test): + conn = base_driver(**CISCO_IOSXE_DEVICE, transport=transport) results = conn.channel.send_inputs_interact(test["inputs"]) cleaned_result = clean_output_data(test, results[0].result) assert cleaned_result == test["outputs"][0] diff --git a/tests/functional/driver/test_network_driver.py b/tests/functional/driver/test_network_driver.py index 0e80ec39..bc6983e5 100644 --- a/tests/functional/driver/test_network_driver.py +++ b/tests/functional/driver/test_network_driver.py @@ -3,12 +3,12 @@ import pytest -import nssh -from nssh.driver.core.cisco_iosxe.driver import PRIVS as CISCO_IOSXE_PRIVS +import scrapli +from scrapli.driver.core.cisco_iosxe.driver import PRIVS as CISCO_IOSXE_PRIVS from .core.cisco_iosxe.helper import clean_output_data -TEST_DATA_PATH = f"{Path(nssh.__file__).parents[1]}/tests/functional/test_data" +TEST_DATA_PATH = f"{Path(scrapli.__file__).parents[1]}/tests/functional/test_data" with open(f"{TEST_DATA_PATH}/devices/cisco_iosxe.json", "r") as f: CISCO_IOSXE_DEVICE = json.load(f) with open(f"{TEST_DATA_PATH}/test_cases/cisco_iosxe.json", "r") as f: @@ -19,10 +19,10 @@ @pytest.mark.parametrize( - "driver", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] + "transport", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] ) -def test_get_prompt(base_driver, driver): - conn = base_driver(**CISCO_IOSXE_DEVICE, driver=driver) +def test_get_prompt(base_driver, transport): + conn = base_driver(**CISCO_IOSXE_DEVICE, transport=transport) result = conn.channel.get_prompt() assert result == "csr1000v#" conn.close() @@ -34,10 +34,10 @@ def test_get_prompt(base_driver, driver): ids=[n["name"] for n in CISCO_IOSXE_TEST_CASES["channel.send_inputs"]["tests"]], ) @pytest.mark.parametrize( - "driver", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] + "transport", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] ) -def test_channel_send_inputs(base_driver, driver, test): - conn = base_driver(**CISCO_IOSXE_DEVICE, driver=driver) +def test_channel_send_inputs(base_driver, transport, test): + conn = base_driver(**CISCO_IOSXE_DEVICE, transport=transport) results = conn.channel.send_inputs(test["inputs"], **test["kwargs"]) for index, result in enumerate(results): cleaned_result = clean_output_data(test, result.result) @@ -51,10 +51,10 @@ def test_channel_send_inputs(base_driver, driver, test): ids=[n["name"] for n in CISCO_IOSXE_TEST_CASES["channel.send_inputs_interact"]["tests"]], ) @pytest.mark.parametrize( - "driver", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] + "transport", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] ) -def test_channel_send_inputs_interact(base_driver, driver, test): - conn = base_driver(**CISCO_IOSXE_DEVICE, driver=driver) +def test_channel_send_inputs_interact(base_driver, transport, test): + conn = base_driver(**CISCO_IOSXE_DEVICE, transport=transport) results = conn.channel.send_inputs_interact(test["inputs"]) cleaned_result = clean_output_data(test, results[0].result) assert cleaned_result == test["outputs"][0] @@ -67,18 +67,20 @@ def test_channel_send_inputs_interact(base_driver, driver, test): ids=[n["name"] for n in CISCO_IOSXE_TEST_CASES["send_commands"]["tests"]], ) @pytest.mark.parametrize( - "driver", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] + "transport", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] ) -def test_send_commands(network_driver, driver, test): - conn = network_driver(**CISCO_IOSXE_DEVICE, driver=driver) +def test_send_commands(network_driver, transport, test): + conn = network_driver(**CISCO_IOSXE_DEVICE, transport=transport) conn.default_desired_priv = "privilege_exec" conn.privs = CISCO_IOSXE_PRIVS + try_textfsm = test["kwargs"].pop("textfsm", None) results = conn.send_commands(test["inputs"], **test["kwargs"]) for index, result in enumerate(results): cleaned_result = clean_output_data(test, result.result) assert cleaned_result == test["outputs"][index] - if test.get("textfsm", None): + if try_textfsm: + result.textfsm_parse_output() assert isinstance(result.structured_result, (list, dict)) conn.close() @@ -89,10 +91,10 @@ def test_send_commands(network_driver, driver, test): ids=[n["name"] for n in CISCO_IOSXE_TEST_CASES["send_interactive"]["tests"]], ) @pytest.mark.parametrize( - "driver", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] + "transport", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] ) -def test_send_commands(network_driver, driver, test): - conn = network_driver(**CISCO_IOSXE_DEVICE, driver=driver) +def test_send_commands(network_driver, transport, test): + conn = network_driver(**CISCO_IOSXE_DEVICE, transport=transport) conn.default_desired_priv = "privilege_exec" conn.privs = CISCO_IOSXE_PRIVS results = conn.send_interactive(test["inputs"], **test["kwargs"]) @@ -106,10 +108,10 @@ def test_send_commands(network_driver, driver, test): @pytest.mark.parametrize( - "driver", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] + "transport", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] ) -def test__acquire_priv_escalate(network_driver, driver): - conn = network_driver(**CISCO_IOSXE_DEVICE, driver=driver) +def test__acquire_priv_escalate(network_driver, transport): + conn = network_driver(**CISCO_IOSXE_DEVICE, transport=transport) conn.default_desired_priv = "privilege_exec" conn.privs = CISCO_IOSXE_PRIVS conn.acquire_priv("configuration") @@ -119,10 +121,10 @@ def test__acquire_priv_escalate(network_driver, driver): @pytest.mark.parametrize( - "driver", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] + "transport", ["system", "ssh2", "paramiko"], ids=["system", "ssh2", "paramiko"] ) -def test__acquire_priv_deescalate(network_driver, driver): - conn = network_driver(**CISCO_IOSXE_DEVICE, driver=driver) +def test__acquire_priv_deescalate(network_driver, transport): + conn = network_driver(**CISCO_IOSXE_DEVICE, transport=transport) conn.default_desired_priv = "privilege_exec" conn.privs = CISCO_IOSXE_PRIVS conn.acquire_priv("exec") diff --git a/tests/functional/test_data/test_cases/cisco_iosxe.json b/tests/functional/test_data/test_cases/cisco_iosxe.json index 3613deeb..d56e4c94 100644 --- a/tests/functional/test_data/test_cases/cisco_iosxe.json +++ b/tests/functional/test_data/test_cases/cisco_iosxe.json @@ -48,7 +48,7 @@ ], "outputs": [ "", - "Building configuration...\nCurrent configuration : CONFIG_BYTES\n!\n! Last configuration change at TIME_STAMP_REPLACED\n!\nversion 16.4\nservice timestamps debug datetime msec\nservice timestamps log datetime msec\nno platform punt-keepalive disable-kernel-core\nplatform console serial\n!\nhostname csr1000v\n!\nboot-start-marker\nboot-end-marker\n!\n!\n!\nno aaa new-model\n!\n!\n!\n!\n!\n!\n!\n!\n!\n\n\n\nip domain name example.com\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\nsubscriber templating\n!\n!\n!\nmultilink bundle-name authenticated\n!\n!\n!\n!\n!\n!\n\n\n!\n!\n!\n!\n!\n!\n!\nlicense udi pid CSR1000V sn 9FKLJWM5EB0\ndiagnostic bootup level minimal\n!\nspanning-tree extend system-id\nnetconf-yang cisco-odm actions ACL\nnetconf-yang cisco-odm actions BGP\nnetconf-yang cisco-odm actions OSPF\nnetconf-yang cisco-odm actions Archive\nnetconf-yang cisco-odm actions IPRoute\nnetconf-yang cisco-odm actions EFPStats\nnetconf-yang cisco-odm actions IPSLAStats\nnetconf-yang cisco-odm actions Interfaces\nnetconf-yang cisco-odm actions Environment\nnetconf-yang cisco-odm actions FlowMonitor\nnetconf-yang cisco-odm actions MemoryStats\nnetconf-yang cisco-odm actions BFDNeighbors\nnetconf-yang cisco-odm actions BridgeDomain\nnetconf-yang cisco-odm actions CPUProcesses\nnetconf-yang cisco-odm actions LLDPNeighbors\nnetconf-yang cisco-odm actions VirtualService\nnetconf-yang cisco-odm actions MemoryProcesses\nnetconf-yang cisco-odm actions EthernetCFMStats\nnetconf-yang cisco-odm actions MPLSLDPNeighbors\nnetconf-yang cisco-odm actions PlatformSoftware\nnetconf-yang cisco-odm actions MPLSStaticBinding\nnetconf-yang cisco-odm actions MPLSForwardingTable\nnetconf-yang\n!\nrestconf\n!\nusername vrnetlab privilege 15 password 0 VR-netlab9\n!\nredundancy\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\ninterface GigabitEthernet1\n ip address 10.0.0.15 255.255.255.0\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet2\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet3\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet4\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet5\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet6\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet7\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet8\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet9\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet10\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\n!\nvirtual-service csr_mgmt\n!\nip forward-protocol nd\nno ip http server\nno ip http secure-server\n!\n!\n!\n!\n!\n!\n!\ncontrol-plane\n!\n !\n !\n !\n !\n!\n!\n!\n!\n!\nline con 0\n stopbits 1\nline vty 0\n login local\n transport input all\nline vty 1\n login local\n length 0\n transport input all\nline vty 2 4\n login local\n transport input all\nline vty 5 98\n login local\n transport input all\n!\n!\n!\n!\n!\n!\nend" + "Building configuration...\n\nCurrent configuration : CONFIG_BYTES\n!\n! Last configuration change at TIME_STAMP_REPLACED\n!\nversion 16.4\nservice timestamps debug datetime msec\nservice timestamps log datetime msec\nno platform punt-keepalive disable-kernel-core\nplatform console serial\n!\nhostname csr1000v\n!\nboot-start-marker\nboot-end-marker\n!\n!\n!\nno aaa new-model\n!\n!\n!\n!\n!\n!\n!\n!\n!\n\n\n\nip domain name example.com\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\nsubscriber templating\n!\n!\n!\nmultilink bundle-name authenticated\n!\n!\n!\n!\n!\n!\n\n\n!\n!\n!\n!\n!\n!\n!\nlicense udi pid CSR1000V sn 9FKLJWM5EB0\ndiagnostic bootup level minimal\n!\nspanning-tree extend system-id\nnetconf-yang cisco-odm actions ACL\nnetconf-yang cisco-odm actions BGP\nnetconf-yang cisco-odm actions OSPF\nnetconf-yang cisco-odm actions Archive\nnetconf-yang cisco-odm actions IPRoute\nnetconf-yang cisco-odm actions EFPStats\nnetconf-yang cisco-odm actions IPSLAStats\nnetconf-yang cisco-odm actions Interfaces\nnetconf-yang cisco-odm actions Environment\nnetconf-yang cisco-odm actions FlowMonitor\nnetconf-yang cisco-odm actions MemoryStats\nnetconf-yang cisco-odm actions BFDNeighbors\nnetconf-yang cisco-odm actions BridgeDomain\nnetconf-yang cisco-odm actions CPUProcesses\nnetconf-yang cisco-odm actions LLDPNeighbors\nnetconf-yang cisco-odm actions VirtualService\nnetconf-yang cisco-odm actions MemoryProcesses\nnetconf-yang cisco-odm actions EthernetCFMStats\nnetconf-yang cisco-odm actions MPLSLDPNeighbors\nnetconf-yang cisco-odm actions PlatformSoftware\nnetconf-yang cisco-odm actions MPLSStaticBinding\nnetconf-yang cisco-odm actions MPLSForwardingTable\nnetconf-yang\n!\nrestconf\n!\nusername vrnetlab privilege 15 password 0 VR-netlab9\n!\nredundancy\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\ninterface GigabitEthernet1\n ip address 10.0.0.15 255.255.255.0\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet2\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet3\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet4\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet5\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet6\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet7\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet8\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet9\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet10\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\n!\nvirtual-service csr_mgmt\n!\nip forward-protocol nd\nno ip http server\nno ip http secure-server\n!\n!\n!\n!\n!\n!\n!\ncontrol-plane\n!\n !\n !\n !\n !\n!\n!\n!\n!\n!\nline con 0\n stopbits 1\nline vty 0 4\n login local\n transport input all\nline vty 5 98\n login local\n transport input all\n!\n!\n!\n!\n!\n!\nend" ] } ] @@ -70,7 +70,7 @@ "csr1000v#" ], "outputs": [ - "Clear logging buffer [confirm]\ncsr1000v#" + "Clear logging buffer [confirm]\n\n\ncsr1000v#" ] } ] @@ -121,7 +121,7 @@ "show run" ], "outputs": [ - "Building configuration...\nCurrent configuration : CONFIG_BYTES\n!\n! Last configuration change at TIME_STAMP_REPLACED\n!\nversion 16.4\nservice timestamps debug datetime msec\nservice timestamps log datetime msec\nno platform punt-keepalive disable-kernel-core\nplatform console serial\n!\nhostname csr1000v\n!\nboot-start-marker\nboot-end-marker\n!\n!\n!\nno aaa new-model\n!\n!\n!\n!\n!\n!\n!\n!\n!\n\n\n\nip domain name example.com\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\nsubscriber templating\n!\n!\n!\nmultilink bundle-name authenticated\n!\n!\n!\n!\n!\n!\n\n\n!\n!\n!\n!\n!\n!\n!\nlicense udi pid CSR1000V sn 9FKLJWM5EB0\ndiagnostic bootup level minimal\n!\nspanning-tree extend system-id\nnetconf-yang cisco-odm actions ACL\nnetconf-yang cisco-odm actions BGP\nnetconf-yang cisco-odm actions OSPF\nnetconf-yang cisco-odm actions Archive\nnetconf-yang cisco-odm actions IPRoute\nnetconf-yang cisco-odm actions EFPStats\nnetconf-yang cisco-odm actions IPSLAStats\nnetconf-yang cisco-odm actions Interfaces\nnetconf-yang cisco-odm actions Environment\nnetconf-yang cisco-odm actions FlowMonitor\nnetconf-yang cisco-odm actions MemoryStats\nnetconf-yang cisco-odm actions BFDNeighbors\nnetconf-yang cisco-odm actions BridgeDomain\nnetconf-yang cisco-odm actions CPUProcesses\nnetconf-yang cisco-odm actions LLDPNeighbors\nnetconf-yang cisco-odm actions VirtualService\nnetconf-yang cisco-odm actions MemoryProcesses\nnetconf-yang cisco-odm actions EthernetCFMStats\nnetconf-yang cisco-odm actions MPLSLDPNeighbors\nnetconf-yang cisco-odm actions PlatformSoftware\nnetconf-yang cisco-odm actions MPLSStaticBinding\nnetconf-yang cisco-odm actions MPLSForwardingTable\nnetconf-yang\n!\nrestconf\n!\nusername vrnetlab privilege 15 password 0 VR-netlab9\n!\nredundancy\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\ninterface GigabitEthernet1\n ip address 10.0.0.15 255.255.255.0\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet2\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet3\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet4\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet5\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet6\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet7\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet8\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet9\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet10\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\n!\nvirtual-service csr_mgmt\n!\nip forward-protocol nd\nno ip http server\nno ip http secure-server\n!\n!\n!\n!\n!\n!\n!\ncontrol-plane\n!\n !\n !\n !\n !\n!\n!\n!\n!\n!\nline con 0\n stopbits 1\nline vty 0\n login local\n transport input all\nline vty 1\n login local\n length 0\n transport input all\nline vty 2 4\n login local\n transport input all\nline vty 5 98\n login local\n transport input all\n!\n!\n!\n!\n!\n!\nend" + "Building configuration...\n\nCurrent configuration : CONFIG_BYTES\n!\n! Last configuration change at TIME_STAMP_REPLACED\n!\nversion 16.4\nservice timestamps debug datetime msec\nservice timestamps log datetime msec\nno platform punt-keepalive disable-kernel-core\nplatform console serial\n!\nhostname csr1000v\n!\nboot-start-marker\nboot-end-marker\n!\n!\n!\nno aaa new-model\n!\n!\n!\n!\n!\n!\n!\n!\n!\n\n\n\nip domain name example.com\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\nsubscriber templating\n!\n!\n!\nmultilink bundle-name authenticated\n!\n!\n!\n!\n!\n!\n\n\n!\n!\n!\n!\n!\n!\n!\nlicense udi pid CSR1000V sn 9FKLJWM5EB0\ndiagnostic bootup level minimal\n!\nspanning-tree extend system-id\nnetconf-yang cisco-odm actions ACL\nnetconf-yang cisco-odm actions BGP\nnetconf-yang cisco-odm actions OSPF\nnetconf-yang cisco-odm actions Archive\nnetconf-yang cisco-odm actions IPRoute\nnetconf-yang cisco-odm actions EFPStats\nnetconf-yang cisco-odm actions IPSLAStats\nnetconf-yang cisco-odm actions Interfaces\nnetconf-yang cisco-odm actions Environment\nnetconf-yang cisco-odm actions FlowMonitor\nnetconf-yang cisco-odm actions MemoryStats\nnetconf-yang cisco-odm actions BFDNeighbors\nnetconf-yang cisco-odm actions BridgeDomain\nnetconf-yang cisco-odm actions CPUProcesses\nnetconf-yang cisco-odm actions LLDPNeighbors\nnetconf-yang cisco-odm actions VirtualService\nnetconf-yang cisco-odm actions MemoryProcesses\nnetconf-yang cisco-odm actions EthernetCFMStats\nnetconf-yang cisco-odm actions MPLSLDPNeighbors\nnetconf-yang cisco-odm actions PlatformSoftware\nnetconf-yang cisco-odm actions MPLSStaticBinding\nnetconf-yang cisco-odm actions MPLSForwardingTable\nnetconf-yang\n!\nrestconf\n!\nusername vrnetlab privilege 15 password 0 VR-netlab9\n!\nredundancy\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\n!\ninterface GigabitEthernet1\n ip address 10.0.0.15 255.255.255.0\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet2\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet3\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet4\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet5\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet6\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet7\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet8\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet9\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\ninterface GigabitEthernet10\n no ip address\n shutdown\n negotiation auto\n no mop enabled\n no mop sysid\n!\n!\nvirtual-service csr_mgmt\n!\nip forward-protocol nd\nno ip http server\nno ip http secure-server\n!\n!\n!\n!\n!\n!\n!\ncontrol-plane\n!\n !\n !\n !\n !\n!\n!\n!\n!\n!\nline con 0\n stopbits 1\nline vty 0 4\n login local\n transport input all\nline vty 5 98\n login local\n transport input all\n!\n!\n!\n!\n!\n!\nend" ] }, { @@ -158,7 +158,7 @@ "csr1000v#" ], "outputs": [ - "Clear logging buffer [confirm]\ncsr1000v#" + "Clear logging buffer [confirm]\n\n\ncsr1000v#" ] } ] @@ -168,7 +168,7 @@ { "name": "send configs", "notes": "test basic configuration", - "replace_bytes": false, + "replace_bytes": true, "replace_timestamps": false, "replace_cfg_by": false, "replace_crypto": false, @@ -181,7 +181,7 @@ "show run int loopback123" ], "outputs": [ - "Building configuration...\nCurrent configuration : 71 bytes\n!\ninterface Loopback123\n description nssh was here\n no ip address\nend" + "Building configuration...\n\nCurrent configuration : CONFIG_BYTES\n!\ninterface Loopback123\n description nssh was here\n no ip address\nend" ], "teardown": [ "no interface loopback123" diff --git a/tests/unit/channel/test_channel.py b/tests/unit/channel/test_channel.py index 8f30a4b9..c9590d4d 100644 --- a/tests/unit/channel/test_channel.py +++ b/tests/unit/channel/test_channel.py @@ -3,14 +3,14 @@ def test__str(mocked_channel): conn = mocked_channel([]) - assert str(conn.channel) == "nssh Channel Object" + assert str(conn.channel) == "scrapli Channel Object" def test__repr(mocked_channel): conn = mocked_channel([]) assert ( repr(conn.channel) - == r"nssh Channel {'comms_prompt_pattern': '^[a-z0-9.\\-@()/:]{1,32}[#>$]$', 'comms_return_char': '\n', 'comms_ansi': False, 'timeout_ops': 10}" + == r"scrapli Channel {'comms_prompt_pattern': '^[a-z0-9.\\-@()/:]{1,32}[#>$]$', 'comms_return_char': '\n', 'comms_ansi': False, 'timeout_ops': 10}" ) @@ -87,8 +87,8 @@ def test__send_input(mocked_channel): test_operations = [(channel_input_1, channel_output_1)] conn = mocked_channel(test_operations) output, processed_output = conn.channel._send_input(channel_input_1, strip_prompt=False) - assert output == channel_output_1.encode() - assert processed_output.decode() == channel_output_1 + assert output.lstrip() == channel_output_1.encode() + assert processed_output.lstrip().decode() == channel_output_1 def test_send_inputs(mocked_channel): @@ -149,7 +149,7 @@ def test_send_inputs_interact(mocked_channel): test_operations = [(interact[0], interact[1]), (interact[2], interact[3])] conn = mocked_channel(test_operations) output = conn.channel.send_inputs_interact(interact, hidden_response=False) - assert output[0].result == "Clear logging buffer [confirm]\n3560CX#" + assert output[0].result == "Clear logging buffer [confirm]\n\n3560CX#" def test_send_inputs_interact_invalid_input(mocked_channel): diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 1475c991..be616aad 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -5,19 +5,19 @@ import pytest -from nssh.channel import Channel -from nssh.driver import NSSH, NetworkDriver -from nssh.driver.core.cisco_iosxe.driver import PRIVS -from nssh.netmiko_compatability import ( +from scrapli.channel import Channel +from scrapli.driver import NetworkDriver, Scrape +from scrapli.driver.core.cisco_iosxe.driver import PRIVS +from scrapli.netmiko_compatability import ( netmiko_find_prompt, netmiko_send_command, netmiko_send_command_timing, netmiko_send_config_set, ) -from nssh.transport.transport import Transport +from scrapli.transport.transport import Transport -class MockNSSH(NSSH): +class MockNSSH(Scrape): def __init__(self, channel_ops, initial_bytes, *args, comms_ansi=False, **kwargs): self.channel_ops = channel_ops self.initial_bytes = initial_bytes diff --git a/tests/unit/driver/core/cisco_iosxe/test_driver.py b/tests/unit/driver/core/cisco_iosxe/test_driver.py index 3674a794..f5992ae5 100644 --- a/tests/unit/driver/core/cisco_iosxe/test_driver.py +++ b/tests/unit/driver/core/cisco_iosxe/test_driver.py @@ -1,4 +1,4 @@ -from nssh.driver.core.cisco_iosxe.driver import IOSXEDriver +from scrapli.driver.core.cisco_iosxe.driver import IOSXEDriver def test_init(): diff --git a/tests/unit/driver/test_driver.py b/tests/unit/driver/test_driver.py index 2f5ce292..c31e029e 100644 --- a/tests/unit/driver/test_driver.py +++ b/tests/unit/driver/test_driver.py @@ -2,127 +2,127 @@ import pytest -from nssh import NSSH -from nssh.transport import MikoTransport, SSH2Transport, SystemSSHTransport +from scrapli import Scrape +from scrapli.transport import MikoTransport, SSH2Transport, SystemSSHTransport def test__str(): - conn = NSSH(host="myhost") - assert str(conn) == "NSSH Object for host myhost" + conn = Scrape(host="myhost") + assert str(conn) == "Scrape Object for host myhost" def test__repr(): - conn = NSSH(host="myhost") + conn = Scrape(host="myhost") assert ( repr(conn) - == "NSSH {'host': 'myhost', 'port': 22, 'auth_username': '', 'auth_password': '********', 'auth_strict_key': True, 'auth_public_key': b'', 'timeout_socket': 5, 'timeout_ssh': 5000, 'timeout_ops': 10, 'comms_prompt_pattern': '^[a-z0-9.\\\\-@()/:]{1,32}[#>$]$', 'comms_return_char': '\\n', 'comms_ansi': False, 'session_pre_login_handler': None, 'session_disable_paging': 'terminal length 0', 'ssh_config_file': True, 'transport_class': , 'transport_args': {'host': 'myhost', 'port': 22, 'timeout_socket': 5, 'timeout_ssh': 5000, 'auth_username': '', 'auth_public_key': b'', 'auth_password': '', 'auth_strict_key': True, 'comms_return_char': '\\n', 'ssh_config_file': True}, 'channel_args': {'comms_prompt_pattern': '^[a-z0-9.\\\\-@()/:]{1,32}[#>$]$', 'comms_return_char': '\\n', 'comms_ansi': False, 'timeout_ops': 10}}" + == "Scrape {'host': 'myhost', 'port': 22, 'auth_username': '', 'auth_password': '********', 'auth_strict_key': True, 'auth_public_key': b'', 'timeout_socket': 5, 'timeout_ssh': 5000, 'timeout_ops': 10, 'comms_prompt_pattern': '^[a-z0-9.\\\\-@()/:]{1,32}[#>$]$', 'comms_return_char': '\\n', 'comms_ansi': False, 'session_pre_login_handler': None, 'session_disable_paging': 'terminal length 0', 'ssh_config_file': True, 'transport_class': , 'transport_args': {'host': 'myhost', 'port': 22, 'timeout_socket': 5, 'timeout_ssh': 5000, 'auth_username': '', 'auth_public_key': b'', 'auth_password': '', 'auth_strict_key': True, 'comms_prompt_pattern': '^[a-z0-9.\\\\-@()/:]{1,32}[#>$]$', 'comms_return_char': '\\n', 'ssh_config_file': True}, 'channel_args': {'comms_prompt_pattern': '^[a-z0-9.\\\\-@()/:]{1,32}[#>$]$', 'comms_return_char': '\\n', 'comms_ansi': False, 'timeout_ops': 10}}" ) def test_host(): - conn = NSSH(host="myhost") + conn = Scrape(host="myhost") assert conn.host == "myhost" def test_host_strip(): - conn = NSSH(host=" whitespace ") + conn = Scrape(host=" whitespace ") assert conn.host == "whitespace" def test_port(): - conn = NSSH(port=123) + conn = Scrape(port=123) assert conn.port == 123 def test_port_invalid(): with pytest.raises(TypeError) as e: - NSSH(port="notint") + Scrape(port="notint") assert str(e.value) == "port should be int, got " def test_user(): - conn = NSSH(auth_username="nssh") - assert conn.auth_username == "nssh" + conn = Scrape(auth_username="scrapli") + assert conn.auth_username == "scrapli" def test_user_strip(): - conn = NSSH(auth_username=" nssh ") - assert conn.auth_username == "nssh" + conn = Scrape(auth_username=" scrapli ") + assert conn.auth_username == "scrapli" def test_user_invalid(): with pytest.raises(AttributeError): - NSSH(auth_username=1234) + Scrape(auth_username=1234) def test_system_driver(): - conn = NSSH() + conn = Scrape() assert conn.transport_class == SystemSSHTransport def test_ssh2_driver(): - conn = NSSH(driver="ssh2") + conn = Scrape(transport="ssh2") assert conn.transport_class == SSH2Transport def test_paramiko_driver(): - conn = NSSH(driver="paramiko") + conn = Scrape(transport="paramiko") assert conn.transport_class == MikoTransport def test_invalid_driver(): with pytest.raises(ValueError) as e: - NSSH(driver="notreal") - assert str(e.value) == "transport should be one of ssh2|paramiko|system, got notreal" + Scrape(transport="notreal") + assert str(e.value) == "transport should be one of ssh2|paramiko|system|telnet, got notreal" def test_auth_ssh_key_strip(): - conn = NSSH(auth_public_key="~/some_neat_path ") + conn = Scrape(auth_public_key="~/some_neat_path ") user_path = os.path.expanduser("~/") assert conn.auth_public_key == f"{user_path}some_neat_path".encode() def test_auth_password_strip(): - conn = NSSH(auth_password=" password ") + conn = Scrape(auth_password=" password ") assert conn.auth_password == "password" def test_auth_strict_key_invalid(): with pytest.raises(TypeError) as e: - NSSH(auth_strict_key="notreal") + Scrape(auth_strict_key="notreal") assert str(e.value) == "auth_strict_key should be bool, got " def test_valid_comms_return_char(): - conn = NSSH(comms_return_char="\rn") + conn = Scrape(comms_return_char="\rn") assert conn.comms_return_char == "\rn" def test_invalid_comms_return_char(): with pytest.raises(TypeError) as e: - NSSH(comms_return_char=False) + Scrape(comms_return_char=False) assert str(e.value) == "comms_return_char should be str, got " def test_valid_comms_ansi(): - conn = NSSH(comms_ansi=True) + conn = Scrape(comms_ansi=True) assert conn.comms_ansi is True def test_invalid_comms_ansi(): with pytest.raises(TypeError) as e: - NSSH(comms_ansi=123) + Scrape(comms_ansi=123) assert str(e.value) == "comms_ansi should be bool, got " def test_valid_comms_prompt_pattern(): - conn = NSSH(comms_prompt_pattern="somestr") + conn = Scrape(comms_prompt_pattern="somestr") assert conn.comms_prompt_pattern == "somestr" def test_invalid_comms_prompt_pattern(): with pytest.raises(TypeError): - NSSH(comms_prompt_pattern=123) + Scrape(comms_prompt_pattern=123) def test_valid_session_pre_login_handler_func(): @@ -130,12 +130,12 @@ def pre_login_handler_func(): pass login_handler = pre_login_handler_func - conn = NSSH(session_pre_login_handler=login_handler) + conn = Scrape(session_pre_login_handler=login_handler) assert callable(conn.session_pre_login_handler) def test_valid_session_pre_login_handler_ext_func(): - conn = NSSH( + conn = Scrape( session_pre_login_handler="tests.unit.driver.ext_test_funcs.some_pre_login_handler_func" ) assert callable(conn.session_pre_login_handler) @@ -143,7 +143,7 @@ def test_valid_session_pre_login_handler_ext_func(): def test_invalid_session_pre_login_handler(): with pytest.raises(TypeError) as e: - NSSH(session_pre_login_handler="not.valid.func") + Scrape(session_pre_login_handler="not.valid.func") assert ( str(e.value) == "not.valid.func is an invalid session_pre_login_handler function or path to a function." @@ -151,7 +151,7 @@ def test_invalid_session_pre_login_handler(): def test_valid_session_disable_paging_default(): - conn = NSSH() + conn = Scrape() assert conn.session_disable_paging == "terminal length 0" @@ -160,28 +160,30 @@ def disable_paging_func(): pass disable_paging = disable_paging_func - conn = NSSH(session_disable_paging=disable_paging) + conn = Scrape(session_disable_paging=disable_paging) assert callable(conn.session_disable_paging) def test_valid_session_disable_paging_ext_func(): - conn = NSSH(session_disable_paging="tests.unit.driver.ext_test_funcs.some_disable_paging_func") + conn = Scrape( + session_disable_paging="tests.unit.driver.ext_test_funcs.some_disable_paging_func" + ) assert callable(conn.session_disable_paging) def test_valid_session_disable_paging_str(): - conn = NSSH(session_disable_paging="disable all the paging") + conn = Scrape(session_disable_paging="disable all the paging") assert conn.session_disable_paging == "disable all the paging" def test_invalid_session_disable_paging_func(): - conn = NSSH(session_disable_paging="not.valid.func") + conn = Scrape(session_disable_paging="not.valid.func") assert conn.session_disable_paging == "not.valid.func" def test_invalid_session_disable_paging(): with pytest.raises(TypeError) as e: - NSSH(session_disable_paging=123) + Scrape(session_disable_paging=123) assert str(e.value) == "session_disable_paging should be str or callable, got " diff --git a/tests/unit/driver/test_network_driver.py b/tests/unit/driver/test_network_driver.py index f8b2c761..aace6708 100644 --- a/tests/unit/driver/test_network_driver.py +++ b/tests/unit/driver/test_network_driver.py @@ -2,8 +2,8 @@ import pytest -from nssh.driver.network_driver import PrivilegeLevel -from nssh.exceptions import CouldNotAcquirePrivLevel, UnknownPrivLevel +from scrapli.driver.network_driver import PrivilegeLevel +from scrapli.exceptions import CouldNotAcquirePrivLevel, UnknownPrivLevel try: import ntc_templates @@ -26,28 +26,6 @@ def test__determine_current_priv_unknown(mocked_network_driver): conn._determine_current_priv("!!!!thisissoooowrongggg!!!!!!?!") -@pytest.mark.skipif(textfsm_avail is False, reason="textfsm and/or ntc_templates are not installed") -def test_textfsm_parse_output(mocked_network_driver): - conn = mocked_network_driver([]) - conn.textfsm_platform = "cisco_ios" - channel_output = """Protocol Address Age (min) Hardware Addr Type Interface -Internet 172.31.254.1 - 0000.0c07.acfe ARPA Vlan254 -Internet 172.31.254.2 - c800.84b2.e9c2 ARPA Vlan254 -""" - result = conn.textfsm_parse_output("show ip arp", channel_output) - assert isinstance(result, list) - assert result[0] == ["Internet", "172.31.254.1", "-", "0000.0c07.acfe", "ARPA", "Vlan254"] - - -@pytest.mark.skipif(textfsm_avail is False, reason="textfsm and/or ntc_templates are not installed") -def test_textfsm_parse_output_failure(mocked_network_driver): - conn = mocked_network_driver([]) - conn.textfsm_platform = "cisco_ios" - channel_output = """Protocol Address Blahblhablah""" - result = conn.textfsm_parse_output("show ip arp", channel_output) - assert result == {} - - def test__escalate(mocked_network_driver): channel_input_1 = "\n" channel_output_1 = "\n3560CX#" @@ -344,7 +322,7 @@ def test_send_inputs_interact(mocked_network_driver): conn = mocked_network_driver(test_operations) conn.default_desired_priv = "privilege_exec" output = conn.send_interactive(interact, hidden_response=False) - assert output[0].result == "Clear logging buffer [confirm]\n3560CX#" + assert output[0].result == "Clear logging buffer [confirm]\n\n3560CX#" def test_send_configs(mocked_network_driver): @@ -384,9 +362,6 @@ def test_send_configs(mocked_network_driver): assert output[0].result == channel_output_5 -# TODO -- add textfsm parsing test - - @pytest.mark.skipif(textfsm_avail is False, reason="textfsm and/or ntc_templates are not installed") def test_send_inputs_textfsm_success(mocked_network_driver): channel_input_1 = "\n" @@ -463,6 +438,89 @@ def test_send_inputs_textfsm_success(mocked_network_driver): conn = mocked_network_driver(test_operations) conn.default_desired_priv = "privilege_exec" conn.textfsm_platform = "cisco_ios" - results = conn.send_commands(channel_input_2, textfsm=True) + results = conn.send_commands(channel_input_2) + results[0].textfsm_parse_output() assert isinstance(results[0].structured_result, list) assert results[0].structured_result[0][0] == "15.2(4)E7" + + +@pytest.mark.skipif(textfsm_avail is False, reason="textfsm and/or ntc_templates are not installed") +def test_send_inputs_textfsm_fail(mocked_network_driver): + channel_input_1 = "\n" + channel_output_1 = "\n3560CX#" + channel_input_2 = "show version" + channel_output_2 = """Cisco IOS Software, C3560CX Software (C3560CX-UNIVERSALK9-M), Version 15.2(4)E7, RELEASE SOFTWARE (fc2) +Technical Support: http://www.cisco.com/techsupport +Copyright (c) 1986-2018 by Cisco Systems, Inc. +Compiled Tue 18-Sep-18 13:20 by prod_rel_team + +ROM: Bootstrap program is C3560CX boot loader +BOOTLDR: C3560CX Boot Loader (C3560CX-HBOOT-M) Version 15.2(4r)E5, RELEASE SOFTWARE (fc4) + +3560CX uptime is 2 weeks, 3 days, 18 hours, 3 minutes +System returned to ROM by power-on +System restarted at 15:59:35 PST Wed Jan 15 2020 +System image file is "flash:c3560cx-universalk9-mz.152-4.E7.bin" +Last reload reason: power-on + + + +This product contains cryptographic features and is subject to United +States and local country laws governing import, export, transfer and +use. Delivery of Cisco cryptographic products does not imply +third-party authority to import, export, distribute or use encryption. +Importers, exporters, distributors and users are responsible for +compliance with U.S. and local country laws. By using this product you +agree to comply with applicable laws and regulations. If you are unable +to comply with U.S. and local laws, return this product immediately. + +A summary of U.S. laws governing Cisco cryptographic products may be found at: +http://www.cisco.com/wwl/export/crypto/tool/stqrg.html + +If you require further assistance please contact us by sending email to +export@cisco.com. + +License Level: ipservices +License Type: Permanent Right-To-Use +Next reload license Level: ipservices + +cisco WS-C3560CX-8PC-S (APM86XXX) processor (revision A0) with 524288K bytes of memory. +Processor board ID FOCXXXXXXXX +Last reset from power-on +5 Virtual Ethernet interfaces +12 Gigabit Ethernet interfaces +The password-recovery mechanism is enabled. + +512K bytes of flash-simulated non-volatile configuration memory. +Base ethernet MAC Address : FF:FF:FF:FF:FF:FF +Motherboard assembly number : XX-XXXXX-XX +Power supply part number : XXX-XXXX-XX +Motherboard serial number : FOCXXXXXXXX +Power supply serial number : FOCXXXXXXXX +Model revision number : A0 +Motherboard revision number : A0 +Model number : WS-C3560CX-8PC-S +System serial number : FOCXXXXXXXX +Top Assembly Part Number : XX-XXXX-XX +Top Assembly Revision Number : A0 +Version ID : V01 +CLEI Code Number : XXXXXXXXXX +Hardware Board Revision Number : 0x02 + + +Switch Ports Model SW Version SW Image +------ ----- ----- ---------- ---------- +* 1 12 WS-C3560CX-8PC-S 15.2(4)E7 C3560CX-UNIVERSALK9-M + + +Configuration register is 0xF + +3560CX#""" + test_operations = [(channel_input_1, channel_output_1), (channel_input_2, channel_output_2)] + conn = mocked_network_driver(test_operations) + conn.default_desired_priv = "privilege_exec" + conn.textfsm_platform = "not_real" + results = conn.send_commands(channel_input_2) + results[0].textfsm_parse_output() + assert isinstance(results[0].structured_result, list) + assert results[0].structured_result == [] diff --git a/tests/unit/test_decorators.py b/tests/unit/test_decorators.py index 85976427..20cfda9b 100644 --- a/tests/unit/test_decorators.py +++ b/tests/unit/test_decorators.py @@ -2,7 +2,7 @@ import pytest -from nssh.decorators import operation_timeout +from scrapli.decorators import operation_timeout class SlowClass: diff --git a/tests/unit/test_helper.py b/tests/unit/test_helper.py index e7856793..9f545499 100644 --- a/tests/unit/test_helper.py +++ b/tests/unit/test_helper.py @@ -5,7 +5,7 @@ import pkg_resources # pylint: disable=C041 import pytest -from nssh.helper import _textfsm_get_template, get_prompt_pattern, strip_ansi, textfsm_parse +from scrapli.helper import _textfsm_get_template, get_prompt_pattern, strip_ansi, textfsm_parse IOS_ARP = """Protocol Address Age (min) Hardware Addr Type Interface Internet 172.31.254.1 - 0000.0c07.acfe ARPA Vlan254 diff --git a/tests/unit/test_netmiko_compatability.py b/tests/unit/test_netmiko_compatability.py index f59de548..25af553c 100644 --- a/tests/unit/test_netmiko_compatability.py +++ b/tests/unit/test_netmiko_compatability.py @@ -1,7 +1,7 @@ import pkg_resources # pylint: disable=C0411 import pytest -from nssh.netmiko_compatability import connect_handler, transform_netmiko_kwargs +from scrapli.netmiko_compatability import connect_handler, transform_netmiko_kwargs try: import ntc_templates @@ -216,9 +216,9 @@ def test_netmiko_send_command_expect_string(mocked_netmiko_driver): assert ( record[0].message.args[0] == """ -***** nssh netmiko interoperability does not support expect_string! ******************* +***** scrapli netmiko interoperability does not support expect_string! **************** To resolve this issue, use native or driver mode with `send_inputs_interact` method. -***** nssh netmiko interoperability does not support expect_string! *******************""" +***** scrapli netmiko interoperability does not support expect_string! ****************""" ) assert result == channel_output_2 diff --git a/tests/unit/test_response.py b/tests/unit/test_response.py new file mode 100644 index 00000000..e38c329d --- /dev/null +++ b/tests/unit/test_response.py @@ -0,0 +1,30 @@ +from datetime import datetime + +from scrapli.response import Response + + +def test_response_init(): + response = Response("localhost", "ls -al") + response_start_time = str(datetime.now())[:-7] + assert response.host == "localhost" + assert response.channel_input == "ls -al" + assert str(response.start_time)[:-7] == response_start_time + assert response.failed is True + assert bool(response) is True + assert repr(response) == "Scrape " + assert str(response) == "Scrape " + + +def test_response_record_result(): + response = Response("localhost", "ls -al") + response_end_time = str(datetime.now())[:-7] + response_str = """ +ls -al +total 264 +drwxr-xr-x 34 carl staff 1088 Jan 27 19:07 ./ +drwxr-xr-x 21 carl staff 672 Jan 25 15:56 ../ +-rw-r--r-- 1 carl staff 53248 Jan 27 19:07 .coverage +drwxr-xr-x 12 carl staff 384 Jan 27 19:13 .git/""" + response.record_response(response_str) + assert str(response.finish_time)[:-7] == response_end_time + assert response.result == response_str diff --git a/tests/unit/test_result.py b/tests/unit/test_result.py deleted file mode 100644 index a0e8e68b..00000000 --- a/tests/unit/test_result.py +++ /dev/null @@ -1,30 +0,0 @@ -from datetime import datetime - -from nssh.result import Result - - -def test_result_init(): - result = Result("localhost", "ls -al") - result_start_time = str(datetime.now())[:-7] - assert result.host == "localhost" - assert result.channel_input == "ls -al" - assert str(result.start_time)[:-7] == result_start_time - assert result.failed is True - assert bool(result) is True - assert repr(result) == "SSH2NetResponse " - assert str(result) == "SSH2NetResponse " - - -def test_result_record_result(): - result = Result("localhost", "ls -al") - result_end_time = str(datetime.now())[:-7] - result_str = """ -ls -al -total 264 -drwxr-xr-x 34 carl staff 1088 Jan 27 19:07 ./ -drwxr-xr-x 21 carl staff 672 Jan 25 15:56 ../ --rw-r--r-- 1 carl staff 53248 Jan 27 19:07 .coverage -drwxr-xr-x 12 carl staff 384 Jan 27 19:13 .git/""" - result.record_result(result_str) - assert str(result.finish_time)[:-7] == result_end_time - assert result.result == result_str diff --git a/tests/unit/transport/systemssh.py b/tests/unit/transport/systemssh.py index e7f2c484..3eb3e64d 100644 --- a/tests/unit/transport/systemssh.py +++ b/tests/unit/transport/systemssh.py @@ -1,6 +1,6 @@ import pytest -from nssh.transport import SystemSSHTransport +from scrapli.transport import SystemSSHTransport def test_creation(): diff --git a/tests/unit/transport/test_socket.py b/tests/unit/transport/test_socket.py index 28bc3855..db66f36b 100644 --- a/tests/unit/transport/test_socket.py +++ b/tests/unit/transport/test_socket.py @@ -1,7 +1,7 @@ import pytest -from nssh.exceptions import NSSHTimeout -from nssh.transport.socket import Socket +from scrapli.exceptions import ScrapliTimeout +from scrapli.transport.socket import Socket def test_socket_open_success(): @@ -19,7 +19,7 @@ def test_socket_open_failure_connection_refused(): def test_socket_open_failure_timeout(): sock = Socket("240.0.0.1", 22, 0.1) - with pytest.raises(NSSHTimeout) as e: + with pytest.raises(ScrapliTimeout) as e: sock.socket_open() assert str(e.value) == "Timed out trying to open socket to 240.0.0.1 on port 22" diff --git a/tests/unit/transport/test_transport.py b/tests/unit/transport/test_transport.py index a23353d4..4e8ae676 100644 --- a/tests/unit/transport/test_transport.py +++ b/tests/unit/transport/test_transport.py @@ -1,6 +1,6 @@ from typing import Optional -from nssh.transport import Transport +from scrapli.transport import Transport class MockTransport(Transport): diff --git a/tox.ini b/tox.ini index c9c24d1e..706c1e4c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py37,py38 +envlist = py36,py37,py38 [testenv] deps = -rrequirements-dev.txt @@ -10,7 +10,7 @@ whitelist_externals = bash deps = -rrequirements-dev.txt commands = python -m pytest \ - --cov=nssh \ + --cov=scrapli \ --cov-report html \ --cov-report term \ tests/unit/. @@ -18,5 +18,5 @@ commands = python -m black . python -m pylama . python -m pydocstyle . - python -m mypy --strict nssh/ - bash -c 'find nssh -type f \( -iname "*.py" ! -iname "ptyprocess.py" \) | xargs darglint' + python -m mypy --strict scrapli/ + bash -c 'find scrapli -type f \( -iname "*.py" ! -iname "ptyprocess.py" \) | xargs darglint -x'