diff --git a/.gitignore b/.gitignore index 2939bb1..df8eeee 100644 --- a/.gitignore +++ b/.gitignore @@ -78,4 +78,7 @@ dmypy.json private/ # log output -*.log \ No newline at end of file +*.log + +# mkdocs site +site/* \ No newline at end of file diff --git a/LICENSE b/LICENSE index 3a367dd..e643fdf 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 Carl Montanari +Copyright (c) 2021 Carl Montanari Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index 065ef23..8d697ec 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,8 @@ lint: - python -m isort nornir_scrapli/ - python -m isort examples/ - python -m isort tests/ - python -m black nornir_scrapli/ - python -m black examples/ - python -m black tests/ - python -m pylama nornir_scrapli/ - python -m pydocstyle nornir_scrapli/ + python -m isort . + python -m black . + python -m pylama . + python -m pydocstyle . python -m mypy nornir_scrapli/ cov: @@ -21,9 +17,7 @@ test: .PHONY: docs docs: - rm -rf docs/nornir_scrapli - python -m pdoc \ - --html \ - --output-dir docs \ - nornir_scrapli \ - --force + python docs/generate/generate_docs.py + +deploy_docs: + mkdocs gh-deploy diff --git a/README.md b/README.md index 6cbc785..33ae55c 100644 --- a/README.md +++ b/README.md @@ -1,334 +1,69 @@ -![](https://github.com/carlmontanari/nornir_scrapli/workflows/Weekly%20Build/badge.svg) +[![Supported Versions](https://img.shields.io/pypi/pyversions/scrapli.svg)](https://pypi.org/project/nornir_scrapli) [![PyPI version](https://badge.fury.io/py/scrapli.svg)](https://badge.fury.io/py/nornir_scrapli) -[![Python 3.6](https://img.shields.io/badge/python-3.6-blue.svg)](https://www.python.org/downloads/release/python-360/) -[![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/) -[![Python 3.9](https://img.shields.io/badge/python-3.9-blue.svg)](https://www.python.org/downloads/release/python-390/) +[![Weekly Build](https://github.com/scrapli/nornir_scrapli/workflows/Weekly%20Build/badge.svg)](https://github.com/scrapli/nornir_scrapli/actions?query=workflow%3A%22Weekly+Build%22) [![Code Style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) - +[![License: MIT](https://img.shields.io/badge/License-MIT-blueviolet.svg)](https://opensource.org/licenses/MIT) nornir_scrapli ============== -nornir_scrapli -- [scrapli](https://github.com/carlmontanari/scrapli)'s and -[scrapli_netconf](https://github.com/scrapli/scrapli_netconf)'s plugin for nornir! - -Feel free to join the very awesome networktocode slack workspace [here](https://networktocode.slack.com/), where you - will find a `scrapli` channel where you can discuss anything about scrapli, as well as tons of other channels covering - all sorts of network/network-automation topics! - - -# Table of Contents - -- [Quick Start Guide](#quick-start-guide) - - [Installation](#installation) - - [A Simple Example](#a-simple-example) - - [Additional Examples](#additional-examples) -- [Supported Platforms](#supported-platforms) -- [Using Different Transports](#using-different-transports) -- [Documentation](#documentation) -- [General Information](#general-information) -- [Available Tasks](#available-tasks) - - [Telnet/SSH Tasks](#scrapli-core-tasks) - - [NETCONF Tasks](#scrapli-netconf-tasks) -- [Available Functions](#available-functions) - - -# Quick Start Guide - -## Installation - -In most cases installation via pip is the simplest and best way to install nornir_scrapli. - -``` -pip install nornir-scrapli -``` - - -## A Simple Example - -Example config file: - -```yaml ---- -inventory: - plugin: YAMLInventory - options: - host_file: "nornir_data/hosts.yaml" - group_file: "nornir_data/groups.yaml" - defaults_file: "nornir_data/defaults.yaml" -``` - -Example inventory file (host/group/default, see "real" Nornir docs for lots more info!) -- please notice that there - is a `scrapli` and a `scrapli_netconf` connection type here!: -```yaml --- -iosxe-1: - hostname: 172.18.0.11 - connection_options: - scrapli: - platform: cisco_iosxe - port: 22 - extras: - ssh_config_file: True - auth_strict_key: False - scrapli_netconf: - port: 830 - extras: - ssh_config_file: True - auth_strict_key: False -``` - -**NOTE:** `scrapli-netconf` has no concept (at the moment!) of "platforms" - it simply implements RFC compliant - NETCONF RPCs, so you do not need to pass `iosxr`, `junos` or anything like that to the `scrapli_netconf` connection - options section! - - -```python -from nornir import InitNornir -from nornir_scrapli.tasks import ( - get_prompt, - send_command, - send_configs -) - -nr = InitNornir(config_file="nornir_data/config.yaml") - -prompt_results = nr.run(task=get_prompt) -command_results = nr.run(task=send_command, command="show version") -config_results = nr.run( - task=send_configs, - configs=["interface loopback123", "description nornir_scrapli was here"], -) - -print("get_prompt result:") -print(prompt_results["iosxe-1"].result) -print("send_command result:") -print(command_results["iosxe-1"].result) -print("send_configs result:") -print(config_results["iosxe-1"].result) -``` - -``` -$ python my_scrapli_script.py -get_prompt result: -3560CX# -send_command result: -Cisco IOS Software, C3560CX Software (C3560CX-UNIVERSALK9-M), Version 15.2(4)E7, RELEASE SOFTWARE (fc2) - -send_configs result: - - -``` - -Netconf tasks are imported from the same package and in the same fashion as the "core" `scrapli` tasks: - -```python -from nornir_scrapli.tasks import ( - netconf_lock, - netconf_unlock, - netconf_edit_config, - netconf_get, - netconf_get_config, - netconf_rpc -) -``` - -And are executed in the same fashion as well: - -```python -config = """ - - - GigabitEthernet1 - scrapli was here! - - -""" -result = nr.run(task=netconf_edit_config, config=config) -print(result['iosxe1'][0].result) -print(result['iosxe1'][0].scrapli_response.xml_result) -``` - -When using the `scrapli-netconf` tasks the result object `result` will be the string of the returned data from the - device. As with all other `nornir-scrapli` results, the `scrapli_response` object will be assigned to the `Result - ` object and will contain all of the "normal" `scrapli` response object data (or `scrapli-netconf` response data - ), such as the `elapsed_time`, `raw_result`, `xml_result`, etc. -- you can see this in the above example! - - -## Additional Examples - -- [NETCONF Usage](/examples/basic_netconf_usage) -- [Structured Data](/examples/structured_data) - -# Supported Platforms +**Documentation**: https://scrapli.github.io/nornir_scrapli -nornir_scrapli supports the "core" scrapli drivers, the GenericDriver (for use with linux hosts generally speaking -), and the [scrapli_community](https://github.com/scrapli/scrapli_community) platforms as well! See -[scrapli core docs](https://github.com/carlmontanari/scrapli#supported-platforms) and the -[scrapli community docs](https://github.com/scrapli/scrapli_community#supported-platforms) for more info. The `platform -` argument in the inventory data should use the "normal" NAPALM style platform names, `generic`, or the name of the - scrapli_community platform (i.e. `huawei_vrp`)). +**Source Code**: https://github.com/scrapli/nornir_scrapli -Example platform values (for inventory data): +**Examples**: https://github.com/scrapli/nornir_scrapli/tree/master/examples -``` -platform: cisco_iosxe -platform: cisco_iosxr -platform: cisco_nxos -platform: arista_eos -platform: juniper_junos -platform: generic -platform: huawei_vrp -``` +--- +nornir_scrapli -- scrapli's plugin for nornir -# Using Different Transports -nornir_scrapli supports all *synchronous* scrapli transport plugins. By default, the "system" transport will be used, -however you can change this in the `extras` section of your nornir inventory: +#### Key Features: -```yaml -connection_options: - scrapli: - port: 22 - extras: - ssh_config_file: True - auth_strict_key: False - transport: ssh2 -``` +- __Easy__: It's easy to get going with scrapli -- check out the documentation and example links above, and you'll be + connecting to devices in no time. +- __Fast__: Do you like to go fast? Of course you do! All of scrapli is built with speed in mind, but if you really + feel the need for speed, check out the `ssh2` transport plugin to take it to the next level! +- __Great Developer Experience__: scrapli has great editor support thanks to being fully typed; that plus thorough + docs make developing with scrapli a breeze. +- __Well Tested__: Perhaps out of paranoia, but regardless of the reason, scrapli has lots of tests! Unit tests + cover the basics, regularly ran functional tests connect to virtual routers to ensure that everything works IRL! +- __Pluggable__: scrapli provides a pluggable transport system -- don't like the currently available transports, + simply extend the base classes and add your own! Need additional device support? Create a simple "platform" in + [scrapli_community](https://github.com/scrapli/scrapli_community) to easily add new device support! +- __But wait, there's more!__: Have NETCONF devices in your environment, but love the speed and simplicity of + scrapli? You're in luck! Check out [scrapli_netconf](https://github.com/scrapli/scrapli_netconf)! -Note that you will need to install `scrapli_ssh2` or `scrapli_paramiko` if you want to use those transport plugins! +## Requirements -# Documentation +MacOS or \*nix1, Python 3.6+ -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/). +1 Although many parts of scrapli *do* run on Windows, Windows is not officially supported -Documentation is hosted via GitHub Pages and can be found -[here](https://carlmontanari.github.io/nornir_scrapli/docs/nornir_scrapli/index.html). You can also view this readme as a web - page [here](https://carlmontanari.github.io/nornir_scrapli/). -To regenerate documentation locally, use the following make command: +## Installation ``` -make docs +pip install nornir-scrapli ``` +See the [docs](https://scrapli.github.io/nornir_scrapli/user_guide/installation) for other installation methods/details. -# General Information -Nornir has historically contained it's plugins within the actual Nornir codebase itself, this however has changed! As - of mid September 2020, Nornir 3.0.0 has been officially released -- this move to the 3.x.x version now expects - plugins to be external to the code base. If you are looking for pre 3.x.x support, please use the `2020.09.01 - ` version. -If you have used Nornir before (pre 3.x.x), this package should be very similar to what you already know. Since the - plugins used to live in Nornir you could simply import them from the appropriate package as such: - -```python -from nornir.plugins.tasks.networking import netconf_get_config -``` +## A simple Example -With nornir_scrapli you simply install this package along side "regular" Nornir, and import the tasks from - nornir_scrapli directly: - ```python +from nornir import InitNornir from nornir_scrapli.tasks import send_command -``` -As soon as a nornir_scrapli task is imported, it (`nornir_scrapli`) will register as a connection, and things should - work as normal from there! -The last important difference with nornir_scrapli is that in addition to the "normal" data in the Nornir Result - object, nornir_scrapli also assigns the scrapli `Response` object (or list of `Response` objects) to the - `scrapli_response` attribute. This means that you can access all of the "normal" scrapli response data from this - object -- including things like `elapsed_time` and `textfsm_parse_output`: - -```python ->>> some_nornir_result["sea-ios-1"].scrapli_response.elapsed_time -0.039469 ->>> some_nornir_result["sea-ios-1"].scrapli_response.textfsm_parse_output() -[[some structured data back from the device!]] -``` - -If you would like to continue using `print_result` like "normal" in nornir, but would like to see structured data (if - available) in the `print_result` output, you can use the nornir_scrapli `print_structured_result` function. This - function can be imported from the scrapli functions module: - -```python -from nornir_scrapli.functions import print_structured_result -``` +nr = InitNornir(config_file="nornir_data/config.yaml") +command_results = nr.run(task=send_command, command="show version") -This function acts pretty much exactly like the "normal" print result function, but will of course try to print the - structured result. By default this will try to use textfsm to parse results, but it is of course configurable via - the `parser` keyword argument. As scrapli will return an empty data structure if parsing fails, this may cause - tasks to look like they are getting skipped in the output (nornir's print result function does not print empty - lists), if you would like to fall back to printing the unparsed output you can do so by setting the - `fail_to_string` keyword argument to `True` as follows: - -```python -print_structured_result(my_agg_result, parser="genie", fail_to_string=True) +print("send_command result:") +print(command_results["iosxe-1"].result) ``` - - -# Available Tasks - -All tasks presented here are methods that live in `scrapli` or `scrapli_netconf` -- these tasks are simply "wrapped -" in such a way that they may be used within the constructs of `nornir`! The links below link back to the `scrapli -` or `scrapli_netconf` docs for the given method -- in all (or very nearly all?) cases, the same arguments that the - underlying library supports will be exposed to `nornir`! - -## Scrapli "core" Tasks - -- [get_prompt](https://carlmontanari.github.io/scrapli/docs/scrapli/driver/index.html#scrapli.driver.GenericDriver.get_prompt) - -Get the current prompt of the device -- [send_command](https://carlmontanari.github.io/scrapli/docs/scrapli/driver/index.html#scrapli.driver.NetworkDriver.send_command) - -Send a single command to the device -- [send_commands](https://carlmontanari.github.io/scrapli/docs/scrapli/driver/index.html#scrapli.driver.GenericDriver.send_commands) - -Send a list of commands to the device -- [send_commands_from_file](https://carlmontanari.github.io/scrapli/docs/scrapli/driver/index.html#scrapli.driver.GenericDriver.send_commands_from_file) - -Send a list of commands from a file to the device -- [send_config](https://carlmontanari.github.io/scrapli/docs/scrapli/driver/index.html#scrapli.driver.NetworkDriver.send_config) - -Send a configuration to the device -- [send_configs](https://carlmontanari.github.io/scrapli/docs/scrapli/driver/index.html#scrapli.driver.NetworkDriver.send_configs) - -Send a list of configurations to the device -- [send_configs_from_file](https://carlmontanari.github.io/scrapli/docs/scrapli/driver/index.html#scrapli.driver.NetworkDriver.send_configs_from_file) - -Send a list of configurations from a file to the device -- [send_interactive](https://carlmontanari.github.io/scrapli/docs/scrapli/driver/index.html#scrapli.driver.GenericDriver.send_interactive) - -"Interact" with the device (handle prompts and inputs and things like that) - -## Scrapli Netconf Tasks - -Note that not all devices will support all operations! - -- netconf_capabilities - Get list of capabilities as exchanged during netconf connection establishment -- [netconf_commit](https://scrapli.github.io/scrapli_netconf/docs/scrapli_netconf/index.html#scrapli_netconf.NetconfScrape.commit) - -Commit the configuration on the device -- [netconf_discard](https://scrapli.github.io/scrapli_netconf/docs/scrapli_netconf/index.html#scrapli_netconf.NetconfScrape.discard) - -Discard the configuration on the device -- [netconf_edit_config](https://scrapli.github.io/scrapli_netconf/docs/scrapli_netconf/index.html#scrapli_netconf.NetconfScrape.edit_config) - -Edit the configuration on the device -- netconf_delete_config - Delete a given datastore on the device -- [netconf_get](https://scrapli.github.io/scrapli_netconf/docs/scrapli_netconf/index.html#scrapli_netconf.NetconfScrape.get) - -Get a subtree or xpath from the device -- [netconf_get_config](https://scrapli.github.io/scrapli_netconf/docs/scrapli_netconf/index.html#scrapli_netconf.NetconfScrape.get_config) = -Get the configuration from the device -- [netconf_lock](https://scrapli.github.io/scrapli_netconf/docs/scrapli_netconf/index.html#scrapli_netconf.NetconfScrape.lock) - -Lock the datastore on the device -- [netconf_unlock](https://scrapli.github.io/scrapli_netconf/docs/scrapli_netconf/index.html#scrapli_netconf.NetconfScrape.unlock) - -Unlock the datastore on the device -- [netconf_rpc](https://scrapli.github.io/scrapli_netconf/docs/scrapli_netconf/index.html#scrapli_netconf.NetconfScrape.rpc) - -Send a "bare" RPC to the device -- netconf_validate - Execute the `validate` rpc against a given datastore - - -# Available Functions - -- [print_structured_result](/nornir_scrapli) -- this function is very similar to the "normal" `print_result` function - that now ships with the `nornir_utils` library (historically with nornir "core"), except it contains several - additional arguments, most importantly the `parser` argument allows you to select `textfsm` or `genie` to decide - which parser to use to parse the unstructured data stored in the results object. Please see the structured - results example [here](/examples/structured_data) for more details. diff --git a/_config.yml b/_config.yml deleted file mode 100644 index 1885487..0000000 --- a/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-midnight \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/docs/about/code_of_conduct.md similarity index 100% rename from CODE_OF_CONDUCT.md rename to docs/about/code_of_conduct.md diff --git a/docs/about/contributing.md b/docs/about/contributing.md new file mode 100644 index 0000000..8cc207a --- /dev/null +++ b/docs/about/contributing.md @@ -0,0 +1,16 @@ +Contributing +======= + +Thanks for thinking about contributing to nornir_scrapli! Contributions are not expected, but are quite welcome. + +Contributions of all kinds are welcomed -- typos, doc updates, adding examples, bug fixes, and feature adds. + + +Some notes on contributing: + +- Please open an issue to discuss any bug fixes, feature adds, or really any thing that could result in a pull + request. This allows us to all be on the same page, and could save everyone some extra work! +- Once we've discussed any changes, pull requests are of course welcome and very much appreciated! + - All PRs should pass tests -- checkout the Makefile for some shortcuts for linting and testing. + - Please include tests! Even simple/basic tests are better than nothing -- it helps make sure changes in the future + don't break functionality or make things act in unexpected ways! diff --git a/docs/api_docs/connection.md b/docs/api_docs/connection.md new file mode 100644 index 0000000..522ee92 --- /dev/null +++ b/docs/api_docs/connection.md @@ -0,0 +1,518 @@ + + + + + + + + + + + + + + + + + + + + +#Module nornir_scrapli.connection + +nornir_scrapli.connection + +
+ + Expand source code + +
+        
+"""nornir_scrapli.connection"""
+from typing import Any, Dict, Optional
+
+from scrapli import Scrapli
+from scrapli.driver import GenericDriver
+from scrapli.exceptions import ScrapliModuleNotFound
+from scrapli_netconf.driver import NetconfDriver
+
+from nornir.core.configuration import Config
+from nornir_scrapli.exceptions import NornirScrapliInvalidPlatform
+
+CONNECTION_NAME = "scrapli"
+
+PLATFORM_MAP = {
+    "ios": "cisco_iosxe",
+    "nxos": "cisco_nxos",
+    "iosxr": "cisco_iosxr",
+    "eos": "arista_eos",
+    "junos": "juniper_junos",
+}
+
+
+class ScrapliCore:
+    """Scrapli connection plugin for nornir"""
+
+    def open(
+        self,
+        hostname: Optional[str],
+        username: Optional[str],
+        password: Optional[str],
+        port: Optional[int],
+        platform: Optional[str],
+        extras: Optional[Dict[str, Any]] = None,
+        configuration: Optional[Config] = None,
+    ) -> None:
+        """
+        Open a scrapli connection to a device
+
+        Args:
+            hostname: hostname from nornir inventory
+            username: username from nornir inventory/connection_options for scrapli
+            password: password from nornir inventory/connection_options for scrapli
+            port: port from nornir inventory/connection_options for scrapli
+            platform: platform from nornir inventory/connection_options for scrapli
+            extras: extras dict from connection_options for scrapli -- pass all other scrapli
+                arguments here
+            configuration: nornir configuration
+
+        Returns:
+            N/A  # noqa: DAR202
+
+        Raises:
+            NornirScrapliInvalidPlatform: if no platform or an invalid scrapli/napalm platform
+                string is provided
+
+        """
+        extras = extras or {}
+        # 99.9% configuration will always be passed here... but to be consistent w/ the other
+        # plugins we'll leave the function signature same/same as the others
+        global_config = configuration.dict() if configuration else {}
+
+        parameters: Dict[str, Any] = {
+            "host": hostname,
+            "auth_username": username or "",
+            "auth_password": password or "",
+            "port": port or 22,
+            "ssh_config_file": global_config.get("ssh", {}).get("config_file", False),
+        }
+
+        # will override any of the configs from global nornir config (such as ssh config file) with
+        # options from "extras" (connection options)
+        parameters.update(extras)
+
+        if not platform:
+            raise NornirScrapliInvalidPlatform(
+                f"No `platform` provided in inventory for host `{hostname}`"
+            )
+
+        if platform in PLATFORM_MAP:
+            platform = PLATFORM_MAP.get(platform)
+
+        if platform == "generic":
+            connection = GenericDriver(**parameters)
+        else:
+            try:
+                connection = Scrapli(**parameters, platform=platform)  # type: ignore
+            except ScrapliModuleNotFound as exc:
+                raise NornirScrapliInvalidPlatform(
+                    f"Provided platform `{platform}` is not a valid scrapli or napalm platform, "
+                    "or is not a valid scrapli-community platform."
+                ) from exc
+
+        connection.open()
+        self.connection = connection  # pylint: disable=W0201
+
+    def close(self) -> None:
+        """
+        Close a scrapli connection to a device
+
+        Args:
+            N/A
+
+        Returns:
+            N/A  # noqa: DAR202
+
+        Raises:
+            N/A
+
+        """
+        self.connection.close()
+
+
+class ScrapliNetconf:
+    """Scrapli NETCONF connection plugin for nornir"""
+
+    def open(
+        self,
+        hostname: Optional[str],
+        username: Optional[str],
+        password: Optional[str],
+        port: Optional[int],
+        platform: Optional[str],
+        extras: Optional[Dict[str, Any]] = None,
+        configuration: Optional[Config] = None,
+    ) -> None:
+        """
+        Open a scrapli connection to a device
+
+        Args:
+            hostname: hostname from nornir inventory
+            username: username from nornir inventory/connection_options for scrapli
+            password: password from nornir inventory/connection_options for scrapli
+            port: port from nornir inventory/connection_options for scrapli
+            platform: platform from nornir inventory/connection_options for scrapli; ignored with
+                scrapli netconf
+            extras: extras dict from connection_options for scrapli -- pass all other scrapli
+                arguments here
+            configuration: nornir configuration
+
+        Returns:
+            N/A  # noqa: DAR202
+
+        Raises:
+            N/A
+
+        """
+        # platform is irrelevant for scrapli netconf for now
+        _ = platform
+        extras = extras or {}
+        # 99.9% configuration will always be passed here... but to be consistent w/ the other
+        # plugins we'll leave the function signature same/same as the others
+        global_config = configuration.dict() if configuration else {}
+
+        parameters: Dict[str, Any] = {
+            "host": hostname,
+            "auth_username": username or "",
+            "auth_password": password or "",
+            "port": port or 830,
+            "ssh_config_file": global_config.get("ssh", {}).get("config_file", False),
+        }
+
+        # will override any of the configs from global nornir config (such as ssh config file) with
+        # options from "extras" (connection options)
+        parameters.update(extras)
+
+        connection = NetconfDriver(**parameters)
+        connection.open()
+        self.connection = connection  # pylint: disable=W0201
+
+    def close(self) -> None:
+        """
+        Close a scrapli netconf connection to a device
+
+        Args:
+            N/A
+
+        Returns:
+            N/A  # noqa: DAR202
+
+        Raises:
+            N/A
+
+        """
+        self.connection.close()
+        
+    
+
+ + + + +## Classes + +### ScrapliCore + + +```text +Scrapli connection plugin for nornir +``` + +
+ + Expand source code + +
+        
+class ScrapliCore:
+    """Scrapli connection plugin for nornir"""
+
+    def open(
+        self,
+        hostname: Optional[str],
+        username: Optional[str],
+        password: Optional[str],
+        port: Optional[int],
+        platform: Optional[str],
+        extras: Optional[Dict[str, Any]] = None,
+        configuration: Optional[Config] = None,
+    ) -> None:
+        """
+        Open a scrapli connection to a device
+
+        Args:
+            hostname: hostname from nornir inventory
+            username: username from nornir inventory/connection_options for scrapli
+            password: password from nornir inventory/connection_options for scrapli
+            port: port from nornir inventory/connection_options for scrapli
+            platform: platform from nornir inventory/connection_options for scrapli
+            extras: extras dict from connection_options for scrapli -- pass all other scrapli
+                arguments here
+            configuration: nornir configuration
+
+        Returns:
+            N/A  # noqa: DAR202
+
+        Raises:
+            NornirScrapliInvalidPlatform: if no platform or an invalid scrapli/napalm platform
+                string is provided
+
+        """
+        extras = extras or {}
+        # 99.9% configuration will always be passed here... but to be consistent w/ the other
+        # plugins we'll leave the function signature same/same as the others
+        global_config = configuration.dict() if configuration else {}
+
+        parameters: Dict[str, Any] = {
+            "host": hostname,
+            "auth_username": username or "",
+            "auth_password": password or "",
+            "port": port or 22,
+            "ssh_config_file": global_config.get("ssh", {}).get("config_file", False),
+        }
+
+        # will override any of the configs from global nornir config (such as ssh config file) with
+        # options from "extras" (connection options)
+        parameters.update(extras)
+
+        if not platform:
+            raise NornirScrapliInvalidPlatform(
+                f"No `platform` provided in inventory for host `{hostname}`"
+            )
+
+        if platform in PLATFORM_MAP:
+            platform = PLATFORM_MAP.get(platform)
+
+        if platform == "generic":
+            connection = GenericDriver(**parameters)
+        else:
+            try:
+                connection = Scrapli(**parameters, platform=platform)  # type: ignore
+            except ScrapliModuleNotFound as exc:
+                raise NornirScrapliInvalidPlatform(
+                    f"Provided platform `{platform}` is not a valid scrapli or napalm platform, "
+                    "or is not a valid scrapli-community platform."
+                ) from exc
+
+        connection.open()
+        self.connection = connection  # pylint: disable=W0201
+
+    def close(self) -> None:
+        """
+        Close a scrapli connection to a device
+
+        Args:
+            N/A
+
+        Returns:
+            N/A  # noqa: DAR202
+
+        Raises:
+            N/A
+
+        """
+        self.connection.close()
+        
+    
+
+ + +#### Methods + + + +##### close +`close(self) ‑> NoneType` + +```text +Close a scrapli connection to a device + +Args: + N/A + +Returns: + N/A # noqa: DAR202 + +Raises: + N/A +``` + + + + + +##### open +`open(self, hostname: Union[str, NoneType], username: Union[str, NoneType], password: Union[str, NoneType], port: Union[int, NoneType], platform: Union[str, NoneType], extras: Union[Dict[str, Any], NoneType] = None, configuration: Union[nornir.core.configuration.Config, NoneType] = None) ‑> NoneType` + +```text +Open a scrapli connection to a device + +Args: + hostname: hostname from nornir inventory + username: username from nornir inventory/connection_options for scrapli + password: password from nornir inventory/connection_options for scrapli + port: port from nornir inventory/connection_options for scrapli + platform: platform from nornir inventory/connection_options for scrapli + extras: extras dict from connection_options for scrapli -- pass all other scrapli + arguments here + configuration: nornir configuration + +Returns: + N/A # noqa: DAR202 + +Raises: + NornirScrapliInvalidPlatform: if no platform or an invalid scrapli/napalm platform + string is provided +``` + + + + + +### ScrapliNetconf + + +```text +Scrapli NETCONF connection plugin for nornir +``` + +
+ + Expand source code + +
+        
+class ScrapliNetconf:
+    """Scrapli NETCONF connection plugin for nornir"""
+
+    def open(
+        self,
+        hostname: Optional[str],
+        username: Optional[str],
+        password: Optional[str],
+        port: Optional[int],
+        platform: Optional[str],
+        extras: Optional[Dict[str, Any]] = None,
+        configuration: Optional[Config] = None,
+    ) -> None:
+        """
+        Open a scrapli connection to a device
+
+        Args:
+            hostname: hostname from nornir inventory
+            username: username from nornir inventory/connection_options for scrapli
+            password: password from nornir inventory/connection_options for scrapli
+            port: port from nornir inventory/connection_options for scrapli
+            platform: platform from nornir inventory/connection_options for scrapli; ignored with
+                scrapli netconf
+            extras: extras dict from connection_options for scrapli -- pass all other scrapli
+                arguments here
+            configuration: nornir configuration
+
+        Returns:
+            N/A  # noqa: DAR202
+
+        Raises:
+            N/A
+
+        """
+        # platform is irrelevant for scrapli netconf for now
+        _ = platform
+        extras = extras or {}
+        # 99.9% configuration will always be passed here... but to be consistent w/ the other
+        # plugins we'll leave the function signature same/same as the others
+        global_config = configuration.dict() if configuration else {}
+
+        parameters: Dict[str, Any] = {
+            "host": hostname,
+            "auth_username": username or "",
+            "auth_password": password or "",
+            "port": port or 830,
+            "ssh_config_file": global_config.get("ssh", {}).get("config_file", False),
+        }
+
+        # will override any of the configs from global nornir config (such as ssh config file) with
+        # options from "extras" (connection options)
+        parameters.update(extras)
+
+        connection = NetconfDriver(**parameters)
+        connection.open()
+        self.connection = connection  # pylint: disable=W0201
+
+    def close(self) -> None:
+        """
+        Close a scrapli netconf connection to a device
+
+        Args:
+            N/A
+
+        Returns:
+            N/A  # noqa: DAR202
+
+        Raises:
+            N/A
+
+        """
+        self.connection.close()
+        
+    
+
+ + +#### Methods + + + +##### close +`close(self) ‑> NoneType` + +```text +Close a scrapli netconf connection to a device + +Args: + N/A + +Returns: + N/A # noqa: DAR202 + +Raises: + N/A +``` + + + + + +##### open +`open(self, hostname: Union[str, NoneType], username: Union[str, NoneType], password: Union[str, NoneType], port: Union[int, NoneType], platform: Union[str, NoneType], extras: Union[Dict[str, Any], NoneType] = None, configuration: Union[nornir.core.configuration.Config, NoneType] = None) ‑> NoneType` + +```text +Open a scrapli connection to a device + +Args: + hostname: hostname from nornir inventory + username: username from nornir inventory/connection_options for scrapli + password: password from nornir inventory/connection_options for scrapli + port: port from nornir inventory/connection_options for scrapli + platform: platform from nornir inventory/connection_options for scrapli; ignored with + scrapli netconf + extras: extras dict from connection_options for scrapli -- pass all other scrapli + arguments here + configuration: nornir configuration + +Returns: + N/A # noqa: DAR202 + +Raises: + N/A +``` \ No newline at end of file diff --git a/docs/api_docs/exceptions.md b/docs/api_docs/exceptions.md new file mode 100644 index 0000000..af411c7 --- /dev/null +++ b/docs/api_docs/exceptions.md @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + +#Module nornir_scrapli.exceptions + +nornir_scrapli.exceptions + +
+ + Expand source code + +
+        
+"""nornir_scrapli.exceptions"""
+
+
+class NornirScrapliException(Exception):
+    """nornir_scrapli base exception"""
+
+
+class NornirScrapliInvalidPlatform(NornirScrapliException):
+    """nornir_scrapli base exception"""
+
+
+class NornirScrapliNoConfigModeGenericDriver(NornirScrapliException):
+    """nornir_scrapli exception for attempting config mode on generic platform"""
+        
+    
+
+ + + + +## Classes + +### NornirScrapliException + + +```text +nornir_scrapli base exception +``` + +
+ + Expand source code + +
+        
+class NornirScrapliException(Exception):
+    """nornir_scrapli base exception"""
+        
+    
+
+ + +#### Ancestors (in MRO) +- builtins.Exception +- builtins.BaseException +#### Descendants +- nornir_scrapli.exceptions.NornirScrapliInvalidPlatform +- nornir_scrapli.exceptions.NornirScrapliNoConfigModeGenericDriver + + + +### NornirScrapliInvalidPlatform + + +```text +nornir_scrapli base exception +``` + +
+ + Expand source code + +
+        
+class NornirScrapliInvalidPlatform(NornirScrapliException):
+    """nornir_scrapli base exception"""
+        
+    
+
+ + +#### Ancestors (in MRO) +- nornir_scrapli.exceptions.NornirScrapliException +- builtins.Exception +- builtins.BaseException + + + +### NornirScrapliNoConfigModeGenericDriver + + +```text +nornir_scrapli exception for attempting config mode on generic platform +``` + +
+ + Expand source code + +
+        
+class NornirScrapliNoConfigModeGenericDriver(NornirScrapliException):
+    """nornir_scrapli exception for attempting config mode on generic platform"""
+        
+    
+
+ + +#### Ancestors (in MRO) +- nornir_scrapli.exceptions.NornirScrapliException +- builtins.Exception +- builtins.BaseException \ No newline at end of file diff --git a/docs/api_docs/functions.md b/docs/api_docs/functions.md new file mode 100644 index 0000000..75241b5 --- /dev/null +++ b/docs/api_docs/functions.md @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + +#Module nornir_scrapli.functions + +nornir_scrapli.functions + +
+ + Expand source code + +
+        
+"""nornir_scrapli.functions"""
+from nornir_scrapli.functions.print_structured_result import print_structured_result
+
+__all__ = ("print_structured_result",)
+        
+    
+
+ + + +## Functions + + + +#### print_structured_result +`print_structured_result(result: nornir.core.task.AggregatedResult, failed: bool = False, severity_level: int = 20, parser: str = 'textfsm', to_dict: bool = True, fail_to_string: bool = False) ‑> NoneType` + +```text +Prints the :obj:`nornir.core.task.Result` from a previous task to screen + +Arguments: + result: Nornir AggregateResult object from a previous task + failed: if `True` assume the task failed + severity_level: Print only errors with this severity level or higher + parser: textfsm|genie -- parser to parse output with + to_dict: output structured data in dict form instead -- basically put k:v instead of just + lists of lists of values for textfsm output; ignored if parser == "genie" + fail_to_string: fallback to printing unstructured output or have tasks skipped (because + print_result won't print empty lists which scrapli returns if parsing fails) +``` \ No newline at end of file diff --git a/docs/api_docs/helper.md b/docs/api_docs/helper.md new file mode 100644 index 0000000..1a81eb4 --- /dev/null +++ b/docs/api_docs/helper.md @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + +#Module nornir_scrapli.helper + +nornir_scrapli.helper + +
+ + Expand source code + +
+        
+"""nornir_scrapli.helper"""
+import difflib
+
+ANSI_GREEN = "\033[92m"
+ANSI_RED = "\033[91m"
+ANSI_END = "\033[0m"
+
+
+def diff_xml_text(document_one: str, document_two: str) -> str:
+    """
+    Diff xml text strings
+
+    Really could be just "diff text" but also ensuring we ignore the "message-id" lines. This is
+    really pretty simple and not always super great, but better than nothing for now!
+
+    Args:
+        document_one: string of xml doc 1
+        document_two: string of xml doc 2
+
+    Returns:
+        str: unified diff of the two input documents
+
+    Raises:
+        N/A
+
+    """
+    # ignore message-id stuff -- maybe more in the future?
+    document_one_lines = [line for line in document_one.splitlines() if "message-id" not in line]
+    document_two_lines = [line for line in document_two.splitlines() if "message-id" not in line]
+    diff = difflib.unified_diff(document_one_lines, document_two_lines)
+
+    diff_lines = []
+    for line in diff:
+        if line.startswith("---") or line.startswith("+++"):
+            # may as well just strip out the header lines and such, we dont care about them
+            continue
+        if line.startswith("+"):
+            diff_lines.append(f"{ANSI_GREEN}{line}{ANSI_END}")
+        elif line.startswith("-"):
+            diff_lines.append(f"{ANSI_RED}{line}{ANSI_END}")
+        else:
+            diff_lines.append(line)
+
+    return "\n".join(diff_lines)
+        
+    
+
+ + + +## Functions + + + +#### diff_xml_text +`diff_xml_text(document_one: str, document_two: str) ‑> str` + +```text +Diff xml text strings + +Really could be just "diff text" but also ensuring we ignore the "message-id" lines. This is +really pretty simple and not always super great, but better than nothing for now! + +Args: + document_one: string of xml doc 1 + document_two: string of xml doc 2 + +Returns: + str: unified diff of the two input documents + +Raises: + N/A +``` \ No newline at end of file diff --git a/docs/api_docs/result.md b/docs/api_docs/result.md new file mode 100644 index 0000000..0714a86 --- /dev/null +++ b/docs/api_docs/result.md @@ -0,0 +1,314 @@ + + + + + + + + + + + + + + + + + + + + +#Module nornir_scrapli.result + +nornir_scrapli.result + +
+ + Expand source code + +
+        
+"""nornir_scrapli.result"""
+from typing import TYPE_CHECKING, Any, Optional, Union
+
+from scrapli.response import MultiResponse, Response
+
+from nornir.core.task import Result
+
+if TYPE_CHECKING:
+    from nornir.core.inventory import Host  # pylint: disable=C0412
+
+
+def process_command_result(scrapli_response: Union[Response, MultiResponse]) -> str:
+    """
+    Process and return string of scrapli response(s)
+
+    Args:
+        scrapli_response: scrapli Response or MultiResponse object
+
+    Returns:
+        str: string result from nornir task or None
+
+    Raises:
+        N/A
+
+    """
+    if isinstance(scrapli_response, Response):
+        result: str = scrapli_response.result
+        return result
+    return "\n\n".join([response.result for response in scrapli_response])
+
+
+def process_config_result(scrapli_response: Union[Response, MultiResponse]) -> str:
+    """
+    Process and return string of scrapli response(s)
+
+    Args:
+        scrapli_response: scrapli Response or MultiResponse object
+
+    Returns:
+        str: string result from nornir task or None
+
+    Raises:
+        N/A
+
+    """
+    full_results = ""
+    if isinstance(scrapli_response, Response):
+        for config_input, config_result in zip(
+            scrapli_response.channel_input.split("\n"), scrapli_response.result.split("\n")
+        ):
+            if config_input == config_result:
+                full_results += f"{config_input}\n"
+            else:
+                full_results += "\n".join([config_input, config_result])
+    else:
+        for response in scrapli_response:
+            full_results += "\n".join([response.channel_input, response.result])
+    return full_results
+
+
+class ScrapliResult(Result):  # type: ignore
+    def __init__(
+        self,
+        host: "Host",
+        result: Optional[str],
+        scrapli_response: Optional[Union[Response, MultiResponse]] = None,
+        changed: bool = False,
+        **kwargs: Any,
+    ):
+        """
+        Scrapli Nornir Result object
+
+        A "normal" nornir result object with an additional attribute "scrapli_response" which houses
+        the original response object returned from scrapli
+
+        Args:
+            host: nornir task host object
+            result: result text returned from scrapli task
+            scrapli_response: original response object returned from scrapli task
+            changed: bool indicating if a change has occurred
+            kwargs: keyword arguments to pass to nornir Result
+
+        Returns:
+            N/A  # noqa: DAR202
+
+        Raises:
+            N/A
+
+        """
+        failed = self._process_failed(scrapli_response=scrapli_response)
+
+        super().__init__(host=host, result=result, failed=failed, changed=changed, **kwargs)
+
+        self.scrapli_response = scrapli_response
+
+    @staticmethod
+    def _process_failed(scrapli_response: Optional[Union[Response, MultiResponse]]) -> bool:
+        """
+        Process and return string of scrapli response(s)
+
+        Args:
+            scrapli_response: scrapli Response or MultiResponse object
+
+        Returns:
+            bool: bool indicating if the nornir task failed
+
+        Raises:
+            N/A
+
+        """
+        if scrapli_response is None:
+            return False
+        if isinstance(scrapli_response, Response):
+            failed: bool = scrapli_response.failed
+            return failed
+        if any([response.failed for response in scrapli_response]):
+            return True
+        return False
+        
+    
+
+ + + +## Functions + + + +#### process_command_result +`process_command_result(scrapli_response: Union[scrapli.response.Response, scrapli.response.MultiResponse]) ‑> str` + +```text +Process and return string of scrapli response(s) + +Args: + scrapli_response: scrapli Response or MultiResponse object + +Returns: + str: string result from nornir task or None + +Raises: + N/A +``` + + + + + + +#### process_config_result +`process_config_result(scrapli_response: Union[scrapli.response.Response, scrapli.response.MultiResponse]) ‑> str` + +```text +Process and return string of scrapli response(s) + +Args: + scrapli_response: scrapli Response or MultiResponse object + +Returns: + str: string result from nornir task or None + +Raises: + N/A +``` + + + + +## Classes + +### ScrapliResult + + +```text +Result of running individual tasks. + +Arguments: + changed (bool): ``True`` if the task is changing the system + diff (obj): Diff between state of the system before/after running this task + result (obj): Result of the task execution, see task's documentation for details + host (:obj:`nornir.core.inventory.Host`): Reference to the host that lead ot this result + failed (bool): Whether the execution failed or not + severity_level (logging.LEVEL): Severity level associated to the result of the excecution + exception (Exception): uncaught exception thrown during the exection of the task (if any) + +Attributes: + changed (bool): ``True`` if the task is changing the system + diff (obj): Diff between state of the system before/after running this task + result (obj): Result of the task execution, see task's documentation for details + host (:obj:`nornir.core.inventory.Host`): Reference to the host that lead ot this result + failed (bool): Whether the execution failed or not + severity_level (logging.LEVEL): Severity level associated to the result of the excecution + exception (Exception): uncaught exception thrown during the exection of the task (if any) + +Scrapli Nornir Result object + +A "normal" nornir result object with an additional attribute "scrapli_response" which houses +the original response object returned from scrapli + +Args: + host: nornir task host object + result: result text returned from scrapli task + scrapli_response: original response object returned from scrapli task + changed: bool indicating if a change has occurred + kwargs: keyword arguments to pass to nornir Result + +Returns: + N/A # noqa: DAR202 + +Raises: + N/A +``` + +
+ + Expand source code + +
+        
+class ScrapliResult(Result):  # type: ignore
+    def __init__(
+        self,
+        host: "Host",
+        result: Optional[str],
+        scrapli_response: Optional[Union[Response, MultiResponse]] = None,
+        changed: bool = False,
+        **kwargs: Any,
+    ):
+        """
+        Scrapli Nornir Result object
+
+        A "normal" nornir result object with an additional attribute "scrapli_response" which houses
+        the original response object returned from scrapli
+
+        Args:
+            host: nornir task host object
+            result: result text returned from scrapli task
+            scrapli_response: original response object returned from scrapli task
+            changed: bool indicating if a change has occurred
+            kwargs: keyword arguments to pass to nornir Result
+
+        Returns:
+            N/A  # noqa: DAR202
+
+        Raises:
+            N/A
+
+        """
+        failed = self._process_failed(scrapli_response=scrapli_response)
+
+        super().__init__(host=host, result=result, failed=failed, changed=changed, **kwargs)
+
+        self.scrapli_response = scrapli_response
+
+    @staticmethod
+    def _process_failed(scrapli_response: Optional[Union[Response, MultiResponse]]) -> bool:
+        """
+        Process and return string of scrapli response(s)
+
+        Args:
+            scrapli_response: scrapli Response or MultiResponse object
+
+        Returns:
+            bool: bool indicating if the nornir task failed
+
+        Raises:
+            N/A
+
+        """
+        if scrapli_response is None:
+            return False
+        if isinstance(scrapli_response, Response):
+            failed: bool = scrapli_response.failed
+            return failed
+        if any([response.failed for response in scrapli_response]):
+            return True
+        return False
+        
+    
+
+ + +#### Ancestors (in MRO) +- nornir.core.task.Result \ No newline at end of file diff --git a/docs/api_docs/tasks.md b/docs/api_docs/tasks.md new file mode 100644 index 0000000..1bf2e42 --- /dev/null +++ b/docs/api_docs/tasks.md @@ -0,0 +1,655 @@ + + + + + + + + + + + + + + + + + + + + +#Module nornir_scrapli.tasks + +nornir_scrapli.tasks + +
+ + Expand source code + +
+        
+"""nornir_scrapli.tasks"""
+from nornir_scrapli.tasks.get_prompt import get_prompt
+from nornir_scrapli.tasks.netconf_capabilities import netconf_capabilities
+from nornir_scrapli.tasks.netconf_commit import netconf_commit
+from nornir_scrapli.tasks.netconf_delete_config import netconf_delete_config
+from nornir_scrapli.tasks.netconf_discard import netconf_discard
+from nornir_scrapli.tasks.netconf_edit_config import netconf_edit_config
+from nornir_scrapli.tasks.netconf_get import netconf_get
+from nornir_scrapli.tasks.netconf_get_config import netconf_get_config
+from nornir_scrapli.tasks.netconf_lock import netconf_lock
+from nornir_scrapli.tasks.netconf_rpc import netconf_rpc
+from nornir_scrapli.tasks.netconf_unlock import netconf_unlock
+from nornir_scrapli.tasks.netconf_validate import netconf_validate
+from nornir_scrapli.tasks.send_command import send_command
+from nornir_scrapli.tasks.send_commands import send_commands
+from nornir_scrapli.tasks.send_commands_from_file import send_commands_from_file
+from nornir_scrapli.tasks.send_config import send_config
+from nornir_scrapli.tasks.send_configs import send_configs
+from nornir_scrapli.tasks.send_configs_from_file import send_configs_from_file
+from nornir_scrapli.tasks.send_interactive import send_interactive
+
+__all__ = (
+    "get_prompt",
+    "netconf_capabilities",
+    "netconf_commit",
+    "netconf_delete_config",
+    "netconf_discard",
+    "netconf_edit_config",
+    "netconf_get",
+    "netconf_get_config",
+    "netconf_lock",
+    "netconf_rpc",
+    "netconf_unlock",
+    "netconf_validate",
+    "send_command",
+    "send_commands",
+    "send_commands_from_file",
+    "send_config",
+    "send_configs",
+    "send_configs_from_file",
+    "send_interactive",
+)
+        
+    
+
+ + + +## Functions + + + +#### get_prompt +`get_prompt(task: nornir.core.task.Task) ‑> nornir.core.task.Result` + +```text +Get current prompt from device using scrapli + +Args: + task: nornir task object + +Returns: + Result: nornir result object with Result.result value set to current prompt + +Raises: + N/A +``` + + + + + + +#### netconf_capabilities +`netconf_capabilities(task: nornir.core.task.Task) ‑> nornir.core.task.Result` + +```text +Retrieve the device config with scrapli_netconf + +Args: + task: nornir task object + +Returns: + Result: nornir result object with Result.result value set to a list of strings representing + the device capabilities + +Raises: + N/A +``` + + + + + + +#### netconf_commit +`netconf_commit(task: nornir.core.task.Task) ‑> nornir.core.task.Result` + +```text +Commit the device config with scrapli_netconf + +Args: + task: nornir task object + +Returns: + Result: nornir result object with Result.result value set the string result of the + get operation + +Raises: + N/A +``` + + + + + + +#### netconf_delete_config +`netconf_delete_config(task: nornir.core.task.Task, target: str = 'candidate') ‑> nornir.core.task.Result` + +```text +Send a "delete-config" rcp to the device with scrapli_netconf + +Args: + task: nornir task object + target: configuration source to target; startup|candidate + +Returns: + Result: nornir result object with Result.result value set the string result of the + delete operation + +Raises: + N/A +``` + + + + + + +#### netconf_discard +`netconf_discard(task: nornir.core.task.Task) ‑> nornir.core.task.Result` + +```text +Discard the device config with scrapli_netconf + +Args: + task: nornir task object + +Returns: + Result: nornir result object with Result.result value set the string result of the + get operation + +Raises: + N/A +``` + + + + + + +#### netconf_edit_config +`netconf_edit_config(task: nornir.core.task.Task, config: str, dry_run: Union[bool, NoneType] = None, diff: bool = False, target: str = 'running') ‑> nornir.core.task.Result` + +```text +Edit config from the device with scrapli_netconf + +Args: + task: nornir task object + config: configuration to send to device + dry_run: if True config will be pushed and then discarded; will discard anything already + pushed that has *not* been committed already, so be careful! :D; also note that this + will only work if there is a candidate datastore -- meaning that, for example, with + IOSXE with a target of "running" there is no way to discard the configuration as it will + already have been written to the running datastore + diff: capture/set diff of target datastore xml text of before/after edit config operation + target: configuration source to target; running|startup|candidate + +Returns: + Result: nornir result object with Result.result value set the string result of the + get_config operation + +Raises: + N/A +``` + + + + + + +#### netconf_get +`netconf_get(task: nornir.core.task.Task, filter_: str, filter_type: str = 'subtree') ‑> nornir.core.task.Result` + +```text +Get from the device with scrapli_netconf + +Args: + task: nornir task object + filter_: string filter to apply to the get + filter_type: type of filter; subtree|xpath + +Returns: + Result: nornir result object with Result.result value set the string result of the + get operation + +Raises: + N/A +``` + + + + + + +#### netconf_get_config +`netconf_get_config(task: nornir.core.task.Task, source: str = 'running', filters: Union[str, List[str], NoneType] = None, filter_type: str = 'subtree') ‑> nornir.core.task.Result` + +```text +Get config from the device with scrapli_netconf + +Args: + task: nornir task object + source: configuration source to get; typically one of running|startup|candidate + filters: string or list of strings of filters to apply to configuration + filter_type: type of filter; subtree|xpath + +Returns: + Result: nornir result object with Result.result value set the string result of the + get_config operation + +Raises: + N/A +``` + + + + + + +#### netconf_lock +`netconf_lock(task: nornir.core.task.Task, target: str) ‑> nornir.core.task.Result` + +```text +Lock the device with scrapli_netconf + +Args: + task: nornir task object + target: configuration source to target; running|startup|candidate + +Returns: + Result: nornir result object with Result.result value set the string result of the + get operation + +Raises: + N/A +``` + + + + + + +#### netconf_rpc +`netconf_rpc(task: nornir.core.task.Task, filter_: str) ‑> nornir.core.task.Result` + +```text +Send a "bare" rcp to the device with scrapli_netconf + +Args: + task: nornir task object + filter_: filter/rpc to execute + +Returns: + Result: nornir result object with Result.result value set the string result of the + rpc operation + +Raises: + N/A +``` + + + + + + +#### netconf_unlock +`netconf_unlock(task: nornir.core.task.Task, target: str) ‑> nornir.core.task.Result` + +```text +Unlock the device with scrapli_netconf + +Args: + task: nornir task object + target: configuration source to target; running|startup|candidate + +Returns: + Result: nornir result object with Result.result value set the string result of the + get operation + +Raises: + N/A +``` + + + + + + +#### netconf_validate +`netconf_validate(task: nornir.core.task.Task, source: str) ‑> nornir.core.task.Result` + +```text +Send a "validate" rcp to the device with scrapli_netconf + +Args: + task: nornir task object + source: configuration source to validate; typically one of running|startup|candidate + +Returns: + Result: nornir result object with Result.result value set the string result of the + get operation + +Raises: + N/A +``` + + + + + + +#### send_command +`send_command(task: nornir.core.task.Task, command: str, strip_prompt: bool = True, failed_when_contains: Union[str, List[str], NoneType] = None, timeout_ops: Union[float, NoneType] = None) ‑> nornir.core.task.Result` + +```text +Send a single command to device using scrapli + +Args: + task: nornir task object + command: string to send to device in privilege exec mode + strip_prompt: True/False strip prompt from returned output + failed_when_contains: string or list of strings indicating failure if found in response + timeout_ops: timeout ops value for this operation; only sets the timeout_ops value for + the duration of the operation, value is reset to initial value after operation is + completed + +Returns: + Result: scrapli nornir result object; almost identical to a "normal" nornir result object, + but contains an additional attribute "scrapli_response" that contains the original + response from scrapli + +Raises: + N/A +``` + + + + + + +#### send_commands +`send_commands(task: nornir.core.task.Task, commands: List[str], strip_prompt: bool = True, failed_when_contains: Union[str, List[str], NoneType] = None, stop_on_failed: bool = False, eager: bool = False, timeout_ops: Union[float, NoneType] = None) ‑> nornir.core.task.Result` + +```text +Send a list of commands to device using scrapli + +Args: + task: nornir task object + commands: list of strings to send to device in privilege exec mode + strip_prompt: True/False strip prompt from returned output + failed_when_contains: string or list of strings indicating failure if found in response + stop_on_failed: True/False stop executing commands if a command fails, returns results as of + current execution + eager: if eager is True we do not read until prompt is seen at each command sent to the + channel. Do *not* use this unless you know what you are doing as it is possible that + it can make scrapli less reliable! + timeout_ops: timeout ops value for this operation; only sets the timeout_ops value for + the duration of the operation, value is reset to initial value after operation is + completed. Note that this is the timeout value PER COMMAND sent, not for the total + of the commands being sent! + +Returns: + Result: nornir result object with Result.result value set to returned scrapli Response + object + +Raises: + N/A +``` + + + + + + +#### send_commands_from_file +`send_commands_from_file(task: nornir.core.task.Task, file: str, strip_prompt: bool = True, failed_when_contains: Union[str, List[str], NoneType] = None, stop_on_failed: bool = False, eager: bool = False, timeout_ops: Union[float, NoneType] = None) ‑> nornir.core.task.Result` + +```text +Send a list of commands from a file to device using scrapli + +Args: + task: nornir task object + file: string path to file + strip_prompt: True/False strip prompt from returned output + failed_when_contains: string or list of strings indicating failure if found in response + stop_on_failed: True/False stop executing commands if a command fails, returns results as of + current execution + eager: if eager is True we do not read until prompt is seen at each command sent to the + channel. Do *not* use this unless you know what you are doing as it is possible that + it can make scrapli less reliable! + timeout_ops: timeout ops value for this operation; only sets the timeout_ops value for + the duration of the operation, value is reset to initial value after operation is + completed + +Returns: + Result: nornir result object with Result.result value set to returned scrapli Response + object + +Raises: + N/A +``` + + + + + + +#### send_config +`send_config(task: nornir.core.task.Task, config: str, dry_run: Union[bool, NoneType] = None, strip_prompt: bool = True, failed_when_contains: Union[str, List[str], NoneType] = None, stop_on_failed: bool = False, privilege_level: str = '', eager: bool = False, timeout_ops: Union[float, NoneType] = None) ‑> nornir.core.task.Result` + +```text +Send a config to device using scrapli + +Args: + task: nornir task object + config: string configuration to send to the device, supports sending multi-line strings + dry_run: Whether to apply changes or not; if dry run, will ensure that it is possible to + enter config mode, but will NOT send any configs + strip_prompt: True/False strip prompt from returned output + failed_when_contains: string or list of strings indicating failure if found in response + stop_on_failed: True/False stop executing commands if a command fails, returns results as of + current execution + privilege_level: name of configuration privilege level/type to acquire; this is platform + dependent, so check the device driver for specifics. Examples of privilege_name + would be "configuration_exclusive" for IOSXRDriver, or "configuration_private" for + JunosDriver. You can also pass in a name of a configuration session such as + "my-config-session" if you have registered a session using the + "register_config_session" method of the EOSDriver or NXOSDriver. + eager: if eager is True we do not read until prompt is seen at each command sent to the + channel. Do *not* use this unless you know what you are doing as it is possible that + it can make scrapli less reliable! + timeout_ops: timeout ops value for this operation; only sets the timeout_ops value for + the duration of the operation, value is reset to initial value after operation is + completed. Note that this is the timeout value PER CONFIG sent, not for the total + of the configs being sent! + +Returns: + Result: nornir result object with Result.result value set to returned scrapli Response + object + +Raises: + NornirScrapliNoConfigModeGenericDriver: If attempting to use this task function against a + host that is using the "generic" platform type +``` + + + + + + +#### send_configs +`send_configs(task: nornir.core.task.Task, configs: List[str], dry_run: Union[bool, NoneType] = None, strip_prompt: bool = True, failed_when_contains: Union[str, List[str], NoneType] = None, stop_on_failed: bool = False, privilege_level: str = '', eager: bool = False, timeout_ops: Union[float, NoneType] = None) ‑> nornir.core.task.Result` + +```text +Send configs to device using scrapli + +Args: + task: nornir task object + configs: list of strings to send to device in config mode + dry_run: Whether to apply changes or not; if dry run, will ensure that it is possible to + enter config mode, but will NOT send any configs + strip_prompt: True/False strip prompt from returned output + failed_when_contains: string or list of strings indicating failure if found in response + stop_on_failed: True/False stop executing commands if a command fails, returns results as of + current execution + privilege_level: name of configuration privilege level/type to acquire; this is platform + dependent, so check the device driver for specifics. Examples of privilege_name + would be "configuration_exclusive" for IOSXRDriver, or "configuration_private" for + JunosDriver. You can also pass in a name of a configuration session such as + "my-config-session" if you have registered a session using the + "register_config_session" method of the EOSDriver or NXOSDriver. + eager: if eager is True we do not read until prompt is seen at each command sent to the + channel. Do *not* use this unless you know what you are doing as it is possible that + it can make scrapli less reliable! + timeout_ops: timeout ops value for this operation; only sets the timeout_ops value for + the duration of the operation, value is reset to initial value after operation is + completed. Note that this is the timeout value PER CONFIG sent, not for the total + of the configs being sent! + +Returns: + Result: nornir result object with Result.result value set to returned scrapli Response + object + +Raises: + NornirScrapliNoConfigModeGenericDriver: If attempting to use this task function against a + host that is using the "generic" platform type +``` + + + + + + +#### send_configs_from_file +`send_configs_from_file(task: nornir.core.task.Task, file: str, dry_run: Union[bool, NoneType] = None, strip_prompt: bool = True, failed_when_contains: Union[str, List[str], NoneType] = None, stop_on_failed: bool = False, privilege_level: str = '', eager: bool = False, timeout_ops: Union[float, NoneType] = None) ‑> nornir.core.task.Result` + +```text +Send configs from a file to device using scrapli + +Args: + task: nornir task object + file: string path to file + dry_run: Whether to apply changes or not; if dry run, will ensure that it is possible to + enter config mode, but will NOT send any configs + strip_prompt: True/False strip prompt from returned output + failed_when_contains: string or list of strings indicating failure if found in response + stop_on_failed: True/False stop executing commands if a command fails, returns results as of + current execution + privilege_level: name of configuration privilege level/type to acquire; this is platform + dependent, so check the device driver for specifics. Examples of privilege_name + would be "configuration_exclusive" for IOSXRDriver, or "configuration_private" for + JunosDriver. You can also pass in a name of a configuration session such as + "my-config-session" if you have registered a session using the + "register_config_session" method of the EOSDriver or NXOSDriver. + eager: if eager is True we do not read until prompt is seen at each command sent to the + channel. Do *not* use this unless you know what you are doing as it is possible that + it can make scrapli less reliable! + timeout_ops: timeout ops value for this operation; only sets the timeout_ops value for + the duration of the operation, value is reset to initial value after operation is + completed. Note that this is the timeout value PER CONFIG sent, not for the total + of the configs being sent! + +Returns: + Result: nornir result object with Result.result value set to returned scrapli Response + object + +Raises: + NornirScrapliNoConfigModeGenericDriver: If attempting to use this task function against a + host that is using the "generic" platform type +``` + + + + + + +#### send_interactive +`send_interactive(task: nornir.core.task.Task, interact_events: List[Tuple[str, str, Union[bool, NoneType]]], failed_when_contains: Union[str, List[str], NoneType] = None, privilege_level: str = '', timeout_ops: Union[float, NoneType] = None) ‑> nornir.core.task.Result` + +```text +Send inputs in an interactive fashion using scrapli; usually used to handle prompts + +Used to interact with devices where prompts change per input, and where inputs may be hidden +such as in the case of a password input. This can be used to respond to challenges from +devices such as the confirmation for the command "clear logging" on IOSXE devices for +example. You may have as many elements in the "interact_events" list as needed, and each +element of that list should be a tuple of two or three elements. The first element is always +the input to send as a string, the second should be the expected response as a string, and +the optional third a bool for whether or not the input is "hidden" (i.e. password input) +An example where we need this sort of capability: + +``` +3560CX#copy flash: scp: +Source filename []? test1.txt +Address or name of remote host []? 172.31.254.100 +Destination username [carl]? +Writing test1.txt +Password: +Password: + Sink: C0644 639 test1.txt +! +639 bytes copied in 12.066 secs (53 bytes/sec) +3560CX# +``` + +To accomplish this we can use the following (in "native" scrapli): + +``` +interact = conn.channel.send_inputs_interact( + [ + ("copy flash: scp:", "Source filename []?", False), + ("test1.txt", "Address or name of remote host []?", False), + ("172.31.254.100", "Destination username [carl]?", False), + ("carl", "Password:", False), + ("super_secure_password", prompt, True), + ] +) +``` + +If we needed to deal with more prompts we could simply continue adding tuples to the list of +interact "events". + +Args: + task: nornir task object + interact_events: list of tuples containing the "interactions" with the device + each list element must have an input and an expected response, and may have an + optional bool for the third and final element -- the optional bool specifies if the + input that is sent to the device is "hidden" (ex: password), if the hidden param is + not provided it is assumed the input is "normal" (not hidden) + failed_when_contains: list of strings that, if present in final output, represent a + failed command/interaction + privilege_level: name of the privilege level to operate in + timeout_ops: timeout ops value for this operation; only sets the timeout_ops value for + the duration of the operation, value is reset to initial value after operation is + completed + +Returns: + Result: nornir result object with Result.result value set to returned scrapli Response + object + +Raises: + N/A +``` \ No newline at end of file diff --git a/docs/generate/generate_docs.py b/docs/generate/generate_docs.py new file mode 100644 index 0000000..17c59b1 --- /dev/null +++ b/docs/generate/generate_docs.py @@ -0,0 +1,44 @@ +"""nornir_scrapli.docs.generate""" +import pdoc +from pdoc import _render_template, tpl_lookup + +context = pdoc.Context() +module = pdoc.Module("nornir_scrapli", context=context) +pdoc.link_inheritance(context) +tpl_lookup.directories.insert(0, "docs/generate") + +doc_map = { + "nornir_scrapli.tasks": {"path": "tasks", "content": None}, + "nornir_scrapli.functions": {"path": "functions", "content": None}, + "nornir_scrapli.connection": {"path": "connection", "content": None}, + "nornir_scrapli.exceptions": {"path": "exceptions", "content": None}, + "nornir_scrapli.helper": {"path": "helper", "content": None}, + "nornir_scrapli.result": {"path": "result", "content": None}, +} + + +def recursive_mds(module): # noqa + """Recursively render mkdocs friendly markdown/html""" + yield module.name, _render_template("/mkdocs_markdown.mako", module=module) + for submod in module.submodules(): + yield from recursive_mds(submod) + + +def main(): + """Generate docs""" + for module_name, html in recursive_mds(module=module): + if module_name not in doc_map.keys(): + continue + + doc_map[module_name]["content"] = html + + for module_name, module_doc_data in doc_map.items(): + if not module_doc_data["content"]: + print(f"broken module {module_name}") + continue + with open(f"docs/api_docs/{module_doc_data['path']}.md", "w") as f: + f.write(module_doc_data["content"]) + + +if __name__ == "__main__": + main() diff --git a/docs/generate/mkdocs_markdown.mako b/docs/generate/mkdocs_markdown.mako new file mode 100644 index 0000000..5dccbcc --- /dev/null +++ b/docs/generate/mkdocs_markdown.mako @@ -0,0 +1,175 @@ + + + + + + + +<%def name="variable(var)" buffered="True"> + <% + annot = show_type_annotations and var.type_annotation() or '' + if annot: + annot = ': ' + annot + %> +`${var.name}${annot}` + +% if var.docstring: +```text +${var.docstring} +``` +% endif + + + +<%def name="function(func)" buffered="True"> + <% + returns = show_type_annotations and func.return_annotation() or '' + if returns: + returns = ' \N{non-breaking hyphen}> ' + returns + %> + +<%text>#### ${func.name} +`${func.name}(${", ".join(func.params(annotate=show_type_annotations))})${returns}` + +% if func.docstring: +```text +${func.docstring} +``` +% endif + + + +<%def name="method(meth)" buffered="True"> + <% + returns = show_type_annotations and meth.return_annotation() or '' + if returns: + returns = ' \N{non-breaking hyphen}> ' + returns + %> + +<%text>##### ${meth.name} +`${meth.name}(${", ".join(meth.params(annotate=show_type_annotations))})${returns}` + +% if meth.docstring: +```text +${meth.docstring} +``` +% endif + + + + +<%def name="class_(cls)" buffered="True"> +<%text>### ${cls.name} + + +% if cls.docstring: +```text +${cls.docstring} +``` +% endif + +
+ + Expand source code + +
+        
+${cls.source}
+        
+    
+
+ +<% + class_vars = cls.class_variables(show_inherited_members, sort=sort_identifiers) + static_methods = cls.functions(show_inherited_members, sort=sort_identifiers) + inst_vars = cls.instance_variables(show_inherited_members, sort=sort_identifiers) + methods = cls.methods(show_inherited_members, sort=sort_identifiers) + mro = cls.mro() + subclasses = cls.subclasses() +%> +% if mro: +<%text>#### Ancestors (in MRO) + % for c in mro: +- ${c.refname} + % endfor +% endif +% if subclasses: +<%text>#### Descendants + % for c in subclasses: +- ${c.refname} + % endfor +% endif +% if class_vars: +<%text>#### Class variables + % for v in class_vars: +${variable(v)} + % endfor +% endif +% if static_methods: +<%text>#### Static methods + % for f in static_methods: +${function(f)} + % endfor +% endif +% if inst_vars: +<%text>#### Instance variables + % for v in inst_vars: +${variable(v)} + % endfor +% endif +% if methods: +<%text>#### Methods + % for m in methods: +${method(m)} + % endfor +% endif + + + +## Start the output logic for an entire module. + +<% + variables = module.variables(sort=sort_identifiers) + classes = module.classes(sort=sort_identifiers) + functions = module.functions(sort=sort_identifiers) + submodules = module.submodules() +%> + +<%text>#Module ${module.name} + +${module.docstring} + +
+ + Expand source code + +
+        
+${module.source}
+        
+    
+
+ + +% if submodules: +<%text>## Sub-modules + % for m in submodules: +* ${m.name} + % endfor +% endif + +% if functions: +<%text>## Functions + % for f in functions: +${function(f)} + + % endfor +% endif + +% if classes: +<%text>## Classes + % for c in classes: +${class_(c)} + + % endfor +% endif diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..be07cb6 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,4 @@ +# nornir_scrapli + + +nornir_scrapli -- [scrapli](https://github.com/carlmontanari/scrapli)'s and [scrapli_netconf](https://github.com/scrapli/scrapli_netconf)'s plugin for nornir! diff --git a/docs/more_scrapli/scrapli.md b/docs/more_scrapli/scrapli.md new file mode 100644 index 0000000..f680c9b --- /dev/null +++ b/docs/more_scrapli/scrapli.md @@ -0,0 +1,5 @@ +# scrapli + + +[scrapli](https://github.com/carlmontanari/scrapli) ([docs](https://github.com/carlmontanari/scrapli)) is the +"parent" library on which scrapli_netconf is built. Check it out if you need to connect to devices with telnet or ssh! diff --git a/docs/more_scrapli/scrapli_community.md b/docs/more_scrapli/scrapli_community.md new file mode 100644 index 0000000..6d4eab7 --- /dev/null +++ b/docs/more_scrapli/scrapli_community.md @@ -0,0 +1,6 @@ +# scrapli Community + + +If you would like to use scrapli, but the platform(s) that you work with are not supported in the "core" scrapli +platforms, you should check out [scrapli_community](https://github.com/scrapli/scrapli_community)! This is the place +for users to share "non-core" scrapli platforms. diff --git a/docs/more_scrapli/scrapli_netconf.md b/docs/more_scrapli/scrapli_netconf.md new file mode 100644 index 0000000..18fa216 --- /dev/null +++ b/docs/more_scrapli/scrapli_netconf.md @@ -0,0 +1,8 @@ +# scrapli Netconf + + +[scrapli_netconf](https://github.com/scrapli/scrapli_netconf) ([docs](https://scrapli.github.io/scrapli_netconf/)) +is a netconf driver built on top of scrapli. The purpose of scrapli_netconf is to provide a fast, flexible, +thoroughly tested, well typed, well documented, simple API that supports both synchronous and asynchronous usage. +Working together scrapli and scrapli_netconf aim to provide a consistent (as is practical) look and feel when +automating devices over telnet, SSH, or netconf (over SSH). diff --git a/docs/more_scrapli/scrapli_stubs.md b/docs/more_scrapli/scrapli_stubs.md new file mode 100644 index 0000000..5f35bce --- /dev/null +++ b/docs/more_scrapli/scrapli_stubs.md @@ -0,0 +1,5 @@ +# scrapli Stubs + + +Do you __REALLY__ love typing and scrapli? Great news, type stubs for scrapli are generated periodically and updated +[here](https://github.com/scrapli/scrapli_stubs), enjoy! diff --git a/docs/nornir_scrapli/connection.html b/docs/nornir_scrapli/connection.html deleted file mode 100644 index a41aca2..0000000 --- a/docs/nornir_scrapli/connection.html +++ /dev/null @@ -1,712 +0,0 @@ - - - - - - -nornir_scrapli.connection API documentation - - - - - - - - - - - -
-
-
-

Module nornir_scrapli.connection

-
-
-

nornir_scrapli.connection

-
- -Expand source code - -
"""nornir_scrapli.connection"""
-from typing import Any, Dict, Optional
-
-from scrapli import Scrapli
-from scrapli.driver import GenericDriver
-from scrapli_netconf.driver import NetconfScrape
-
-from nornir.core.configuration import Config
-from nornir_scrapli.exceptions import NornirScrapliInvalidPlatform
-
-CONNECTION_NAME = "scrapli"
-
-PLATFORM_MAP = {
-    "ios": "cisco_iosxe",
-    "nxos": "cisco_nxos",
-    "iosxr": "cisco_iosxr",
-    "eos": "arista_eos",
-    "junos": "juniper_junos",
-}
-
-
-class ScrapliCore:
-    """Scrapli connection plugin for nornir"""
-
-    def open(
-        self,
-        hostname: Optional[str],
-        username: Optional[str],
-        password: Optional[str],
-        port: Optional[int],
-        platform: Optional[str],
-        extras: Optional[Dict[str, Any]] = None,
-        configuration: Optional[Config] = None,
-    ) -> None:
-        """
-        Open a scrapli connection to a device
-
-        Args:
-            hostname: hostname from nornir inventory
-            username: username from nornir inventory/connection_options for scrapli
-            password: password from nornir inventory/connection_options for scrapli
-            port: port from nornir inventory/connection_options for scrapli
-            platform: platform from nornir inventory/connection_options for scrapli
-            extras: extras dict from connection_options for scrapli -- pass all other scrapli
-                arguments here
-            configuration: nornir configuration
-
-        Returns:
-            N/A  # noqa: DAR202
-
-        Raises:
-            NornirScrapliInvalidPlatform: if no platform or an invalid scrapli/napalm platform
-                string is provided
-
-        """
-        extras = extras or {}
-        # 99.9% configuration will always be passed here... but to be consistent w/ the other
-        # plugins we'll leave the function signature same/same as the others
-        global_config = configuration.dict() if configuration else {}
-
-        parameters: Dict[str, Any] = {
-            "host": hostname,
-            "auth_username": username or "",
-            "auth_password": password or "",
-            "port": port or 22,
-            "ssh_config_file": global_config.get("ssh", {}).get("config_file", False),
-        }
-
-        # will override any of the configs from global nornir config (such as ssh config file) with
-        # options from "extras" (connection options)
-        parameters.update(extras)
-
-        if not platform:
-            raise NornirScrapliInvalidPlatform(
-                f"No `platform` provided in inventory for host `{hostname}`"
-            )
-
-        if platform in PLATFORM_MAP:
-            platform = PLATFORM_MAP.get(platform)
-
-        if platform == "generic":
-            connection = GenericDriver(**parameters)
-        else:
-            try:
-                connection = Scrapli(**parameters, platform=platform)  # type: ignore
-            except ModuleNotFoundError as exc:
-                raise NornirScrapliInvalidPlatform(
-                    f"Provided platform `{platform}` is not a valid scrapli or napalm platform, "
-                    "or is not a valid scrapli-community platform."
-                ) from exc
-
-        connection.open()
-        self.connection = connection  # pylint: disable=W0201
-
-    def close(self) -> None:
-        """
-        Close a scrapli connection to a device
-
-        Args:
-            N/A
-
-        Returns:
-            N/A  # noqa: DAR202
-
-        Raises:
-            N/A
-
-        """
-        self.connection.close()
-
-
-class ScrapliNetconf:
-    """Scrapli NETCONF connection plugin for nornir"""
-
-    def open(
-        self,
-        hostname: Optional[str],
-        username: Optional[str],
-        password: Optional[str],
-        port: Optional[int],
-        platform: Optional[str],
-        extras: Optional[Dict[str, Any]] = None,
-        configuration: Optional[Config] = None,
-    ) -> None:
-        """
-        Open a scrapli connection to a device
-
-        Args:
-            hostname: hostname from nornir inventory
-            username: username from nornir inventory/connection_options for scrapli
-            password: password from nornir inventory/connection_options for scrapli
-            port: port from nornir inventory/connection_options for scrapli
-            platform: platform from nornir inventory/connection_options for scrapli; ignored with
-                scrapli netconf
-            extras: extras dict from connection_options for scrapli -- pass all other scrapli
-                arguments here
-            configuration: nornir configuration
-
-        Returns:
-            N/A  # noqa: DAR202
-
-        Raises:
-            N/A
-
-        """
-        # platform is irrelevant for scrapli netconf for now
-        _ = platform
-        extras = extras or {}
-        # 99.9% configuration will always be passed here... but to be consistent w/ the other
-        # plugins we'll leave the function signature same/same as the others
-        global_config = configuration.dict() if configuration else {}
-
-        parameters: Dict[str, Any] = {
-            "host": hostname,
-            "auth_username": username or "",
-            "auth_password": password or "",
-            "port": port or 830,
-            "ssh_config_file": global_config.get("ssh", {}).get("config_file", False),
-        }
-
-        # will override any of the configs from global nornir config (such as ssh config file) with
-        # options from "extras" (connection options)
-        parameters.update(extras)
-
-        connection = NetconfScrape(**parameters)
-        connection.open()
-        self.connection = connection  # pylint: disable=W0201
-
-    def close(self) -> None:
-        """
-        Close a scrapli netconf connection to a device
-
-        Args:
-            N/A
-
-        Returns:
-            N/A  # noqa: DAR202
-
-        Raises:
-            N/A
-
-        """
-        self.connection.close()
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class ScrapliCore -
-
-

Scrapli connection plugin for nornir

-
- -Expand source code - -
class ScrapliCore:
-    """Scrapli connection plugin for nornir"""
-
-    def open(
-        self,
-        hostname: Optional[str],
-        username: Optional[str],
-        password: Optional[str],
-        port: Optional[int],
-        platform: Optional[str],
-        extras: Optional[Dict[str, Any]] = None,
-        configuration: Optional[Config] = None,
-    ) -> None:
-        """
-        Open a scrapli connection to a device
-
-        Args:
-            hostname: hostname from nornir inventory
-            username: username from nornir inventory/connection_options for scrapli
-            password: password from nornir inventory/connection_options for scrapli
-            port: port from nornir inventory/connection_options for scrapli
-            platform: platform from nornir inventory/connection_options for scrapli
-            extras: extras dict from connection_options for scrapli -- pass all other scrapli
-                arguments here
-            configuration: nornir configuration
-
-        Returns:
-            N/A  # noqa: DAR202
-
-        Raises:
-            NornirScrapliInvalidPlatform: if no platform or an invalid scrapli/napalm platform
-                string is provided
-
-        """
-        extras = extras or {}
-        # 99.9% configuration will always be passed here... but to be consistent w/ the other
-        # plugins we'll leave the function signature same/same as the others
-        global_config = configuration.dict() if configuration else {}
-
-        parameters: Dict[str, Any] = {
-            "host": hostname,
-            "auth_username": username or "",
-            "auth_password": password or "",
-            "port": port or 22,
-            "ssh_config_file": global_config.get("ssh", {}).get("config_file", False),
-        }
-
-        # will override any of the configs from global nornir config (such as ssh config file) with
-        # options from "extras" (connection options)
-        parameters.update(extras)
-
-        if not platform:
-            raise NornirScrapliInvalidPlatform(
-                f"No `platform` provided in inventory for host `{hostname}`"
-            )
-
-        if platform in PLATFORM_MAP:
-            platform = PLATFORM_MAP.get(platform)
-
-        if platform == "generic":
-            connection = GenericDriver(**parameters)
-        else:
-            try:
-                connection = Scrapli(**parameters, platform=platform)  # type: ignore
-            except ModuleNotFoundError as exc:
-                raise NornirScrapliInvalidPlatform(
-                    f"Provided platform `{platform}` is not a valid scrapli or napalm platform, "
-                    "or is not a valid scrapli-community platform."
-                ) from exc
-
-        connection.open()
-        self.connection = connection  # pylint: disable=W0201
-
-    def close(self) -> None:
-        """
-        Close a scrapli connection to a device
-
-        Args:
-            N/A
-
-        Returns:
-            N/A  # noqa: DAR202
-
-        Raises:
-            N/A
-
-        """
-        self.connection.close()
-
-

Methods

-
-
-def close(self) ‑> NoneType -
-
-

Close a scrapli connection to a device

-

Args

-

N/A

-

Returns

-

N/A -# noqa: DAR202

-

Raises

-

N/A

-
- -Expand source code - -
def close(self) -> None:
-    """
-    Close a scrapli connection to a device
-
-    Args:
-        N/A
-
-    Returns:
-        N/A  # noqa: DAR202
-
-    Raises:
-        N/A
-
-    """
-    self.connection.close()
-
-
-
-def open(self, hostname: Union[str, NoneType], username: Union[str, NoneType], password: Union[str, NoneType], port: Union[int, NoneType], platform: Union[str, NoneType], extras: Union[Dict[str, Any], NoneType] = None, configuration: Union[nornir.core.configuration.Config, NoneType] = None) ‑> NoneType -
-
-

Open a scrapli connection to a device

-

Args

-
-
hostname
-
hostname from nornir inventory
-
username
-
username from nornir inventory/connection_options for scrapli
-
password
-
password from nornir inventory/connection_options for scrapli
-
port
-
port from nornir inventory/connection_options for scrapli
-
platform
-
platform from nornir inventory/connection_options for scrapli
-
extras
-
extras dict from connection_options for scrapli – pass all other scrapli -arguments here
-
configuration
-
nornir configuration
-
-

Returns

-

N/A -# noqa: DAR202

-

Raises

-
-
NornirScrapliInvalidPlatform
-
if no platform or an invalid scrapli/napalm platform -string is provided
-
-
- -Expand source code - -
def open(
-    self,
-    hostname: Optional[str],
-    username: Optional[str],
-    password: Optional[str],
-    port: Optional[int],
-    platform: Optional[str],
-    extras: Optional[Dict[str, Any]] = None,
-    configuration: Optional[Config] = None,
-) -> None:
-    """
-    Open a scrapli connection to a device
-
-    Args:
-        hostname: hostname from nornir inventory
-        username: username from nornir inventory/connection_options for scrapli
-        password: password from nornir inventory/connection_options for scrapli
-        port: port from nornir inventory/connection_options for scrapli
-        platform: platform from nornir inventory/connection_options for scrapli
-        extras: extras dict from connection_options for scrapli -- pass all other scrapli
-            arguments here
-        configuration: nornir configuration
-
-    Returns:
-        N/A  # noqa: DAR202
-
-    Raises:
-        NornirScrapliInvalidPlatform: if no platform or an invalid scrapli/napalm platform
-            string is provided
-
-    """
-    extras = extras or {}
-    # 99.9% configuration will always be passed here... but to be consistent w/ the other
-    # plugins we'll leave the function signature same/same as the others
-    global_config = configuration.dict() if configuration else {}
-
-    parameters: Dict[str, Any] = {
-        "host": hostname,
-        "auth_username": username or "",
-        "auth_password": password or "",
-        "port": port or 22,
-        "ssh_config_file": global_config.get("ssh", {}).get("config_file", False),
-    }
-
-    # will override any of the configs from global nornir config (such as ssh config file) with
-    # options from "extras" (connection options)
-    parameters.update(extras)
-
-    if not platform:
-        raise NornirScrapliInvalidPlatform(
-            f"No `platform` provided in inventory for host `{hostname}`"
-        )
-
-    if platform in PLATFORM_MAP:
-        platform = PLATFORM_MAP.get(platform)
-
-    if platform == "generic":
-        connection = GenericDriver(**parameters)
-    else:
-        try:
-            connection = Scrapli(**parameters, platform=platform)  # type: ignore
-        except ModuleNotFoundError as exc:
-            raise NornirScrapliInvalidPlatform(
-                f"Provided platform `{platform}` is not a valid scrapli or napalm platform, "
-                "or is not a valid scrapli-community platform."
-            ) from exc
-
-    connection.open()
-    self.connection = connection  # pylint: disable=W0201
-
-
-
-
-
-class ScrapliNetconf -
-
-

Scrapli NETCONF connection plugin for nornir

-
- -Expand source code - -
class ScrapliNetconf:
-    """Scrapli NETCONF connection plugin for nornir"""
-
-    def open(
-        self,
-        hostname: Optional[str],
-        username: Optional[str],
-        password: Optional[str],
-        port: Optional[int],
-        platform: Optional[str],
-        extras: Optional[Dict[str, Any]] = None,
-        configuration: Optional[Config] = None,
-    ) -> None:
-        """
-        Open a scrapli connection to a device
-
-        Args:
-            hostname: hostname from nornir inventory
-            username: username from nornir inventory/connection_options for scrapli
-            password: password from nornir inventory/connection_options for scrapli
-            port: port from nornir inventory/connection_options for scrapli
-            platform: platform from nornir inventory/connection_options for scrapli; ignored with
-                scrapli netconf
-            extras: extras dict from connection_options for scrapli -- pass all other scrapli
-                arguments here
-            configuration: nornir configuration
-
-        Returns:
-            N/A  # noqa: DAR202
-
-        Raises:
-            N/A
-
-        """
-        # platform is irrelevant for scrapli netconf for now
-        _ = platform
-        extras = extras or {}
-        # 99.9% configuration will always be passed here... but to be consistent w/ the other
-        # plugins we'll leave the function signature same/same as the others
-        global_config = configuration.dict() if configuration else {}
-
-        parameters: Dict[str, Any] = {
-            "host": hostname,
-            "auth_username": username or "",
-            "auth_password": password or "",
-            "port": port or 830,
-            "ssh_config_file": global_config.get("ssh", {}).get("config_file", False),
-        }
-
-        # will override any of the configs from global nornir config (such as ssh config file) with
-        # options from "extras" (connection options)
-        parameters.update(extras)
-
-        connection = NetconfScrape(**parameters)
-        connection.open()
-        self.connection = connection  # pylint: disable=W0201
-
-    def close(self) -> None:
-        """
-        Close a scrapli netconf connection to a device
-
-        Args:
-            N/A
-
-        Returns:
-            N/A  # noqa: DAR202
-
-        Raises:
-            N/A
-
-        """
-        self.connection.close()
-
-

Methods

-
-
-def close(self) ‑> NoneType -
-
-

Close a scrapli netconf connection to a device

-

Args

-

N/A

-

Returns

-

N/A -# noqa: DAR202

-

Raises

-

N/A

-
- -Expand source code - -
def close(self) -> None:
-    """
-    Close a scrapli netconf connection to a device
-
-    Args:
-        N/A
-
-    Returns:
-        N/A  # noqa: DAR202
-
-    Raises:
-        N/A
-
-    """
-    self.connection.close()
-
-
-
-def open(self, hostname: Union[str, NoneType], username: Union[str, NoneType], password: Union[str, NoneType], port: Union[int, NoneType], platform: Union[str, NoneType], extras: Union[Dict[str, Any], NoneType] = None, configuration: Union[nornir.core.configuration.Config, NoneType] = None) ‑> NoneType -
-
-

Open a scrapli connection to a device

-

Args

-
-
hostname
-
hostname from nornir inventory
-
username
-
username from nornir inventory/connection_options for scrapli
-
password
-
password from nornir inventory/connection_options for scrapli
-
port
-
port from nornir inventory/connection_options for scrapli
-
platform
-
platform from nornir inventory/connection_options for scrapli; ignored with -scrapli netconf
-
extras
-
extras dict from connection_options for scrapli – pass all other scrapli -arguments here
-
configuration
-
nornir configuration
-
-

Returns

-

N/A -# noqa: DAR202

-

Raises

-

N/A

-
- -Expand source code - -
def open(
-    self,
-    hostname: Optional[str],
-    username: Optional[str],
-    password: Optional[str],
-    port: Optional[int],
-    platform: Optional[str],
-    extras: Optional[Dict[str, Any]] = None,
-    configuration: Optional[Config] = None,
-) -> None:
-    """
-    Open a scrapli connection to a device
-
-    Args:
-        hostname: hostname from nornir inventory
-        username: username from nornir inventory/connection_options for scrapli
-        password: password from nornir inventory/connection_options for scrapli
-        port: port from nornir inventory/connection_options for scrapli
-        platform: platform from nornir inventory/connection_options for scrapli; ignored with
-            scrapli netconf
-        extras: extras dict from connection_options for scrapli -- pass all other scrapli
-            arguments here
-        configuration: nornir configuration
-
-    Returns:
-        N/A  # noqa: DAR202
-
-    Raises:
-        N/A
-
-    """
-    # platform is irrelevant for scrapli netconf for now
-    _ = platform
-    extras = extras or {}
-    # 99.9% configuration will always be passed here... but to be consistent w/ the other
-    # plugins we'll leave the function signature same/same as the others
-    global_config = configuration.dict() if configuration else {}
-
-    parameters: Dict[str, Any] = {
-        "host": hostname,
-        "auth_username": username or "",
-        "auth_password": password or "",
-        "port": port or 830,
-        "ssh_config_file": global_config.get("ssh", {}).get("config_file", False),
-    }
-
-    # will override any of the configs from global nornir config (such as ssh config file) with
-    # options from "extras" (connection options)
-    parameters.update(extras)
-
-    connection = NetconfScrape(**parameters)
-    connection.open()
-    self.connection = connection  # pylint: disable=W0201
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/nornir_scrapli/exceptions.html b/docs/nornir_scrapli/exceptions.html deleted file mode 100644 index 94d9a19..0000000 --- a/docs/nornir_scrapli/exceptions.html +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - -nornir_scrapli.exceptions API documentation - - - - - - - - - - - -
-
-
-

Module nornir_scrapli.exceptions

-
-
-

nornir_scrapli.exceptions

-
- -Expand source code - -
"""nornir_scrapli.exceptions"""
-
-
-class NornirScrapliException(Exception):
-    """nornir_scrapli base exception"""
-
-
-class NornirScrapliInvalidPlatform(NornirScrapliException):
-    """nornir_scrapli base exception"""
-
-
-class NornirScrapliNoConfigModeGenericDriver(NornirScrapliException):
-    """nornir_scrapli exception for attempting config mode on generic platform"""
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class NornirScrapliException -(*args, **kwargs) -
-
-

nornir_scrapli base exception

-
- -Expand source code - -
class NornirScrapliException(Exception):
-    """nornir_scrapli base exception"""
-
-

Ancestors

-
    -
  • builtins.Exception
  • -
  • builtins.BaseException
  • -
-

Subclasses

- -
-
-class NornirScrapliInvalidPlatform -(*args, **kwargs) -
-
-

nornir_scrapli base exception

-
- -Expand source code - -
class NornirScrapliInvalidPlatform(NornirScrapliException):
-    """nornir_scrapli base exception"""
-
-

Ancestors

- -
-
-class NornirScrapliNoConfigModeGenericDriver -(*args, **kwargs) -
-
-

nornir_scrapli exception for attempting config mode on generic platform

-
- -Expand source code - -
class NornirScrapliNoConfigModeGenericDriver(NornirScrapliException):
-    """nornir_scrapli exception for attempting config mode on generic platform"""
-
-

Ancestors

- -
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/nornir_scrapli/functions/index.html b/docs/nornir_scrapli/functions/index.html deleted file mode 100644 index 920bb6f..0000000 --- a/docs/nornir_scrapli/functions/index.html +++ /dev/null @@ -1,157 +0,0 @@ - - - - - - -nornir_scrapli.functions API documentation - - - - - - - - - - - -
-
-
-

Module nornir_scrapli.functions

-
-
-

nornir_scrapli.functions

-
- -Expand source code - -
"""nornir_scrapli.functions"""
-from nornir_scrapli.functions.print_structured_result import print_structured_result
-
-__all__ = ("print_structured_result",)
-
-
-
-
-
-
-
-

Functions

-
-
-def print_structured_result(result: nornir.core.task.AggregatedResult, failed: bool = False, severity_level: int = 20, parser: str = 'textfsm', to_dict: bool = True, fail_to_string: bool = False) ‑> NoneType -
-
-

Prints the :obj:nornir.core.task.Result from a previous task to screen

-

Arguments

-

result: Nornir AggregateResult object from a previous task -failed: if True assume the task failed -severity_level: Print only errors with this severity level or higher -parser: textfsm|genie – parser to parse output with -to_dict: output structured data in dict form instead – basically put k:v instead of just -lists of lists of values for textfsm output; ignored if parser == "genie" -fail_to_string: fallback to printing unstructured output or have tasks skipped (because -print_result won't print empty lists which scrapli returns if parsing fails)

-
- -Expand source code - -
def print_structured_result(
-    result: AggregatedResult,
-    failed: bool = False,
-    severity_level: int = logging.INFO,
-    parser: str = "textfsm",
-    to_dict: bool = True,
-    fail_to_string: bool = False,
-) -> None:
-    """
-    Prints the :obj:`nornir.core.task.Result` from a previous task to screen
-
-    Arguments:
-        result: Nornir AggregateResult object from a previous task
-        failed: if `True` assume the task failed
-        severity_level: Print only errors with this severity level or higher
-        parser: textfsm|genie -- parser to parse output with
-        to_dict: output structured data in dict form instead -- basically put k:v instead of just
-            lists of lists of values for textfsm output; ignored if parser == "genie"
-        fail_to_string: fallback to printing unstructured output or have tasks skipped (because
-            print_result won't print empty lists which scrapli returns if parsing fails)
-
-    """
-    updated_agg_result = AggregatedResult(result.name)
-    for hostname, multi_result in result.items():
-        updated_multi_result = MultiResult(result.name)
-        for individual_result in multi_result:
-            scrapli_responses = getattr(individual_result, "scrapli_response", None)
-            if isinstance(scrapli_responses, Response):
-                scrapli_responses = [scrapli_responses]
-            if not scrapli_responses:
-                updated_multi_result.append(individual_result)
-                continue
-            for scrapli_response in scrapli_responses:
-                parser_method = getattr(scrapli_response, f"{parser}_parse_output")
-                updated_result = Result(
-                    host=individual_result.host,
-                    changed=individual_result.changed,
-                    diff=individual_result.diff,
-                    exception=individual_result.exception,
-                    failed=individual_result.failed,
-                    name=individual_result.name,
-                    severity_level=individual_result.severity_level,
-                    stderr=individual_result.stderr,
-                    stdout=individual_result.stdout,
-                )
-
-                if parser == "textfsm":
-                    structured_result = parser_method(to_dict=to_dict)
-                else:
-                    structured_result = parser_method()
-
-                if not structured_result and fail_to_string:
-                    updated_result.result = scrapli_response.result
-                else:
-                    updated_result.result = structured_result
-                updated_multi_result.append(updated_result)
-        if updated_multi_result:
-            updated_agg_result[hostname] = updated_multi_result  # noqa
-
-    LOCK.acquire()
-    try:
-        _print_result(
-            result=updated_agg_result, attrs=None, failed=failed, severity_level=severity_level
-        )
-    finally:
-        LOCK.release()
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/nornir_scrapli/helper.html b/docs/nornir_scrapli/helper.html deleted file mode 100644 index b5cb670..0000000 --- a/docs/nornir_scrapli/helper.html +++ /dev/null @@ -1,174 +0,0 @@ - - - - - - -nornir_scrapli.helper API documentation - - - - - - - - - - - -
-
-
-

Module nornir_scrapli.helper

-
-
-

nornir_scrapli.helper

-
- -Expand source code - -
"""nornir_scrapli.helper"""
-import difflib
-
-ANSI_GREEN = "\033[92m"
-ANSI_RED = "\033[91m"
-ANSI_END = "\033[0m"
-
-
-def diff_xml_text(document_one: str, document_two: str) -> str:
-    """
-    Diff xml text strings
-
-    Really could be just "diff text" but also ensuring we ignore the "message-id" lines. This is
-    really pretty simple and not always super great, but better than nothing for now!
-
-    Args:
-        document_one: string of xml doc 1
-        document_two: string of xml doc 2
-
-    Returns:
-        str: unified diff of the two input documents
-
-    Raises:
-        N/A
-
-    """
-    # ignore message-id stuff -- maybe more in the future?
-    document_one_lines = [line for line in document_one.splitlines() if "message-id" not in line]
-    document_two_lines = [line for line in document_two.splitlines() if "message-id" not in line]
-    diff = difflib.unified_diff(document_one_lines, document_two_lines)
-
-    diff_lines = []
-    for line in diff:
-        if line.startswith("---") or line.startswith("+++"):
-            # may as well just strip out the header lines and such, we dont care about them
-            continue
-        if line.startswith("+"):
-            diff_lines.append(f"{ANSI_GREEN}{line}{ANSI_END}")
-        elif line.startswith("-"):
-            diff_lines.append(f"{ANSI_RED}{line}{ANSI_END}")
-        else:
-            diff_lines.append(line)
-
-    return "\n".join(diff_lines)
-
-
-
-
-
-
-
-

Functions

-
-
-def diff_xml_text(document_one: str, document_two: str) ‑> str -
-
-

Diff xml text strings

-

Really could be just "diff text" but also ensuring we ignore the "message-id" lines. This is -really pretty simple and not always super great, but better than nothing for now!

-

Args

-
-
document_one
-
string of xml doc 1
-
document_two
-
string of xml doc 2
-
-

Returns

-
-
str
-
unified diff of the two input documents
-
-

Raises

-

N/A

-
- -Expand source code - -
def diff_xml_text(document_one: str, document_two: str) -> str:
-    """
-    Diff xml text strings
-
-    Really could be just "diff text" but also ensuring we ignore the "message-id" lines. This is
-    really pretty simple and not always super great, but better than nothing for now!
-
-    Args:
-        document_one: string of xml doc 1
-        document_two: string of xml doc 2
-
-    Returns:
-        str: unified diff of the two input documents
-
-    Raises:
-        N/A
-
-    """
-    # ignore message-id stuff -- maybe more in the future?
-    document_one_lines = [line for line in document_one.splitlines() if "message-id" not in line]
-    document_two_lines = [line for line in document_two.splitlines() if "message-id" not in line]
-    diff = difflib.unified_diff(document_one_lines, document_two_lines)
-
-    diff_lines = []
-    for line in diff:
-        if line.startswith("---") or line.startswith("+++"):
-            # may as well just strip out the header lines and such, we dont care about them
-            continue
-        if line.startswith("+"):
-            diff_lines.append(f"{ANSI_GREEN}{line}{ANSI_END}")
-        elif line.startswith("-"):
-            diff_lines.append(f"{ANSI_RED}{line}{ANSI_END}")
-        else:
-            diff_lines.append(line)
-
-    return "\n".join(diff_lines)
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/nornir_scrapli/index.html b/docs/nornir_scrapli/index.html deleted file mode 100644 index ade18fd..0000000 --- a/docs/nornir_scrapli/index.html +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - -nornir_scrapli API documentation - - - - - - - - - - - -
- - -
- - - \ No newline at end of file diff --git a/docs/nornir_scrapli/result.html b/docs/nornir_scrapli/result.html deleted file mode 100644 index 9c172bd..0000000 --- a/docs/nornir_scrapli/result.html +++ /dev/null @@ -1,406 +0,0 @@ - - - - - - -nornir_scrapli.result API documentation - - - - - - - - - - - -
-
-
-

Module nornir_scrapli.result

-
-
-

nornir_scrapli.result

-
- -Expand source code - -
"""nornir_scrapli.result"""
-from typing import TYPE_CHECKING, Any, Optional, Union
-
-from scrapli.response import MultiResponse, Response
-
-from nornir.core.task import Result
-
-if TYPE_CHECKING:
-    from nornir.core.inventory import Host  # pylint: disable=C0412
-
-
-def process_command_result(scrapli_response: Union[Response, MultiResponse]) -> str:
-    """
-    Process and return string of scrapli response(s)
-
-    Args:
-        scrapli_response: scrapli Response or MultiResponse object
-
-    Returns:
-        str: string result from nornir task or None
-
-    Raises:
-        N/A
-
-    """
-    if isinstance(scrapli_response, Response):
-        result: str = scrapli_response.result
-        return result
-    return "\n\n".join([response.result for response in scrapli_response])
-
-
-def process_config_result(scrapli_response: Union[Response, MultiResponse]) -> str:
-    """
-    Process and return string of scrapli response(s)
-
-    Args:
-        scrapli_response: scrapli Response or MultiResponse object
-
-    Returns:
-        str: string result from nornir task or None
-
-    Raises:
-        N/A
-
-    """
-    full_results = ""
-    if isinstance(scrapli_response, Response):
-        for config_input, config_result in zip(
-            scrapli_response.channel_input.split("\n"), scrapli_response.result.split("\n")
-        ):
-            if config_input == config_result:
-                full_results += f"{config_input}\n"
-            else:
-                full_results += "\n".join([config_input, config_result])
-    else:
-        for response in scrapli_response:
-            full_results += "\n".join([response.channel_input, response.result])
-    return full_results
-
-
-class ScrapliResult(Result):  # type: ignore
-    def __init__(
-        self,
-        host: "Host",
-        result: Optional[str],
-        scrapli_response: Optional[Union[Response, MultiResponse]] = None,
-        changed: bool = False,
-        **kwargs: Any,
-    ):
-        """
-        Scrapli Nornir Result object
-
-        A "normal" nornir result object with an additional attribute "scrapli_response" which houses
-        the original response object returned from scrapli
-
-        Args:
-            host: nornir task host object
-            result: result text returned from scrapli task
-            scrapli_response: original response object returned from scrapli task
-            changed: bool indicating if a change has occurred
-            kwargs: keyword arguments to pass to nornir Result
-
-        Returns:
-            N/A  # noqa: DAR202
-
-        Raises:
-            N/A
-
-        """
-        failed = self._process_failed(scrapli_response=scrapli_response)
-
-        super().__init__(host=host, result=result, failed=failed, changed=changed, **kwargs)
-
-        self.scrapli_response = scrapli_response
-
-    @staticmethod
-    def _process_failed(scrapli_response: Optional[Union[Response, MultiResponse]]) -> bool:
-        """
-        Process and return string of scrapli response(s)
-
-        Args:
-            scrapli_response: scrapli Response or MultiResponse object
-
-        Returns:
-            bool: bool indicating if the nornir task failed
-
-        Raises:
-            N/A
-
-        """
-        if scrapli_response is None:
-            return False
-        if isinstance(scrapli_response, Response):
-            failed: bool = scrapli_response.failed
-            return failed
-        if any([response.failed for response in scrapli_response]):
-            return True
-        return False
-
-
-
-
-
-
-
-

Functions

-
-
-def process_command_result(scrapli_response: Union[scrapli.response.Response, scrapli.response.MultiResponse]) ‑> str -
-
-

Process and return string of scrapli response(s)

-

Args

-
-
scrapli_response
-
scrapli Response or MultiResponse object
-
-

Returns

-
-
str
-
string result from nornir task or None
-
-

Raises

-

N/A

-
- -Expand source code - -
def process_command_result(scrapli_response: Union[Response, MultiResponse]) -> str:
-    """
-    Process and return string of scrapli response(s)
-
-    Args:
-        scrapli_response: scrapli Response or MultiResponse object
-
-    Returns:
-        str: string result from nornir task or None
-
-    Raises:
-        N/A
-
-    """
-    if isinstance(scrapli_response, Response):
-        result: str = scrapli_response.result
-        return result
-    return "\n\n".join([response.result for response in scrapli_response])
-
-
-
-def process_config_result(scrapli_response: Union[scrapli.response.Response, scrapli.response.MultiResponse]) ‑> str -
-
-

Process and return string of scrapli response(s)

-

Args

-
-
scrapli_response
-
scrapli Response or MultiResponse object
-
-

Returns

-
-
str
-
string result from nornir task or None
-
-

Raises

-

N/A

-
- -Expand source code - -
def process_config_result(scrapli_response: Union[Response, MultiResponse]) -> str:
-    """
-    Process and return string of scrapli response(s)
-
-    Args:
-        scrapli_response: scrapli Response or MultiResponse object
-
-    Returns:
-        str: string result from nornir task or None
-
-    Raises:
-        N/A
-
-    """
-    full_results = ""
-    if isinstance(scrapli_response, Response):
-        for config_input, config_result in zip(
-            scrapli_response.channel_input.split("\n"), scrapli_response.result.split("\n")
-        ):
-            if config_input == config_result:
-                full_results += f"{config_input}\n"
-            else:
-                full_results += "\n".join([config_input, config_result])
-    else:
-        for response in scrapli_response:
-            full_results += "\n".join([response.channel_input, response.result])
-    return full_results
-
-
-
-
-
-

Classes

-
-
-class ScrapliResult -(host: Host, result: Union[str, NoneType], scrapli_response: Union[scrapli.response.Response, scrapli.response.MultiResponse, NoneType] = None, changed: bool = False, **kwargs: Any) -
-
-

Result of running individual tasks.

-

Arguments

-

changed (bool): True if the task is changing the system -diff (obj): Diff between state of the system before/after running this task -result (obj): Result of the task execution, see task's documentation for details -host (:obj:nornir.core.inventory.Host): Reference to the host that lead ot this result -failed (bool): Whether the execution failed or not -severity_level (logging.LEVEL): Severity level associated to the result of the excecution -exception (Exception): uncaught exception thrown during the exection of the task (if any)

-

Attributes

-
-
changed : bool
-
True if the task is changing the system
-
diff : obj
-
Diff between state of the system before/after running this task
-
result : obj
-
Result of the task execution, see task's documentation for details
-
host (:obj:nornir.core.inventory.Host): Reference to the host that lead ot this result
-
failed : bool
-
Whether the execution failed or not
-
severity_level : logging.LEVEL
-
Severity level associated to the result of the excecution
-
exception : Exception
-
uncaught exception thrown during the exection of the task (if any)
-
-

Scrapli Nornir Result object

-

A "normal" nornir result object with an additional attribute "scrapli_response" which houses -the original response object returned from scrapli

-

Args

-
-
host
-
nornir task host object
-
result
-
result text returned from scrapli task
-
scrapli_response
-
original response object returned from scrapli task
-
changed
-
bool indicating if a change has occurred
-
kwargs
-
keyword arguments to pass to nornir Result
-
-

Returns

-

N/A -# noqa: DAR202

-

Raises

-

N/A

-
- -Expand source code - -
class ScrapliResult(Result):  # type: ignore
-    def __init__(
-        self,
-        host: "Host",
-        result: Optional[str],
-        scrapli_response: Optional[Union[Response, MultiResponse]] = None,
-        changed: bool = False,
-        **kwargs: Any,
-    ):
-        """
-        Scrapli Nornir Result object
-
-        A "normal" nornir result object with an additional attribute "scrapli_response" which houses
-        the original response object returned from scrapli
-
-        Args:
-            host: nornir task host object
-            result: result text returned from scrapli task
-            scrapli_response: original response object returned from scrapli task
-            changed: bool indicating if a change has occurred
-            kwargs: keyword arguments to pass to nornir Result
-
-        Returns:
-            N/A  # noqa: DAR202
-
-        Raises:
-            N/A
-
-        """
-        failed = self._process_failed(scrapli_response=scrapli_response)
-
-        super().__init__(host=host, result=result, failed=failed, changed=changed, **kwargs)
-
-        self.scrapli_response = scrapli_response
-
-    @staticmethod
-    def _process_failed(scrapli_response: Optional[Union[Response, MultiResponse]]) -> bool:
-        """
-        Process and return string of scrapli response(s)
-
-        Args:
-            scrapli_response: scrapli Response or MultiResponse object
-
-        Returns:
-            bool: bool indicating if the nornir task failed
-
-        Raises:
-            N/A
-
-        """
-        if scrapli_response is None:
-            return False
-        if isinstance(scrapli_response, Response):
-            failed: bool = scrapli_response.failed
-            return failed
-        if any([response.failed for response in scrapli_response]):
-            return True
-        return False
-
-

Ancestors

-
    -
  • nornir.core.task.Result
  • -
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/nornir_scrapli/tasks/index.html b/docs/nornir_scrapli/tasks/index.html deleted file mode 100644 index dd90444..0000000 --- a/docs/nornir_scrapli/tasks/index.html +++ /dev/null @@ -1,1632 +0,0 @@ - - - - - - -nornir_scrapli.tasks API documentation - - - - - - - - - - - -
-
-
-

Module nornir_scrapli.tasks

-
-
-

nornir_scrapli.tasks

-
- -Expand source code - -
"""nornir_scrapli.tasks"""
-from nornir_scrapli.tasks.get_prompt import get_prompt
-from nornir_scrapli.tasks.netconf_capabilities import netconf_capabilities
-from nornir_scrapli.tasks.netconf_commit import netconf_commit
-from nornir_scrapli.tasks.netconf_delete_config import netconf_delete_config
-from nornir_scrapli.tasks.netconf_discard import netconf_discard
-from nornir_scrapli.tasks.netconf_edit_config import netconf_edit_config
-from nornir_scrapli.tasks.netconf_get import netconf_get
-from nornir_scrapli.tasks.netconf_get_config import netconf_get_config
-from nornir_scrapli.tasks.netconf_lock import netconf_lock
-from nornir_scrapli.tasks.netconf_rpc import netconf_rpc
-from nornir_scrapli.tasks.netconf_unlock import netconf_unlock
-from nornir_scrapli.tasks.netconf_validate import netconf_validate
-from nornir_scrapli.tasks.send_command import send_command
-from nornir_scrapli.tasks.send_commands import send_commands
-from nornir_scrapli.tasks.send_commands_from_file import send_commands_from_file
-from nornir_scrapli.tasks.send_config import send_config
-from nornir_scrapli.tasks.send_configs import send_configs
-from nornir_scrapli.tasks.send_configs_from_file import send_configs_from_file
-from nornir_scrapli.tasks.send_interactive import send_interactive
-
-__all__ = (
-    "get_prompt",
-    "netconf_capabilities",
-    "netconf_commit",
-    "netconf_delete_config",
-    "netconf_discard",
-    "netconf_edit_config",
-    "netconf_get",
-    "netconf_get_config",
-    "netconf_lock",
-    "netconf_rpc",
-    "netconf_unlock",
-    "netconf_validate",
-    "send_command",
-    "send_commands",
-    "send_commands_from_file",
-    "send_config",
-    "send_configs",
-    "send_configs_from_file",
-    "send_interactive",
-)
-
-
-
-
-
-
-
-

Functions

-
-
-def get_prompt(task: nornir.core.task.Task) ‑> nornir.core.task.Result -
-
-

Get current prompt from device using scrapli

-

Args

-
-
task
-
nornir task object
-
-

Returns

-
-
Result
-
nornir result object with Result.result value set to current prompt
-
-

Raises

-

N/A

-
- -Expand source code - -
def get_prompt(task: Task) -> Result:
-    """
-    Get current prompt from device using scrapli
-
-    Args:
-        task: nornir task object
-
-    Returns:
-        Result: nornir result object with Result.result value set to current prompt
-
-    Raises:
-        N/A
-
-    """
-    scrapli_conn = task.host.get_connection("scrapli", task.nornir.config)
-    prompt = scrapli_conn.get_prompt()
-    return Result(host=task.host, result=prompt, failed=False, changed=False)
-
-
-
-def netconf_capabilities(task: nornir.core.task.Task) ‑> nornir.core.task.Result -
-
-

Retrieve the device config with scrapli_netconf

-

Args

-
-
task
-
nornir task object
-
-

Returns

-
-
Result
-
nornir result object with Result.result value set to a list of strings representing -the device capabilities
-
-

Raises

-

N/A

-
- -Expand source code - -
def netconf_capabilities(
-    task: Task,
-) -> Result:
-    """
-    Retrieve the device config with scrapli_netconf
-
-    Args:
-        task: nornir task object
-
-    Returns:
-        Result: nornir result object with Result.result value set to a list of strings representing
-            the device capabilities
-
-    Raises:
-        N/A
-
-    """
-    scrapli_conn = task.host.get_connection("scrapli_netconf", task.nornir.config)
-
-    result = Result(
-        host=task.host,
-        result=scrapli_conn.server_capabilities,
-        failed=False,
-        changed=False,
-    )
-    return result
-
-
-
-def netconf_commit(task: nornir.core.task.Task) ‑> nornir.core.task.Result -
-
-

Commit the device config with scrapli_netconf

-

Args

-
-
task
-
nornir task object
-
-

Returns

-
-
Result
-
nornir result object with Result.result value set the string result of the -get operation
-
-

Raises

-

N/A

-
- -Expand source code - -
def netconf_commit(
-    task: Task,
-) -> Result:
-    """
-    Commit the device config with scrapli_netconf
-
-    Args:
-        task: nornir task object
-
-    Returns:
-        Result: nornir result object with Result.result value set the string result of the
-            get operation
-
-    Raises:
-        N/A
-
-    """
-    scrapli_conn = task.host.get_connection("scrapli_netconf", task.nornir.config)
-    scrapli_response = scrapli_conn.commit()
-
-    result = ScrapliResult(
-        host=task.host,
-        result=process_command_result(scrapli_response=scrapli_response),
-        scrapli_response=scrapli_response,
-        changed=True,
-    )
-    return result
-
-
-
-def netconf_delete_config(task: nornir.core.task.Task, target: str = 'candidate') ‑> nornir.core.task.Result -
-
-

Send a "delete-config" rcp to the device with scrapli_netconf

-

Args

-
-
task
-
nornir task object
-
target
-
configuration source to target; startup|candidate
-
-

Returns

-
-
Result
-
nornir result object with Result.result value set the string result of the -delete operation
-
-

Raises

-

N/A

-
- -Expand source code - -
def netconf_delete_config(
-    task: Task,
-    target: str = "candidate",
-) -> Result:
-    """
-    Send a "delete-config" rcp to the device with scrapli_netconf
-
-    Args:
-        task: nornir task object
-        target: configuration source to target; startup|candidate
-
-    Returns:
-        Result: nornir result object with Result.result value set the string result of the
-            delete operation
-
-    Raises:
-        N/A
-
-    """
-    scrapli_conn = task.host.get_connection("scrapli_netconf", task.nornir.config)
-    scrapli_response = scrapli_conn.delete_config(target=target)
-
-    result = ScrapliResult(
-        host=task.host,
-        result=process_command_result(scrapli_response=scrapli_response),
-        scrapli_response=scrapli_response,
-        changed=True,
-    )
-    return result
-
-
-
-def netconf_discard(task: nornir.core.task.Task) ‑> nornir.core.task.Result -
-
-

Discard the device config with scrapli_netconf

-

Args

-
-
task
-
nornir task object
-
-

Returns

-
-
Result
-
nornir result object with Result.result value set the string result of the -get operation
-
-

Raises

-

N/A

-
- -Expand source code - -
def netconf_discard(
-    task: Task,
-) -> Result:
-    """
-    Discard the device config with scrapli_netconf
-
-    Args:
-        task: nornir task object
-
-    Returns:
-        Result: nornir result object with Result.result value set the string result of the
-            get operation
-
-    Raises:
-        N/A
-
-    """
-    scrapli_conn = task.host.get_connection("scrapli_netconf", task.nornir.config)
-    scrapli_response = scrapli_conn.discard()
-
-    result = ScrapliResult(
-        host=task.host,
-        result=process_command_result(scrapli_response=scrapli_response),
-        scrapli_response=scrapli_response,
-        changed=True,
-    )
-    return result
-
-
-
-def netconf_edit_config(task: nornir.core.task.Task, config: str, dry_run: Union[bool, NoneType] = None, diff: bool = False, target: str = 'running') ‑> nornir.core.task.Result -
-
-

Edit config from the device with scrapli_netconf

-

Args

-
-
task
-
nornir task object
-
config
-
configuration to send to device
-
dry_run
-
if True config will be pushed and then discarded; will discard anything already -pushed that has not been committed already, so be careful! :D; also note that this -will only work if there is a candidate datastore – meaning that, for example, with -IOSXE with a target of "running" there is no way to discard the configuration as it will -already have been written to the running datastore
-
diff
-
capture/set diff of target datastore xml text of before/after edit config operation
-
target
-
configuration source to target; running|startup|candidate
-
-

Returns

-
-
Result
-
nornir result object with Result.result value set the string result of the -get_config operation
-
-

Raises

-

N/A

-
- -Expand source code - -
def netconf_edit_config(
-    task: Task,
-    config: str,
-    dry_run: Optional[bool] = None,
-    diff: bool = False,
-    target: str = "running",
-) -> Result:
-    """
-    Edit config from the device with scrapli_netconf
-
-    Args:
-        task: nornir task object
-        config: configuration to send to device
-        dry_run: if True config will be pushed and then discarded; will discard anything already
-            pushed that has *not* been committed already, so be careful! :D; also note that this
-            will only work if there is a candidate datastore -- meaning that, for example, with
-            IOSXE with a target of "running" there is no way to discard the configuration as it will
-            already have been written to the running datastore
-        diff: capture/set diff of target datastore xml text of before/after edit config operation
-        target: configuration source to target; running|startup|candidate
-
-    Returns:
-        Result: nornir result object with Result.result value set the string result of the
-            get_config operation
-
-    Raises:
-        N/A
-
-    """
-    scrapli_conn = task.host.get_connection("scrapli_netconf", task.nornir.config)
-
-    if diff:
-        original_config = scrapli_conn.get_config(source=target)
-
-    scrapli_response = scrapli_conn.edit_config(config=config, target=target)
-
-    if diff:
-        edited_config = scrapli_conn.get_config(source=target)
-        diff_result = diff_xml_text(original_config.result, edited_config.result)
-    else:
-        diff_result = ""
-
-    _task_dry_run = dry_run if dry_run is not None else task.global_dry_run
-
-    if _task_dry_run:
-        scrapli_conn.discard()
-        changed = False
-    else:
-        changed = True
-
-    result = ScrapliResult(
-        host=task.host,
-        result=process_command_result(scrapli_response=scrapli_response),
-        scrapli_response=scrapli_response,
-        changed=changed,
-        diff=diff_result,
-    )
-    return result
-
-
-
-def netconf_get(task: nornir.core.task.Task, filter_: str, filter_type: str = 'subtree') ‑> nornir.core.task.Result -
-
-

Get from the device with scrapli_netconf

-

Args

-
-
task
-
nornir task object
-
filter_
-
string filter to apply to the get
-
filter_type
-
type of filter; subtree|xpath
-
-

Returns

-
-
Result
-
nornir result object with Result.result value set the string result of the -get operation
-
-

Raises

-

N/A

-
- -Expand source code - -
def netconf_get(
-    task: Task,
-    filter_: str,
-    filter_type: str = "subtree",
-) -> Result:
-    """
-    Get from the device with scrapli_netconf
-
-    Args:
-        task: nornir task object
-        filter_: string filter to apply to the get
-        filter_type: type of filter; subtree|xpath
-
-    Returns:
-        Result: nornir result object with Result.result value set the string result of the
-            get operation
-
-    Raises:
-        N/A
-
-    """
-    scrapli_conn = task.host.get_connection("scrapli_netconf", task.nornir.config)
-    scrapli_response = scrapli_conn.get(filter_=filter_, filter_type=filter_type)
-
-    result = ScrapliResult(
-        host=task.host,
-        result=process_command_result(scrapli_response=scrapli_response),
-        scrapli_response=scrapli_response,
-        changed=False,
-    )
-    return result
-
-
-
-def netconf_get_config(task: nornir.core.task.Task, source: str = 'running', filters: Union[str, List[str], NoneType] = None, filter_type: str = 'subtree') ‑> nornir.core.task.Result -
-
-

Get config from the device with scrapli_netconf

-

Args

-
-
task
-
nornir task object
-
source
-
configuration source to get; typically one of running|startup|candidate
-
filters
-
string or list of strings of filters to apply to configuration
-
filter_type
-
type of filter; subtree|xpath
-
-

Returns

-
-
Result
-
nornir result object with Result.result value set the string result of the -get_config operation
-
-

Raises

-

N/A

-
- -Expand source code - -
def netconf_get_config(
-    task: Task,
-    source: str = "running",
-    filters: Optional[Union[str, List[str]]] = None,
-    filter_type: str = "subtree",
-) -> Result:
-    """
-    Get config from the device with scrapli_netconf
-
-    Args:
-        task: nornir task object
-        source: configuration source to get; typically one of running|startup|candidate
-        filters: string or list of strings of filters to apply to configuration
-        filter_type: type of filter; subtree|xpath
-
-    Returns:
-        Result: nornir result object with Result.result value set the string result of the
-            get_config operation
-
-    Raises:
-        N/A
-
-    """
-    scrapli_conn = task.host.get_connection("scrapli_netconf", task.nornir.config)
-    scrapli_response = scrapli_conn.get_config(
-        source=source, filters=filters, filter_type=filter_type
-    )
-
-    result = ScrapliResult(
-        host=task.host,
-        result=process_command_result(scrapli_response=scrapli_response),
-        scrapli_response=scrapli_response,
-        changed=False,
-    )
-    return result
-
-
-
-def netconf_lock(task: nornir.core.task.Task, target: str) ‑> nornir.core.task.Result -
-
-

Lock the device with scrapli_netconf

-

Args

-
-
task
-
nornir task object
-
target
-
configuration source to target; running|startup|candidate
-
-

Returns

-
-
Result
-
nornir result object with Result.result value set the string result of the -get operation
-
-

Raises

-

N/A

-
- -Expand source code - -
def netconf_lock(
-    task: Task,
-    target: str,
-) -> Result:
-    """
-    Lock the device with scrapli_netconf
-
-    Args:
-        task: nornir task object
-        target: configuration source to target; running|startup|candidate
-
-    Returns:
-        Result: nornir result object with Result.result value set the string result of the
-            get operation
-
-    Raises:
-        N/A
-
-    """
-    scrapli_conn = task.host.get_connection("scrapli_netconf", task.nornir.config)
-    scrapli_response = scrapli_conn.lock(target=target)
-
-    result = ScrapliResult(
-        host=task.host,
-        result=process_command_result(scrapli_response=scrapli_response),
-        scrapli_response=scrapli_response,
-        changed=True,
-    )
-    return result
-
-
-
-def netconf_rpc(task: nornir.core.task.Task, filter_: str) ‑> nornir.core.task.Result -
-
-

Send a "bare" rcp to the device with scrapli_netconf

-

Args

-
-
task
-
nornir task object
-
filter_
-
filter/rpc to execute
-
-

Returns

-
-
Result
-
nornir result object with Result.result value set the string result of the -rpc operation
-
-

Raises

-

N/A

-
- -Expand source code - -
def netconf_rpc(
-    task: Task,
-    filter_: str,
-) -> Result:
-    """
-    Send a "bare" rcp to the device with scrapli_netconf
-
-    Args:
-        task: nornir task object
-        filter_: filter/rpc to execute
-
-    Returns:
-        Result: nornir result object with Result.result value set the string result of the
-            rpc operation
-
-    Raises:
-        N/A
-
-    """
-    scrapli_conn = task.host.get_connection("scrapli_netconf", task.nornir.config)
-    scrapli_response = scrapli_conn.rpc(filter_=filter_)
-
-    result = ScrapliResult(
-        host=task.host,
-        result=process_command_result(scrapli_response=scrapli_response),
-        scrapli_response=scrapli_response,
-        changed=True,
-    )
-    return result
-
-
-
-def netconf_unlock(task: nornir.core.task.Task, target: str) ‑> nornir.core.task.Result -
-
-

Unlock the device with scrapli_netconf

-

Args

-
-
task
-
nornir task object
-
target
-
configuration source to target; running|startup|candidate
-
-

Returns

-
-
Result
-
nornir result object with Result.result value set the string result of the -get operation
-
-

Raises

-

N/A

-
- -Expand source code - -
def netconf_unlock(
-    task: Task,
-    target: str,
-) -> Result:
-    """
-    Unlock the device with scrapli_netconf
-
-    Args:
-        task: nornir task object
-        target: configuration source to target; running|startup|candidate
-
-    Returns:
-        Result: nornir result object with Result.result value set the string result of the
-            get operation
-
-    Raises:
-        N/A
-
-    """
-    scrapli_conn = task.host.get_connection("scrapli_netconf", task.nornir.config)
-    scrapli_response = scrapli_conn.unlock(target=target)
-
-    result = ScrapliResult(
-        host=task.host,
-        result=process_command_result(scrapli_response=scrapli_response),
-        scrapli_response=scrapli_response,
-        changed=True,
-    )
-    return result
-
-
-
-def netconf_validate(task: nornir.core.task.Task, source: str) ‑> nornir.core.task.Result -
-
-

Send a "validate" rcp to the device with scrapli_netconf

-

Args

-
-
task
-
nornir task object
-
source
-
configuration source to validate; typically one of running|startup|candidate
-
-

Returns

-
-
Result
-
nornir result object with Result.result value set the string result of the -get operation
-
-

Raises

-

N/A

-
- -Expand source code - -
def netconf_validate(
-    task: Task,
-    source: str,
-) -> Result:
-    """
-    Send a "validate" rcp to the device with scrapli_netconf
-
-    Args:
-        task: nornir task object
-        source: configuration source to validate; typically one of running|startup|candidate
-
-    Returns:
-        Result: nornir result object with Result.result value set the string result of the
-            get operation
-
-    Raises:
-        N/A
-
-    """
-    scrapli_conn = task.host.get_connection("scrapli_netconf", task.nornir.config)
-    scrapli_response = scrapli_conn.validate(source=source)
-
-    result = ScrapliResult(
-        host=task.host,
-        result=process_command_result(scrapli_response=scrapli_response),
-        scrapli_response=scrapli_response,
-        changed=False,
-    )
-    return result
-
-
-
-def send_command(task: nornir.core.task.Task, command: str, strip_prompt: bool = True, failed_when_contains: Union[str, List[str], NoneType] = None, timeout_ops: Union[float, NoneType] = None) ‑> nornir.core.task.Result -
-
-

Send a single command to device using scrapli

-

Args

-
-
task
-
nornir task object
-
command
-
string to send to device in privilege exec mode
-
strip_prompt
-
True/False strip prompt from returned output
-
failed_when_contains
-
string or list of strings indicating failure if found in response
-
timeout_ops
-
timeout ops value for this operation; only sets the timeout_ops value for -the duration of the operation, value is reset to initial value after operation is -completed
-
-

Returns

-
-
Result
-
scrapli nornir result object; almost identical to a "normal" nornir result object, -but contains an additional attribute "scrapli_response" that contains the original -response from scrapli
-
-

Raises

-

N/A

-
- -Expand source code - -
def send_command(
-    task: Task,
-    command: str,
-    strip_prompt: bool = True,
-    failed_when_contains: Optional[Union[str, List[str]]] = None,
-    timeout_ops: Optional[float] = None,
-) -> Result:
-    """
-    Send a single command to device using scrapli
-
-    Args:
-        task: nornir task object
-        command: string to send to device in privilege exec mode
-        strip_prompt: True/False strip prompt from returned output
-        failed_when_contains: string or list of strings indicating failure if found in response
-        timeout_ops: timeout ops value for this operation; only sets the timeout_ops value for
-            the duration of the operation, value is reset to initial value after operation is
-            completed
-
-    Returns:
-        Result: scrapli nornir result object; almost identical to a "normal" nornir result object,
-            but contains an additional attribute "scrapli_response" that contains the original
-            response from scrapli
-
-    Raises:
-        N/A
-
-    """
-    scrapli_conn = task.host.get_connection("scrapli", task.nornir.config)
-    scrapli_response = scrapli_conn.send_command(
-        command=command,
-        strip_prompt=strip_prompt,
-        failed_when_contains=failed_when_contains,
-        timeout_ops=timeout_ops,
-    )
-
-    result = ScrapliResult(
-        host=task.host,
-        result=process_command_result(scrapli_response=scrapli_response),
-        scrapli_response=scrapli_response,
-        changed=False,
-    )
-    return result
-
-
-
-def send_commands(task: nornir.core.task.Task, commands: List[str], strip_prompt: bool = True, failed_when_contains: Union[str, List[str], NoneType] = None, stop_on_failed: bool = False, eager: bool = False, timeout_ops: Union[float, NoneType] = None) ‑> nornir.core.task.Result -
-
-

Send a list of commands to device using scrapli

-

Args

-
-
task
-
nornir task object
-
commands
-
list of strings to send to device in privilege exec mode
-
strip_prompt
-
True/False strip prompt from returned output
-
failed_when_contains
-
string or list of strings indicating failure if found in response
-
stop_on_failed
-
True/False stop executing commands if a command fails, returns results as of -current execution
-
eager
-
if eager is True we do not read until prompt is seen at each command sent to the -channel. Do not use this unless you know what you are doing as it is possible that -it can make scrapli less reliable!
-
timeout_ops
-
timeout ops value for this operation; only sets the timeout_ops value for -the duration of the operation, value is reset to initial value after operation is -completed. Note that this is the timeout value PER COMMAND sent, not for the total -of the commands being sent!
-
-

Returns

-
-
Result
-
nornir result object with Result.result value set to returned scrapli Response -object
-
-

Raises

-

N/A

-
- -Expand source code - -
def send_commands(
-    task: Task,
-    commands: List[str],
-    strip_prompt: bool = True,
-    failed_when_contains: Optional[Union[str, List[str]]] = None,
-    stop_on_failed: bool = False,
-    eager: bool = False,
-    timeout_ops: Optional[float] = None,
-) -> Result:
-    """
-    Send a list of commands to device using scrapli
-
-    Args:
-        task: nornir task object
-        commands: list of strings to send to device in privilege exec mode
-        strip_prompt: True/False strip prompt from returned output
-        failed_when_contains: string or list of strings indicating failure if found in response
-        stop_on_failed: True/False stop executing commands if a command fails, returns results as of
-            current execution
-        eager: if eager is True we do not read until prompt is seen at each command sent to the
-            channel. Do *not* use this unless you know what you are doing as it is possible that
-            it can make scrapli less reliable!
-        timeout_ops: timeout ops value for this operation; only sets the timeout_ops value for
-            the duration of the operation, value is reset to initial value after operation is
-            completed. Note that this is the timeout value PER COMMAND sent, not for the total
-            of the commands being sent!
-
-    Returns:
-        Result: nornir result object with Result.result value set to returned scrapli Response
-            object
-
-    Raises:
-        N/A
-
-    """
-    scrapli_conn = task.host.get_connection("scrapli", task.nornir.config)
-    scrapli_response = scrapli_conn.send_commands(
-        commands=commands,
-        strip_prompt=strip_prompt,
-        failed_when_contains=failed_when_contains,
-        stop_on_failed=stop_on_failed,
-        eager=eager,
-        timeout_ops=timeout_ops,
-    )
-
-    result = ScrapliResult(
-        host=task.host,
-        result=process_command_result(scrapli_response=scrapli_response),
-        scrapli_response=scrapli_response,
-        changed=False,
-    )
-    return result
-
-
-
-def send_commands_from_file(task: nornir.core.task.Task, file: str, strip_prompt: bool = True, failed_when_contains: Union[str, List[str], NoneType] = None, stop_on_failed: bool = False, eager: bool = False, timeout_ops: Union[float, NoneType] = None) ‑> nornir.core.task.Result -
-
-

Send a list of commands from a file to device using scrapli

-

Args

-
-
task
-
nornir task object
-
file
-
string path to file
-
strip_prompt
-
True/False strip prompt from returned output
-
failed_when_contains
-
string or list of strings indicating failure if found in response
-
stop_on_failed
-
True/False stop executing commands if a command fails, returns results as of -current execution
-
eager
-
if eager is True we do not read until prompt is seen at each command sent to the -channel. Do not use this unless you know what you are doing as it is possible that -it can make scrapli less reliable!
-
timeout_ops
-
timeout ops value for this operation; only sets the timeout_ops value for -the duration of the operation, value is reset to initial value after operation is -completed
-
-

Returns

-
-
Result
-
nornir result object with Result.result value set to returned scrapli Response -object
-
-

Raises

-

N/A

-
- -Expand source code - -
def send_commands_from_file(
-    task: Task,
-    file: str,
-    strip_prompt: bool = True,
-    failed_when_contains: Optional[Union[str, List[str]]] = None,
-    stop_on_failed: bool = False,
-    eager: bool = False,
-    timeout_ops: Optional[float] = None,
-) -> Result:
-    """
-    Send a list of commands from a file to device using scrapli
-
-    Args:
-        task: nornir task object
-        file: string path to file
-        strip_prompt: True/False strip prompt from returned output
-        failed_when_contains: string or list of strings indicating failure if found in response
-        stop_on_failed: True/False stop executing commands if a command fails, returns results as of
-            current execution
-        eager: if eager is True we do not read until prompt is seen at each command sent to the
-            channel. Do *not* use this unless you know what you are doing as it is possible that
-            it can make scrapli less reliable!
-        timeout_ops: timeout ops value for this operation; only sets the timeout_ops value for
-            the duration of the operation, value is reset to initial value after operation is
-            completed
-
-    Returns:
-        Result: nornir result object with Result.result value set to returned scrapli Response
-            object
-
-    Raises:
-        N/A
-
-    """
-    scrapli_conn = task.host.get_connection("scrapli", task.nornir.config)
-    scrapli_response = scrapli_conn.send_commands_from_file(
-        file=file,
-        strip_prompt=strip_prompt,
-        failed_when_contains=failed_when_contains,
-        stop_on_failed=stop_on_failed,
-        eager=eager,
-        timeout_ops=timeout_ops,
-    )
-
-    result = ScrapliResult(
-        host=task.host,
-        result=process_command_result(scrapli_response=scrapli_response),
-        scrapli_response=scrapli_response,
-        changed=False,
-    )
-    return result
-
-
-
-def send_config(task: nornir.core.task.Task, config: str, dry_run: Union[bool, NoneType] = None, strip_prompt: bool = True, failed_when_contains: Union[str, List[str], NoneType] = None, stop_on_failed: bool = False, privilege_level: str = '', eager: bool = False, timeout_ops: Union[float, NoneType] = None) ‑> nornir.core.task.Result -
-
-

Send a config to device using scrapli

-

Args

-
-
task
-
nornir task object
-
config
-
string configuration to send to the device, supports sending multi-line strings
-
dry_run
-
Whether to apply changes or not; if dry run, will ensure that it is possible to -enter config mode, but will NOT send any configs
-
strip_prompt
-
True/False strip prompt from returned output
-
failed_when_contains
-
string or list of strings indicating failure if found in response
-
stop_on_failed
-
True/False stop executing commands if a command fails, returns results as of -current execution
-
privilege_level
-
name of configuration privilege level/type to acquire; this is platform -dependent, so check the device driver for specifics. Examples of privilege_name -would be "configuration_exclusive" for IOSXRDriver, or "configuration_private" for -JunosDriver. You can also pass in a name of a configuration session such as -"my-config-session" if you have registered a session using the -"register_config_session" method of the EOSDriver or NXOSDriver.
-
eager
-
if eager is True we do not read until prompt is seen at each command sent to the -channel. Do not use this unless you know what you are doing as it is possible that -it can make scrapli less reliable!
-
timeout_ops
-
timeout ops value for this operation; only sets the timeout_ops value for -the duration of the operation, value is reset to initial value after operation is -completed. Note that this is the timeout value PER CONFIG sent, not for the total -of the configs being sent!
-
-

Returns

-
-
Result
-
nornir result object with Result.result value set to returned scrapli Response -object
-
-

Raises

-
-
NornirScrapliNoConfigModeGenericDriver
-
If attempting to use this task function against a -host that is using the "generic" platform type
-
-
- -Expand source code - -
def send_config(
-    task: Task,
-    config: str,
-    dry_run: Optional[bool] = None,
-    strip_prompt: bool = True,
-    failed_when_contains: Optional[Union[str, List[str]]] = None,
-    stop_on_failed: bool = False,
-    privilege_level: str = "",
-    eager: bool = False,
-    timeout_ops: Optional[float] = None,
-) -> Result:
-    """
-    Send a config to device using scrapli
-
-    Args:
-        task: nornir task object
-        config: string configuration to send to the device, supports sending multi-line strings
-        dry_run: Whether to apply changes or not; if dry run, will ensure that it is possible to
-            enter config mode, but will NOT send any configs
-        strip_prompt: True/False strip prompt from returned output
-        failed_when_contains: string or list of strings indicating failure if found in response
-        stop_on_failed: True/False stop executing commands if a command fails, returns results as of
-            current execution
-        privilege_level: name of configuration privilege level/type to acquire; this is platform
-            dependent, so check the device driver for specifics. Examples of privilege_name
-            would be "configuration_exclusive" for IOSXRDriver, or "configuration_private" for
-            JunosDriver. You can also pass in a name of a configuration session such as
-            "my-config-session" if you have registered a session using the
-            "register_config_session" method of the EOSDriver or NXOSDriver.
-        eager: if eager is True we do not read until prompt is seen at each command sent to the
-             channel. Do *not* use this unless you know what you are doing as it is possible that
-             it can make scrapli less reliable!
-        timeout_ops: timeout ops value for this operation; only sets the timeout_ops value for
-            the duration of the operation, value is reset to initial value after operation is
-            completed. Note that this is the timeout value PER CONFIG sent, not for the total
-            of the configs being sent!
-
-    Returns:
-        Result: nornir result object with Result.result value set to returned scrapli Response
-            object
-
-    Raises:
-        NornirScrapliNoConfigModeGenericDriver: If attempting to use this task function against a
-            host that is using the "generic" platform type
-
-    """
-    if task.host.platform == "generic":
-        raise NornirScrapliNoConfigModeGenericDriver("No config mode for 'generic' platform type")
-
-    scrapli_conn = task.host.get_connection("scrapli", task.nornir.config)
-
-    _task_dry_run = dry_run if dry_run is not None else task.global_dry_run
-
-    if _task_dry_run:
-        # if dry run, try to acquire config mode then back out; do not send any configurations!
-        scrapli_conn.acquire_priv("configuration")
-        scrapli_conn.acquire_priv(scrapli_conn.default_desired_privilege_level)
-        return ScrapliResult(host=task.host, result=None, scrapli_response=None, changed=False)
-
-    scrapli_response = scrapli_conn.send_config(
-        config=config,
-        strip_prompt=strip_prompt,
-        failed_when_contains=failed_when_contains,
-        stop_on_failed=stop_on_failed,
-        privilege_level=privilege_level,
-        eager=eager,
-        timeout_ops=timeout_ops,
-    )
-
-    result = ScrapliResult(
-        host=task.host,
-        result=process_config_result(scrapli_response=scrapli_response),
-        scrapli_response=scrapli_response,
-        changed=True,
-    )
-    return result
-
-
-
-def send_configs(task: nornir.core.task.Task, configs: List[str], dry_run: Union[bool, NoneType] = None, strip_prompt: bool = True, failed_when_contains: Union[str, List[str], NoneType] = None, stop_on_failed: bool = False, privilege_level: str = '', eager: bool = False, timeout_ops: Union[float, NoneType] = None) ‑> nornir.core.task.Result -
-
-

Send configs to device using scrapli

-

Args

-
-
task
-
nornir task object
-
configs
-
list of strings to send to device in config mode
-
dry_run
-
Whether to apply changes or not; if dry run, will ensure that it is possible to -enter config mode, but will NOT send any configs
-
strip_prompt
-
True/False strip prompt from returned output
-
failed_when_contains
-
string or list of strings indicating failure if found in response
-
stop_on_failed
-
True/False stop executing commands if a command fails, returns results as of -current execution
-
privilege_level
-
name of configuration privilege level/type to acquire; this is platform -dependent, so check the device driver for specifics. Examples of privilege_name -would be "configuration_exclusive" for IOSXRDriver, or "configuration_private" for -JunosDriver. You can also pass in a name of a configuration session such as -"my-config-session" if you have registered a session using the -"register_config_session" method of the EOSDriver or NXOSDriver.
-
eager
-
if eager is True we do not read until prompt is seen at each command sent to the -channel. Do not use this unless you know what you are doing as it is possible that -it can make scrapli less reliable!
-
timeout_ops
-
timeout ops value for this operation; only sets the timeout_ops value for -the duration of the operation, value is reset to initial value after operation is -completed. Note that this is the timeout value PER CONFIG sent, not for the total -of the configs being sent!
-
-

Returns

-
-
Result
-
nornir result object with Result.result value set to returned scrapli Response -object
-
-

Raises

-
-
NornirScrapliNoConfigModeGenericDriver
-
If attempting to use this task function against a -host that is using the "generic" platform type
-
-
- -Expand source code - -
def send_configs(
-    task: Task,
-    configs: List[str],
-    dry_run: Optional[bool] = None,
-    strip_prompt: bool = True,
-    failed_when_contains: Optional[Union[str, List[str]]] = None,
-    stop_on_failed: bool = False,
-    privilege_level: str = "",
-    eager: bool = False,
-    timeout_ops: Optional[float] = None,
-) -> Result:
-    """
-    Send configs to device using scrapli
-
-    Args:
-        task: nornir task object
-        configs: list of strings to send to device in config mode
-        dry_run: Whether to apply changes or not; if dry run, will ensure that it is possible to
-            enter config mode, but will NOT send any configs
-        strip_prompt: True/False strip prompt from returned output
-        failed_when_contains: string or list of strings indicating failure if found in response
-        stop_on_failed: True/False stop executing commands if a command fails, returns results as of
-            current execution
-        privilege_level: name of configuration privilege level/type to acquire; this is platform
-            dependent, so check the device driver for specifics. Examples of privilege_name
-            would be "configuration_exclusive" for IOSXRDriver, or "configuration_private" for
-            JunosDriver. You can also pass in a name of a configuration session such as
-            "my-config-session" if you have registered a session using the
-            "register_config_session" method of the EOSDriver or NXOSDriver.
-        eager: if eager is True we do not read until prompt is seen at each command sent to the
-            channel. Do *not* use this unless you know what you are doing as it is possible that
-            it can make scrapli less reliable!
-        timeout_ops: timeout ops value for this operation; only sets the timeout_ops value for
-            the duration of the operation, value is reset to initial value after operation is
-            completed. Note that this is the timeout value PER CONFIG sent, not for the total
-            of the configs being sent!
-
-    Returns:
-        Result: nornir result object with Result.result value set to returned scrapli Response
-            object
-
-    Raises:
-        NornirScrapliNoConfigModeGenericDriver: If attempting to use this task function against a
-            host that is using the "generic" platform type
-
-    """
-    if task.host.platform == "generic":
-        raise NornirScrapliNoConfigModeGenericDriver("No config mode for 'generic' platform type")
-
-    scrapli_conn = task.host.get_connection("scrapli", task.nornir.config)
-
-    _task_dry_run = dry_run if dry_run is not None else task.global_dry_run
-
-    if _task_dry_run:
-        # if dry run, try to acquire config mode then back out; do not send any configurations!
-        scrapli_conn.acquire_priv("configuration")
-        scrapli_conn.acquire_priv(scrapli_conn.default_desired_privilege_level)
-        return ScrapliResult(host=task.host, result=None, scrapli_response=None, changed=False)
-
-    scrapli_response = scrapli_conn.send_configs(
-        configs=configs,
-        strip_prompt=strip_prompt,
-        failed_when_contains=failed_when_contains,
-        stop_on_failed=stop_on_failed,
-        privilege_level=privilege_level,
-        eager=eager,
-        timeout_ops=timeout_ops,
-    )
-
-    result = ScrapliResult(
-        host=task.host,
-        result=process_config_result(scrapli_response=scrapli_response),
-        scrapli_response=scrapli_response,
-        changed=True,
-    )
-    return result
-
-
-
-def send_configs_from_file(task: nornir.core.task.Task, file: str, dry_run: Union[bool, NoneType] = None, strip_prompt: bool = True, failed_when_contains: Union[str, List[str], NoneType] = None, stop_on_failed: bool = False, privilege_level: str = '', eager: bool = False, timeout_ops: Union[float, NoneType] = None) ‑> nornir.core.task.Result -
-
-

Send configs from a file to device using scrapli

-

Args

-
-
task
-
nornir task object
-
file
-
string path to file
-
dry_run
-
Whether to apply changes or not; if dry run, will ensure that it is possible to -enter config mode, but will NOT send any configs
-
strip_prompt
-
True/False strip prompt from returned output
-
failed_when_contains
-
string or list of strings indicating failure if found in response
-
stop_on_failed
-
True/False stop executing commands if a command fails, returns results as of -current execution
-
privilege_level
-
name of configuration privilege level/type to acquire; this is platform -dependent, so check the device driver for specifics. Examples of privilege_name -would be "configuration_exclusive" for IOSXRDriver, or "configuration_private" for -JunosDriver. You can also pass in a name of a configuration session such as -"my-config-session" if you have registered a session using the -"register_config_session" method of the EOSDriver or NXOSDriver.
-
eager
-
if eager is True we do not read until prompt is seen at each command sent to the -channel. Do not use this unless you know what you are doing as it is possible that -it can make scrapli less reliable!
-
timeout_ops
-
timeout ops value for this operation; only sets the timeout_ops value for -the duration of the operation, value is reset to initial value after operation is -completed. Note that this is the timeout value PER CONFIG sent, not for the total -of the configs being sent!
-
-

Returns

-
-
Result
-
nornir result object with Result.result value set to returned scrapli Response -object
-
-

Raises

-
-
NornirScrapliNoConfigModeGenericDriver
-
If attempting to use this task function against a -host that is using the "generic" platform type
-
-
- -Expand source code - -
def send_configs_from_file(
-    task: Task,
-    file: str,
-    dry_run: Optional[bool] = None,
-    strip_prompt: bool = True,
-    failed_when_contains: Optional[Union[str, List[str]]] = None,
-    stop_on_failed: bool = False,
-    privilege_level: str = "",
-    eager: bool = False,
-    timeout_ops: Optional[float] = None,
-) -> Result:
-    """
-    Send configs from a file to device using scrapli
-
-    Args:
-        task: nornir task object
-        file: string path to file
-        dry_run: Whether to apply changes or not; if dry run, will ensure that it is possible to
-            enter config mode, but will NOT send any configs
-        strip_prompt: True/False strip prompt from returned output
-        failed_when_contains: string or list of strings indicating failure if found in response
-        stop_on_failed: True/False stop executing commands if a command fails, returns results as of
-            current execution
-        privilege_level: name of configuration privilege level/type to acquire; this is platform
-            dependent, so check the device driver for specifics. Examples of privilege_name
-            would be "configuration_exclusive" for IOSXRDriver, or "configuration_private" for
-            JunosDriver. You can also pass in a name of a configuration session such as
-            "my-config-session" if you have registered a session using the
-            "register_config_session" method of the EOSDriver or NXOSDriver.
-        eager: if eager is True we do not read until prompt is seen at each command sent to the
-            channel. Do *not* use this unless you know what you are doing as it is possible that
-            it can make scrapli less reliable!
-        timeout_ops: timeout ops value for this operation; only sets the timeout_ops value for
-            the duration of the operation, value is reset to initial value after operation is
-            completed. Note that this is the timeout value PER CONFIG sent, not for the total
-            of the configs being sent!
-
-    Returns:
-        Result: nornir result object with Result.result value set to returned scrapli Response
-            object
-
-    Raises:
-        NornirScrapliNoConfigModeGenericDriver: If attempting to use this task function against a
-            host that is using the "generic" platform type
-
-    """
-    if task.host.platform == "generic":
-        raise NornirScrapliNoConfigModeGenericDriver("No config mode for 'generic' platform type")
-
-    scrapli_conn = task.host.get_connection("scrapli", task.nornir.config)
-
-    _task_dry_run = dry_run if dry_run is not None else task.global_dry_run
-
-    if _task_dry_run:
-        # if dry run, try to acquire config mode then back out; do not send any configurations!
-        scrapli_conn.acquire_priv("configuration")
-        scrapli_conn.acquire_priv(scrapli_conn.default_desired_privilege_level)
-        return ScrapliResult(host=task.host, result=None, scrapli_response=None, changed=False)
-
-    scrapli_response = scrapli_conn.send_configs_from_file(
-        file=file,
-        strip_prompt=strip_prompt,
-        failed_when_contains=failed_when_contains,
-        stop_on_failed=stop_on_failed,
-        privilege_level=privilege_level,
-        eager=eager,
-        timeout_ops=timeout_ops,
-    )
-
-    result = ScrapliResult(
-        host=task.host,
-        result=process_config_result(scrapli_response=scrapli_response),
-        scrapli_response=scrapli_response,
-        changed=True,
-    )
-    return result
-
-
-
-def send_interactive(task: nornir.core.task.Task, interact_events: List[Tuple[str, str, Union[bool, NoneType]]], failed_when_contains: Union[str, List[str], NoneType] = None, privilege_level: str = '', timeout_ops: Union[float, NoneType] = None) ‑> nornir.core.task.Result -
-
-

Send inputs in an interactive fashion using scrapli; usually used to handle prompts

-

Used to interact with devices where prompts change per input, and where inputs may be hidden -such as in the case of a password input. This can be used to respond to challenges from -devices such as the confirmation for the command "clear logging" on IOSXE devices for -example. You may have as many elements in the "interact_events" list as needed, and each -element of that list should be a tuple of two or three elements. The first element is always -the input to send as a string, the second should be the expected response as a string, and -the optional third a bool for whether or not the input is "hidden" (i.e. password input) -An example where we need this sort of capability:

-
3560CX#copy flash: scp:
-Source filename []? test1.txt
-Address or name of remote host []? 172.31.254.100
-Destination username [carl]?
-Writing test1.txt
-Password:
-Password:
- Sink: C0644 639 test1.txt
-!
-639 bytes copied in 12.066 secs (53 bytes/sec)
-3560CX#
-
-

To accomplish this we can use the following (in "native" scrapli):

-
interact = conn.channel.send_inputs_interact(
-    [
-        ("copy flash: scp:", "Source filename []?", False),
-        ("test1.txt", "Address or name of remote host []?", False),
-        ("172.31.254.100", "Destination username [carl]?", False),
-        ("carl", "Password:", False),
-        ("super_secure_password", prompt, True),
-    ]
-)
-
-

If we needed to deal with more prompts we could simply continue adding tuples to the list of -interact "events".

-

Args

-
-
task
-
nornir task object
-
interact_events
-
list of tuples containing the "interactions" with the device -each list element must have an input and an expected response, and may have an -optional bool for the third and final element – the optional bool specifies if the -input that is sent to the device is "hidden" (ex: password), if the hidden param is -not provided it is assumed the input is "normal" (not hidden)
-
failed_when_contains
-
list of strings that, if present in final output, represent a -failed command/interaction
-
privilege_level
-
name of the privilege level to operate in
-
timeout_ops
-
timeout ops value for this operation; only sets the timeout_ops value for -the duration of the operation, value is reset to initial value after operation is -completed
-
-

Returns

-
-
Result
-
nornir result object with Result.result value set to returned scrapli Response -object
-
-

Raises

-

N/A

-
- -Expand source code - -
def send_interactive(
-    task: Task,
-    interact_events: List[Tuple[str, str, Optional[bool]]],
-    failed_when_contains: Optional[Union[str, List[str]]] = None,
-    privilege_level: str = "",
-    timeout_ops: Optional[float] = None,
-) -> Result:
-    """
-    Send inputs in an interactive fashion using scrapli; usually used to handle prompts
-
-    Used to interact with devices where prompts change per input, and where inputs may be hidden
-    such as in the case of a password input. This can be used to respond to challenges from
-    devices such as the confirmation for the command "clear logging" on IOSXE devices for
-    example. You may have as many elements in the "interact_events" list as needed, and each
-    element of that list should be a tuple of two or three elements. The first element is always
-    the input to send as a string, the second should be the expected response as a string, and
-    the optional third a bool for whether or not the input is "hidden" (i.e. password input)
-    An example where we need this sort of capability:
-
-    ```
-    3560CX#copy flash: scp:
-    Source filename []? test1.txt
-    Address or name of remote host []? 172.31.254.100
-    Destination username [carl]?
-    Writing test1.txt
-    Password:
-    Password:
-     Sink: C0644 639 test1.txt
-    !
-    639 bytes copied in 12.066 secs (53 bytes/sec)
-    3560CX#
-    ```
-
-    To accomplish this we can use the following (in "native" scrapli):
-
-    ```
-    interact = conn.channel.send_inputs_interact(
-        [
-            ("copy flash: scp:", "Source filename []?", False),
-            ("test1.txt", "Address or name of remote host []?", False),
-            ("172.31.254.100", "Destination username [carl]?", False),
-            ("carl", "Password:", False),
-            ("super_secure_password", prompt, True),
-        ]
-    )
-    ```
-
-    If we needed to deal with more prompts we could simply continue adding tuples to the list of
-    interact "events".
-
-    Args:
-        task: nornir task object
-        interact_events: list of tuples containing the "interactions" with the device
-            each list element must have an input and an expected response, and may have an
-            optional bool for the third and final element -- the optional bool specifies if the
-            input that is sent to the device is "hidden" (ex: password), if the hidden param is
-            not provided it is assumed the input is "normal" (not hidden)
-        failed_when_contains: list of strings that, if present in final output, represent a
-            failed command/interaction
-        privilege_level: name of the privilege level to operate in
-        timeout_ops: timeout ops value for this operation; only sets the timeout_ops value for
-            the duration of the operation, value is reset to initial value after operation is
-            completed
-
-    Returns:
-        Result: nornir result object with Result.result value set to returned scrapli Response
-            object
-
-    Raises:
-        N/A
-
-    """
-    scrapli_conn = task.host.get_connection("scrapli", task.nornir.config)
-    scrapli_response = scrapli_conn.send_interactive(
-        interact_events=interact_events,
-        failed_when_contains=failed_when_contains,
-        privilege_level=privilege_level,
-        timeout_ops=timeout_ops,
-    )
-
-    result = Result(
-        host=task.host,
-        result=scrapli_response,
-        failed=scrapli_response.failed,
-        changed=True,
-    )
-    setattr(result, "scrapli_response", scrapli_response)
-    return result
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/user_guide/available_functions.md b/docs/user_guide/available_functions.md new file mode 100644 index 0000000..4ad864d --- /dev/null +++ b/docs/user_guide/available_functions.md @@ -0,0 +1,7 @@ +# Available Functions + +- [print_structured_result](/nornir_scrapli/api_docs/functions/#print_structured_result) -- this function is very similar to the "normal" + `print_result` function that now ships with the `nornir_utils` library (historically with nornir "core"), except + it contains several additional arguments, most importantly the `parser` argument allows you to select `textfsm` + or `genie` to decide which parser to use to parse the unstructured data stored in the results object. Please see the structured + results example [here](https://github.com/scrapli/nornir_scrapli/tree/master/examples/structured_data) for more details. diff --git a/docs/user_guide/available_tasks.md b/docs/user_guide/available_tasks.md new file mode 100644 index 0000000..62283ff --- /dev/null +++ b/docs/user_guide/available_tasks.md @@ -0,0 +1,35 @@ +# Available Tasks + +All tasks presented here are methods that live in `scrapli` or `scrapli_netconf` -- these tasks are simply "wrapped +" in such a way that they may be used within the constructs of `nornir`! The links below link back to the `scrapli +` or `scrapli_netconf` docs for the given method -- in all (or very nearly all?) cases, the same arguments that the + underlying library supports will be exposed to `nornir`! + + +## Scrapli "core" Tasks + +- [get_prompt](/nornir_scrapli/api_docs/tasks/#get_prompt) - Get the current prompt of the device +- [send_command](/nornir_scrapli/api_docs/tasks/#send_command) - Send a single command to the device +- [send_commands](/nornir_scrapli/api_docs/tasks/#send_commands) - Send a list of commands to the device +- [send_commands_from_file](/nornir_scrapli/api_docs/tasks/#send_commands_from_file) - Send a list of commands from a file to the device +- [send_config](/nornir_scrapli/api_docs/tasks/#send_config) - Send a configuration to the device +- [send_configs](/nornir_scrapli/api_docs/tasks/#send_configs) - Send a list of configurations to the device +- [send_configs_from_file](/nornir_scrapli/api_docs/tasks/#send_configs_from_file) - Send a list of configurations from a file to the device +- [send_interactive](/nornir_scrapli/api_docs/tasks/#send_interactive) -"Interact" with the device (handle prompts and inputs and things like that) + + +## Scrapli Netconf Tasks + +Note that not all devices will support all operations! + +- netconf_capabilities - Get list of capabilities as exchanged during netconf connection establishment +- [netconf_commit](/nornir_scrapli/api_docs/tasks/#commit) - Commit the configuration on the device +- [netconf_discard](/nornir_scrapli/api_docs/tasks/#discard) - Discard the configuration on the device +- [netconf_edit_config](/nornir_scrapli/api_docs/tasks/#edit_config) - Edit the configuration on the device +- [netconf_delete_config](/nornir_scrapli/api_docs/tasks/#delete_config) - Delete a given datastore on the device +- [netconf_get](/nornir_scrapli/api_docs/tasks/#get) - Get a subtree or xpath from the device +- [netconf_get_config](/nornir_scrapli/api_docs/tasks/#get_config) - Get the configuration from the device +- [netconf_lock](/nornir_scrapli/api_docs/tasks/#lock) - Lock the datastore on the device +- [netconf_unlock](/nornir_scrapli/api_docs/tasks/#unlock) - Unlock the datastore on the device +- [netconf_rpc](/nornir_scrapli/api_docs/tasks/#rpc) - Send a "bare" RPC to the device +- [netconf_validate](/nornir_scrapli/api_docs/tasks/#netconf_validate) - Execute the `validate` rpc against a given datastore diff --git a/docs/user_guide/basic_usage.md b/docs/user_guide/basic_usage.md new file mode 100644 index 0000000..2377d4f --- /dev/null +++ b/docs/user_guide/basic_usage.md @@ -0,0 +1,74 @@ +# Basic Usage + +## Basic Information/Usage + +Nornir has historically contained it's plugins within the actual Nornir codebase itself, this however has changed! As + of mid September 2020, Nornir 3.0.0 has been officially released -- this move to the 3.x.x version now expects + plugins to be external to the code base. If you are looking for pre 3.x.x support, please use the `2020.09.01 + ` version. + +If you have used Nornir before (pre 3.x.x), this package should be very similar to what you already know. Since the + plugins used to live in Nornir you could simply import them from the appropriate package as such: + +```python +from nornir.plugins.tasks.networking import netconf_get_config +``` + +With nornir_scrapli you simply install this package along side "regular" Nornir, and import the tasks from + nornir_scrapli directly: + +```python +from nornir_scrapli.tasks import send_command +``` + +As soon as a nornir_scrapli task is imported, it (`nornir_scrapli`) will register as a connection, and things should + work as normal from there! + +The last important difference with nornir_scrapli is that in addition to the "normal" data in the Nornir Result + object, nornir_scrapli also assigns the scrapli `Response` object (or list of `Response` objects) to the + `scrapli_response` attribute. This means that you can access all of the "normal" scrapli response data from this + object -- including things like `elapsed_time` and `textfsm_parse_output`: + +```python +>>> some_nornir_result["sea-ios-1"].scrapli_response.elapsed_time +0.039469 +>>> some_nornir_result["sea-ios-1"].scrapli_response.textfsm_parse_output() +[[some structured data back from the device!]] +``` + +If you would like to continue using `print_result` like "normal" in nornir, but would like to see structured data (if + available) in the `print_result` output, you can use the nornir_scrapli `print_structured_result` function. This + function can be imported from the scrapli functions module: + +```python +from nornir_scrapli.functions import print_structured_result +``` + +This function acts pretty much exactly like the "normal" print result function, but will of course try to print the + structured result. By default this will try to use textfsm to parse results, but it is of course configurable via + the `parser` keyword argument. As scrapli will return an empty data structure if parsing fails, this may cause + tasks to look like they are getting skipped in the output (nornir's print result function does not print empty + lists), if you would like to fall back to printing the unparsed output you can do so by setting the + `fail_to_string` keyword argument to `True` as follows: + +```python +print_structured_result(my_agg_result, parser="genie", fail_to_string=True) +``` + + +## Using Different Transports + +nornir_scrapli supports all *synchronous* scrapli transport plugins. By default, the "system" transport will be used, +however you can change this in the `extras` section of your nornir inventory: + +```yaml +connection_options: + scrapli: + port: 22 + extras: + ssh_config_file: True + auth_strict_key: False + transport: ssh2 +``` + +Note that you will need to install `scrapli_ssh2` or `scrapli_paramiko` if you want to use those transport plugins! diff --git a/docs/user_guide/installation.md b/docs/user_guide/installation.md new file mode 100644 index 0000000..71faf0c --- /dev/null +++ b/docs/user_guide/installation.md @@ -0,0 +1,48 @@ +# Installation + + +## Standard Installation + +As outlined in the quick start, you should be able to pip install scrapli "normally": + +``` +pip install nornir-scrapli +``` + + +## Installing current master branch + +To install from the source repositories master branch: + +``` +pip install git+https://github.com/scrapli/nornir_scrapli +``` + + +## Installing current develop branch + +To install from this repositories develop branch: + +``` +pip install -e git+https://github.com/scrapli/nornir_scrapli.git@develop#egg=nornir_scrapli +``` + + +## Installation from Source + +To install from source: + +``` +git clone https://github.com/scrapli/nornir_scrapli +cd nornir_scrapli +python setup.py install +``` + + +## Supported Platforms + +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. Windows at one point was being tested very minimally via GitHub Actions builds, however this is no + longer the case as it is just not worth the effort. While scrapli should work on Windows when using the paramiko or + ssh2-python transport drivers, it is not "officially" supported. It is *strongly* recommended/preferred for folks + to use WSL/Cygwin instead of Windows. diff --git a/docs/user_guide/project_details.md b/docs/user_guide/project_details.md new file mode 100644 index 0000000..80ac3f2 --- /dev/null +++ b/docs/user_guide/project_details.md @@ -0,0 +1,42 @@ +# Project Details + +## What is nornir_scrapli + +nornir_scrapli is scrapli (and scrapli netconf's) plugin for Nornir. Nearly all (synchronous) methods of scrapli are +available/exposed in nornir scrapli. So if you enjoy scrapli, but also want the built-in concurrency and inventory +management afforded by nornir, this is the place to be! + + +## Supported Platforms + +nornir_scrapli supports the "core" scrapli drivers, the GenericDriver (for use with linux hosts generally speaking +), and the [scrapli_community](https://github.com/scrapli/scrapli_community) platforms as well! See +[scrapli core docs](https://github.com/carlmontanari/scrapli#supported-platforms) and the +[scrapli community docs](https://github.com/scrapli/scrapli_community#supported-platforms) for more info. The `platform +` argument in the inventory data should use the "normal" NAPALM style platform names, `generic`, or the name of the + scrapli_community platform (i.e. `huawei_vrp`)). + +Example platform values (for inventory data): + +``` +platform: cisco_iosxe +platform: cisco_iosxr +platform: cisco_nxos +platform: arista_eos +platform: juniper_junos +platform: generic +platform: huawei_vrp +``` + + +## Related Scrapli Libraries + +This repo is the nornir plugin for scrapli, however there are other libraries/repos in the scrapli family + -- here is a list/link to all of the other scrapli things! + + +- [scrapli](/more_scrapli/scrapli) +- [scrapli_community](/more_scrapli/scrapli_community) +- [scrapli_netconf](/more_scrapli/scrapli_netconf) +- [scrapli_stubs](/more_scrapli/scrapli_stubs) + diff --git a/docs/user_guide/quickstart.md b/docs/user_guide/quickstart.md new file mode 100644 index 0000000..a3add8d --- /dev/null +++ b/docs/user_guide/quickstart.md @@ -0,0 +1,127 @@ +# Quick Start Guide + +## Installation + +In most cases installation via pip is the simplest and best way to install nornir_scrapli. + +``` +pip install nornir-scrapli +``` + + +## A Simple Example + +Example config file: + +```yaml +--- +inventory: + plugin: YAMLInventory + options: + host_file: "nornir_data/hosts.yaml" + group_file: "nornir_data/groups.yaml" + defaults_file: "nornir_data/defaults.yaml" +``` + +Example inventory file (host/group/default, see "real" Nornir docs for lots more info!) -- please notice that there + is a `scrapli` and a `scrapli_netconf` connection type here!: +```yaml +--- +iosxe-1: + hostname: 172.18.0.11 + connection_options: + scrapli: + platform: cisco_iosxe + port: 22 + extras: + ssh_config_file: True + auth_strict_key: False + scrapli_netconf: + port: 830 + extras: + ssh_config_file: True + auth_strict_key: False +``` + +**NOTE:** `scrapli-netconf` has no concept (at the moment!) of "platforms" - it simply implements RFC compliant + NETCONF RPCs, so you do not need to pass `iosxr`, `junos` or anything like that to the `scrapli_netconf` connection + options section! + + +```python +from nornir import InitNornir +from nornir_scrapli.tasks import ( + get_prompt, + send_command, + send_configs +) + +nr = InitNornir(config_file="nornir_data/config.yaml") + +prompt_results = nr.run(task=get_prompt) +command_results = nr.run(task=send_command, command="show version") +config_results = nr.run( + task=send_configs, + configs=["interface loopback123", "description nornir_scrapli was here"], +) + +print("get_prompt result:") +print(prompt_results["iosxe-1"].result) +print("send_command result:") +print(command_results["iosxe-1"].result) +print("send_configs result:") +print(config_results["iosxe-1"].result) +``` + +``` +$ python my_scrapli_script.py +get_prompt result: +3560CX# +send_command result: +Cisco IOS Software, C3560CX Software (C3560CX-UNIVERSALK9-M), Version 15.2(4)E7, RELEASE SOFTWARE (fc2) + +send_configs result: + + +``` + +Netconf tasks are imported from the same package and in the same fashion as the "core" `scrapli` tasks: + +```python +from nornir_scrapli.tasks import ( + netconf_lock, + netconf_unlock, + netconf_edit_config, + netconf_get, + netconf_get_config, + netconf_rpc +) +``` + +And are executed in the same fashion as well: + +```python +config = """ + + + GigabitEthernet1 + scrapli was here! + + +""" +result = nr.run(task=netconf_edit_config, config=config) +print(result['iosxe1'][0].result) +print(result['iosxe1'][0].scrapli_response.xml_result) +``` + +When using the `scrapli-netconf` tasks the result object `result` will be the string of the returned data from the + device. As with all other `nornir-scrapli` results, the `scrapli_response` object will be assigned to the `Result + ` object and will contain all of the "normal" `scrapli` response object data (or `scrapli-netconf` response data + ), such as the `elapsed_time`, `raw_result`, `xml_result`, etc. -- you can see this in the above example! + + + +## Additional Examples + +- [NETCONF Usage](https://github.com/scrapli/nornir_scrapli/tree/master/examples/basic_netconf_usage) +- [Structured Data](https://github.com/scrapli/nornir_scrapli/tree/master/examples/structured_data) diff --git a/docs/user_guide/versioning.md b/docs/user_guide/versioning.md new file mode 100644 index 0000000..3930f55 --- /dev/null +++ b/docs/user_guide/versioning.md @@ -0,0 +1,3 @@ +# Versioning + +Please see the scrapli "core" [here](https://carlmontanari.github.io/scrapli/user_guide/versioning/) documentation for versioning information. diff --git a/examples/basic_netconf_usage/README.md b/examples/basic_netconf_usage/README.md index 444fa7e..cfc4038 100644 --- a/examples/basic_netconf_usage/README.md +++ b/examples/basic_netconf_usage/README.md @@ -565,7 +565,7 @@ vvvv netconf_rpc ** changed : True vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ^^^^ END netconf_rpc ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_process_failed', 'changed', 'diff', 'exception', 'failed', 'host', 'name', 'result', 'scrapli_response', 'severity_level', 'stderr', 'stdout'] -['__bool__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_record_response', '_record_response_netconf_1_0', '_record_response_netconf_1_1', 'channel_input', 'elapsed_time', 'failed', 'failed_when_contains', 'finish_time', 'genie_parse_output', 'genie_platform', 'get_xml_elements', 'host', 'netconf_version', 'raise_for_status', 'raw_result', 'result', 'start_time', 'strip_namespaces', 'textfsm_parse_output', 'textfsm_platform', 'xml_input', 'xml_result'] +['__bool__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'record_response', 'record_response_netconf_1_0', 'record_response_netconf_1_1', 'channel_input', 'elapsed_time', 'failed', 'failed_when_contains', 'finish_time', 'genie_parse_output', 'genie_platform', 'get_xml_elements', 'host', 'netconf_version', 'raise_for_status', 'raw_result', 'result', 'start_time', 'strip_namespaces', 'textfsm_parse_output', 'textfsm_platform', 'xml_input', 'xml_result'] 0.052339 ``` \ No newline at end of file diff --git a/examples/structured_data/README.md b/examples/structured_data/README.md index 9a9f269..8d44972 100644 --- a/examples/structured_data/README.md +++ b/examples/structured_data/README.md @@ -147,7 +147,7 @@ vvvv send_command ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_process_failed', 'changed', 'diff', 'exception', 'failed', 'host', 'name', 'result', 'scrapli_response', 'severity_level', 'stderr', 'stdout'] -['__bool__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_record_response', 'channel_input', 'elapsed_time', 'failed', 'failed_when_contains', 'finish_time', 'genie_parse_output', 'genie_platform', 'host', 'raise_for_status', 'raw_result', 'result', 'start_time', 'textfsm_parse_output', 'textfsm_platform'] +['__bool__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'record_response', 'channel_input', 'elapsed_time', 'failed', 'failed_when_contains', 'finish_time', 'genie_parse_output', 'genie_platform', 'host', 'raise_for_status', 'raw_result', 'result', 'start_time', 'textfsm_parse_output', 'textfsm_platform'] TEXTFSM RESULTS: [{'version': '15.2(4)E7', 'rommon': 'Bootstrap', 'hostname': 'C3560CX', 'uptime': '2 weeks, 4 days, 9 hours, 58 minutes', 'reload_reason': 'power-on', 'running_image': 'c3560cx-universalk9-mz.152-4.E7.bin', 'hardware': ['WS-C3560CX-8PC-S'], 'serial': ['FOC1911Y0NH'], 'config_register': '0xF', 'mac': ['C8:00:84:B2:E9:80']}] GENIE RESULTS: diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..97b3928 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,67 @@ +--- +site_name: Nornir Scrapli +site_url: https://github.com/scrapli/nornir_scrapli +site_description: "Scrapli's plugin for Nornir" +site_author: Carl Montanari + +repo_name: scrapli/nornir_scrapli +repo_url: https://github.com/scrapli/nornir_scrapli +edit_uri: '' + +theme: + name: material + palette: + primary: 'black' + accent: 'teal' + icon: + repo: fontawesome/brands/github-alt + +nav: + - Scrapli Nornir: index.md + - User Guide: + - Quick Start Guide: user_guide/quickstart.md + - Project Details: user_guide/project_details.md + - Versioning: user_guide/versioning.md + - Installation: user_guide/installation.md + - Basic Usage: user_guide/basic_usage.md + - Available Tasks: user_guide/available_tasks.md + - Available Functions: user_guide/available_functions.md + - API Docs: + - Functions: api_docs/functions.md + - Tasks: api_docs/tasks.md + - Connection: api_docs/connection.md + - Exceptions: api_docs/exceptions.md + - Helper: api_docs/helper.md + - Result: api_docs/result.md + - More Scrapli: + - Scrapli: more_scrapli/scrapli.md + - Scrapli Netconf: more_scrapli/scrapli_netconf.md + - Scrapli Community: more_scrapli/scrapli_community.md + - Scrapli Stubs: more_scrapli/scrapli_stubs.md + - Other: + - Contributing: about/contributing.md + - Code of Conduct: about/code_of_conduct.md + +markdown_extensions: + - toc: + permalink: True + - admonition + - codehilite + - extra + - mdx_gh_links: + user: mkdocs + repo: mkdocs + +extra: + social: + - icon: fontawesome/brands/github-alt + link: 'https://github.com/carlmontanari/scrapli' + - icon: fontawesome/brands/twitter + link: 'https://twitter.com/carlrmontanari' + - icon: fontawesome/brands/linkedin + link: 'https://www.linkedin.com/in/carl-montanari-47888931/' + - icon: fontawesome/solid/globe + link: 'https://montanari.io' + +plugins: + - search diff --git a/nornir_scrapli/connection.py b/nornir_scrapli/connection.py index 3a80bcf..da3fae0 100644 --- a/nornir_scrapli/connection.py +++ b/nornir_scrapli/connection.py @@ -3,7 +3,8 @@ from scrapli import Scrapli from scrapli.driver import GenericDriver -from scrapli_netconf.driver import NetconfScrape +from scrapli.exceptions import ScrapliModuleNotFound +from scrapli_netconf.driver import NetconfDriver from nornir.core.configuration import Config from nornir_scrapli.exceptions import NornirScrapliInvalidPlatform @@ -83,7 +84,7 @@ def open( else: try: connection = Scrapli(**parameters, platform=platform) # type: ignore - except ModuleNotFoundError as exc: + except ScrapliModuleNotFound as exc: raise NornirScrapliInvalidPlatform( f"Provided platform `{platform}` is not a valid scrapli or napalm platform, " "or is not a valid scrapli-community platform." @@ -162,7 +163,7 @@ def open( # options from "extras" (connection options) parameters.update(extras) - connection = NetconfScrape(**parameters) + connection = NetconfDriver(**parameters) connection.open() self.connection = connection # pylint: disable=W0201 diff --git a/requirements-dev.txt b/requirements-dev.txt index 18c6fc4..282ec10 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,14 +2,13 @@ nox==2020.12.31 black==20.8b1 isort==5.7.0 mypy==0.800 -pytest==6.2.1 +pytest==6.2.2 pytest-cov==2.11.1 pylama==7.7.1 pycodestyle>=2.6.0 pydocstyle==5.1.1 pylint==2.6.0 -darglint==1.5.8 -pdoc3==0.9.2 ; sys_platform != "win32" +darglint==1.6.0 -e git+https://github.com/scrapli/scrapli_stubs@master#egg=scrapli_stubs -r requirements.txt -r requirements-genie.txt diff --git a/requirements-docs.txt b/requirements-docs.txt new file mode 100644 index 0000000..273b7b1 --- /dev/null +++ b/requirements-docs.txt @@ -0,0 +1,5 @@ +pdoc3==0.9.2 ; sys_platform != "win32" +mkdocs==1.1.2 +mkdocs-material==6.2.8 +mkdocs-material-extensions==1.0.1 +mdx-gh-links==0.2 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 7815582..c680b17 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -scrapli>=2020.12.31,<=2020.12.31 -scrapli_netconf>=2020.10.10,<=2021.01.17 -scrapli_community>=2020.08.08,<=2020.11.15 +scrapli>=2021.01.30 +scrapli_netconf>=2021.01.30 +scrapli_community>=2021.01.30 nornir>=3.0.0,<4.0.0 nornir-utils>=0.1.0 textfsm>=1.1.0,<2.0.0 diff --git a/setup.cfg b/setup.cfg index 2653701..5695eac 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [pylama] linters = mccabe,pycodestyle,pylint -skip = tests/*,.nox/*,venv/*,build/*,private/*,examples/* +skip = tests/*,.nox/*,venv/*,build/*,private/*,examples/*,docs/*,site/* [pylama:pycodestyle] max_line_length = 100 @@ -30,6 +30,3 @@ ignore_missing_imports = True warn_redundant_casts = True warn_unused_configs = True strict_optional = True - -[mypy-scrapli.transport.ptyprocess] -ignore_errors = True diff --git a/setup.py b/setup.py index bb3cd7c..5f8ef06 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ import setuptools __author__ = "Carl Montanari" -__version__ = "2021.01.29" +__version__ = "2021.01.30" with open("README.md", "r") as f: README = f.read() diff --git a/tests/unit/functions/test_print_structured_result.py b/tests/unit/functions/test_print_structured_result.py index 47a2010..eb77549 100644 --- a/tests/unit/functions/test_print_structured_result.py +++ b/tests/unit/functions/test_print_structured_result.py @@ -90,14 +90,14 @@ textfsm_platform="cisco_iosxe", genie_platform="iosxe", ) -TEST_SCRAPLI_RESPONSE_ONE._record_response(result=IOSXE_SHOW_VERSION.encode()) +TEST_SCRAPLI_RESPONSE_ONE.record_response(result=IOSXE_SHOW_VERSION.encode()) TEST_SCRAPLI_RESPONSE_TWO = Response( host="sea-ios-1", channel_input="show ip route", textfsm_platform="cisco_iosxe", genie_platform="iosxe", ) -TEST_SCRAPLI_RESPONSE_TWO._record_response(result=IOSXE_SHOW_IP_ROUTE.encode()) +TEST_SCRAPLI_RESPONSE_TWO.record_response(result=IOSXE_SHOW_IP_ROUTE.encode()) TEST_SCRAPLI_RESPONSE = [TEST_SCRAPLI_RESPONSE_ONE, TEST_SCRAPLI_RESPONSE_TWO] TEST_HOST = Host(name="sea-ios-1") diff --git a/tests/unit/tasks/test_netconf_capabilities.py b/tests/unit/tasks/test_netconf_capabilities.py index b834bc6..0638b9b 100644 --- a/tests/unit/tasks/test_netconf_capabilities.py +++ b/tests/unit/tasks/test_netconf_capabilities.py @@ -1,14 +1,14 @@ -from scrapli_netconf import NetconfScrape +from scrapli_netconf import NetconfDriver -def test_netconf_unlock(nornir_netconf, monkeypatch): +def test_netconf_get_capabilities(nornir_netconf, monkeypatch): from nornir_scrapli.tasks import netconf_capabilities def mock_open(cls): cls.server_capabilities = ["racecar"] pass - monkeypatch.setattr(NetconfScrape, "open", mock_open) + monkeypatch.setattr(NetconfDriver, "open", mock_open) result = nornir_netconf.run(task=netconf_capabilities) assert result["sea-ios-1"].result == ["racecar"] diff --git a/tests/unit/tasks/test_netconf_commit.py b/tests/unit/tasks/test_netconf_commit.py index 00a7c22..290a36a 100644 --- a/tests/unit/tasks/test_netconf_commit.py +++ b/tests/unit/tasks/test_netconf_commit.py @@ -1,5 +1,5 @@ from scrapli.response import Response -from scrapli_netconf import NetconfScrape +from scrapli_netconf import NetconfDriver def test_netconf_commit(nornir_netconf, monkeypatch): @@ -10,11 +10,11 @@ def mock_open(cls): def mock_commit(cls): response = Response(host="fake_as_heck", channel_input="blah") - response._record_response(b"some stuff about whatever") + response.record_response(b"some stuff about whatever") return response - monkeypatch.setattr(NetconfScrape, "open", mock_open) - monkeypatch.setattr(NetconfScrape, "commit", mock_commit) + monkeypatch.setattr(NetconfDriver, "open", mock_open) + monkeypatch.setattr(NetconfDriver, "commit", mock_commit) result = nornir_netconf.run(task=netconf_commit) assert result["sea-ios-1"].result == "some stuff about whatever" diff --git a/tests/unit/tasks/test_netconf_delete_config.py b/tests/unit/tasks/test_netconf_delete_config.py index fd76c4a..bbc8eb1 100644 --- a/tests/unit/tasks/test_netconf_delete_config.py +++ b/tests/unit/tasks/test_netconf_delete_config.py @@ -1,5 +1,5 @@ from scrapli.response import Response -from scrapli_netconf import NetconfScrape +from scrapli_netconf import NetconfDriver def test_netconf_delete_config(nornir_netconf, monkeypatch): @@ -10,11 +10,11 @@ def mock_open(cls): def mock_delete_config(cls, target): response = Response(host="fake_as_heck", channel_input="blah") - response._record_response(b"some stuff about whatever") + response.record_response(b"some stuff about whatever") return response - monkeypatch.setattr(NetconfScrape, "open", mock_open) - monkeypatch.setattr(NetconfScrape, "delete_config", mock_delete_config) + monkeypatch.setattr(NetconfDriver, "open", mock_open) + monkeypatch.setattr(NetconfDriver, "delete_config", mock_delete_config) result = nornir_netconf.run(task=netconf_delete_config, target="blah") assert result["sea-ios-1"].result == "some stuff about whatever" diff --git a/tests/unit/tasks/test_netconf_discard.py b/tests/unit/tasks/test_netconf_discard.py index ea99e65..adcb5c5 100644 --- a/tests/unit/tasks/test_netconf_discard.py +++ b/tests/unit/tasks/test_netconf_discard.py @@ -1,5 +1,5 @@ from scrapli.response import Response -from scrapli_netconf import NetconfScrape +from scrapli_netconf import NetconfDriver def test_netconf_commit(nornir_netconf, monkeypatch): @@ -10,11 +10,11 @@ def mock_open(cls): def mock_discard(cls): response = Response(host="fake_as_heck", channel_input="blah") - response._record_response(b"some stuff about whatever") + response.record_response(b"some stuff about whatever") return response - monkeypatch.setattr(NetconfScrape, "open", mock_open) - monkeypatch.setattr(NetconfScrape, "discard", mock_discard) + monkeypatch.setattr(NetconfDriver, "open", mock_open) + monkeypatch.setattr(NetconfDriver, "discard", mock_discard) result = nornir_netconf.run(task=netconf_discard) assert result["sea-ios-1"].result == "some stuff about whatever" diff --git a/tests/unit/tasks/test_netconf_edit_config.py b/tests/unit/tasks/test_netconf_edit_config.py index d89fd02..10126e5 100644 --- a/tests/unit/tasks/test_netconf_edit_config.py +++ b/tests/unit/tasks/test_netconf_edit_config.py @@ -1,5 +1,5 @@ from scrapli.response import Response -from scrapli_netconf import NetconfScrape +from scrapli_netconf import NetconfDriver def test_netconf_edit_config(nornir_netconf, monkeypatch): @@ -10,17 +10,17 @@ def mock_open(cls): def mock_get_config(cls, source): response = Response(host="fake_as_heck", channel_input="blah") - response._record_response(b"some stuff about whatever") + response.record_response(b"some stuff about whatever") return response def mock_edit_config(cls, config, target): response = Response(host="fake_as_heck", channel_input="blah") - response._record_response(b"some stuff about whatever") + response.record_response(b"some stuff about whatever") return response - monkeypatch.setattr(NetconfScrape, "open", mock_open) - monkeypatch.setattr(NetconfScrape, "get_config", mock_get_config) - monkeypatch.setattr(NetconfScrape, "edit_config", mock_edit_config) + monkeypatch.setattr(NetconfDriver, "open", mock_open) + monkeypatch.setattr(NetconfDriver, "get_config", mock_get_config) + monkeypatch.setattr(NetconfDriver, "edit_config", mock_edit_config) result = nornir_netconf.run(task=netconf_edit_config, config="blah", target="blah") assert result["sea-ios-1"].result == "some stuff about whatever" diff --git a/tests/unit/tasks/test_netconf_get.py b/tests/unit/tasks/test_netconf_get.py index 9dc7a2b..dcffdae 100644 --- a/tests/unit/tasks/test_netconf_get.py +++ b/tests/unit/tasks/test_netconf_get.py @@ -1,5 +1,5 @@ from scrapli.response import Response -from scrapli_netconf import NetconfScrape +from scrapli_netconf import NetconfDriver def test_netconf_get(nornir_netconf, monkeypatch): @@ -10,11 +10,11 @@ def mock_open(cls): def mock_get(cls, filter_, filter_type): response = Response(host="fake_as_heck", channel_input="blah") - response._record_response(b"some stuff about whatever") + response.record_response(b"some stuff about whatever") return response - monkeypatch.setattr(NetconfScrape, "open", mock_open) - monkeypatch.setattr(NetconfScrape, "get", mock_get) + monkeypatch.setattr(NetconfDriver, "open", mock_open) + monkeypatch.setattr(NetconfDriver, "get", mock_get) result = nornir_netconf.run(task=netconf_get, filter_="blah", filter_type="subtree") assert result["sea-ios-1"].result == "some stuff about whatever" diff --git a/tests/unit/tasks/test_netconf_get_config.py b/tests/unit/tasks/test_netconf_get_config.py index 4f24217..b315409 100644 --- a/tests/unit/tasks/test_netconf_get_config.py +++ b/tests/unit/tasks/test_netconf_get_config.py @@ -1,5 +1,5 @@ from scrapli.response import Response -from scrapli_netconf import NetconfScrape +from scrapli_netconf import NetconfDriver def test_netconf_get_config(nornir_netconf, monkeypatch): @@ -10,11 +10,11 @@ def mock_open(cls): def mock_get_config(cls, source, filters, filter_type): response = Response(host="fake_as_heck", channel_input="blah") - response._record_response(b"some stuff about whatever") + response.record_response(b"some stuff about whatever") return response - monkeypatch.setattr(NetconfScrape, "open", mock_open) - monkeypatch.setattr(NetconfScrape, "get_config", mock_get_config) + monkeypatch.setattr(NetconfDriver, "open", mock_open) + monkeypatch.setattr(NetconfDriver, "get_config", mock_get_config) result = nornir_netconf.run(task=netconf_get_config, source="running") assert result["sea-ios-1"].result == "some stuff about whatever" diff --git a/tests/unit/tasks/test_netconf_lock.py b/tests/unit/tasks/test_netconf_lock.py index 6c017cd..a11d46a 100644 --- a/tests/unit/tasks/test_netconf_lock.py +++ b/tests/unit/tasks/test_netconf_lock.py @@ -1,5 +1,5 @@ from scrapli.response import Response -from scrapli_netconf import NetconfScrape +from scrapli_netconf import NetconfDriver def test_netconf_lock(nornir_netconf, monkeypatch): @@ -10,11 +10,11 @@ def mock_open(cls): def mock_lock(cls, target): response = Response(host="fake_as_heck", channel_input="blah") - response._record_response(b"some stuff about whatever") + response.record_response(b"some stuff about whatever") return response - monkeypatch.setattr(NetconfScrape, "open", mock_open) - monkeypatch.setattr(NetconfScrape, "lock", mock_lock) + monkeypatch.setattr(NetconfDriver, "open", mock_open) + monkeypatch.setattr(NetconfDriver, "lock", mock_lock) result = nornir_netconf.run(task=netconf_lock, target="running") assert result["sea-ios-1"].result == "some stuff about whatever" diff --git a/tests/unit/tasks/test_netconf_rpc.py b/tests/unit/tasks/test_netconf_rpc.py index e1e24ef..02e1afc 100644 --- a/tests/unit/tasks/test_netconf_rpc.py +++ b/tests/unit/tasks/test_netconf_rpc.py @@ -1,5 +1,5 @@ from scrapli.response import Response -from scrapli_netconf import NetconfScrape +from scrapli_netconf import NetconfDriver def test_netconf_rpc(nornir_netconf, monkeypatch): @@ -10,11 +10,11 @@ def mock_open(cls): def mock_rpc(cls, filter_): response = Response(host="fake_as_heck", channel_input="blah") - response._record_response(b"some stuff about whatever") + response.record_response(b"some stuff about whatever") return response - monkeypatch.setattr(NetconfScrape, "open", mock_open) - monkeypatch.setattr(NetconfScrape, "rpc", mock_rpc) + monkeypatch.setattr(NetconfDriver, "open", mock_open) + monkeypatch.setattr(NetconfDriver, "rpc", mock_rpc) result = nornir_netconf.run(task=netconf_rpc, filter_="blah") assert result["sea-ios-1"].result == "some stuff about whatever" diff --git a/tests/unit/tasks/test_netconf_unlock.py b/tests/unit/tasks/test_netconf_unlock.py index 59cf789..95290f3 100644 --- a/tests/unit/tasks/test_netconf_unlock.py +++ b/tests/unit/tasks/test_netconf_unlock.py @@ -1,5 +1,5 @@ from scrapli.response import Response -from scrapli_netconf import NetconfScrape +from scrapli_netconf import NetconfDriver def test_netconf_unlock(nornir_netconf, monkeypatch): @@ -10,11 +10,11 @@ def mock_open(cls): def mock_unlock(cls, target): response = Response(host="fake_as_heck", channel_input="blah") - response._record_response(b"some stuff about whatever") + response.record_response(b"some stuff about whatever") return response - monkeypatch.setattr(NetconfScrape, "open", mock_open) - monkeypatch.setattr(NetconfScrape, "unlock", mock_unlock) + monkeypatch.setattr(NetconfDriver, "open", mock_open) + monkeypatch.setattr(NetconfDriver, "unlock", mock_unlock) result = nornir_netconf.run(task=netconf_unlock, target="running") assert result["sea-ios-1"].result == "some stuff about whatever" diff --git a/tests/unit/tasks/test_netconf_validate.py b/tests/unit/tasks/test_netconf_validate.py index 2f09cbf..56a623c 100644 --- a/tests/unit/tasks/test_netconf_validate.py +++ b/tests/unit/tasks/test_netconf_validate.py @@ -1,5 +1,5 @@ from scrapli.response import Response -from scrapli_netconf import NetconfScrape +from scrapli_netconf import NetconfDriver def test_netconf_validate(nornir_netconf, monkeypatch): @@ -10,11 +10,11 @@ def mock_open(cls): def mock_validate(cls, source): response = Response(host="fake_as_heck", channel_input="blah") - response._record_response(b"some stuff about whatever") + response.record_response(b"some stuff about whatever") return response - monkeypatch.setattr(NetconfScrape, "open", mock_open) - monkeypatch.setattr(NetconfScrape, "validate", mock_validate) + monkeypatch.setattr(NetconfDriver, "open", mock_open) + monkeypatch.setattr(NetconfDriver, "validate", mock_validate) result = nornir_netconf.run(task=netconf_validate, source="running") assert result["sea-ios-1"].result == "some stuff about whatever" diff --git a/tests/unit/tasks/test_send_command.py b/tests/unit/tasks/test_send_command.py index 289214d..f03d85c 100644 --- a/tests/unit/tasks/test_send_command.py +++ b/tests/unit/tasks/test_send_command.py @@ -12,7 +12,7 @@ def mock_send_command( cls, command, strip_prompt, failed_when_contains, eager=False, timeout_ops=None ): response = Response(host="fake_as_heck", channel_input=command) - response._record_response(b"some stuff about whatever") + response.record_response(b"some stuff about whatever") return response monkeypatch.setattr(IOSXEDriver, "open", mock_open) diff --git a/tests/unit/tasks/test_send_commands.py b/tests/unit/tasks/test_send_commands.py index 8d21d92..9805f7b 100644 --- a/tests/unit/tasks/test_send_commands.py +++ b/tests/unit/tasks/test_send_commands.py @@ -21,7 +21,7 @@ def mock_send_commands( timeout_ops=None, ): response = Response(host="fake_as_heck", channel_input=commands[0]) - response._record_response(b"some stuff about whatever") + response.record_response(b"some stuff about whatever") return [response] monkeypatch.setattr(IOSXEDriver, "open", mock_open) diff --git a/tests/unit/tasks/test_send_commands_from_file.py b/tests/unit/tasks/test_send_commands_from_file.py index 8e69fbf..e8056ca 100644 --- a/tests/unit/tasks/test_send_commands_from_file.py +++ b/tests/unit/tasks/test_send_commands_from_file.py @@ -27,7 +27,7 @@ def mock_send_commands_from_file( with open(file, "r") as f: commands = f.read().splitlines() response = Response(host="fake_as_heck", channel_input=commands[0]) - response._record_response(b"some stuff about whatever") + response.record_response(b"some stuff about whatever") return [response] monkeypatch.setattr(IOSXEDriver, "open", mock_open) diff --git a/tests/unit/tasks/test_send_config.py b/tests/unit/tasks/test_send_config.py index c8d2eff..bc774d8 100644 --- a/tests/unit/tasks/test_send_config.py +++ b/tests/unit/tasks/test_send_config.py @@ -22,7 +22,7 @@ def mock_send_config( timeout_ops=None, ): response = Response(host="fake_as_heck", channel_input=config) - response._record_response(b"some stuff about whatever") + response.record_response(b"some stuff about whatever") return response monkeypatch.setattr(IOSXEDriver, "open", mock_open) diff --git a/tests/unit/tasks/test_send_configs.py b/tests/unit/tasks/test_send_configs.py index 1ca00b2..e664a65 100644 --- a/tests/unit/tasks/test_send_configs.py +++ b/tests/unit/tasks/test_send_configs.py @@ -23,10 +23,10 @@ def mock_send_configs( ): responses = [] response = Response(host="fake_as_heck", channel_input=configs[0]) - response._record_response(b"") + response.record_response(b"") responses.append(response) response = Response(host="fake_as_heck", channel_input=configs[1]) - response._record_response(b"") + response.record_response(b"") responses.append(response) return responses diff --git a/tests/unit/tasks/test_send_configs_from_file.py b/tests/unit/tasks/test_send_configs_from_file.py index b55d374..c8cb055 100644 --- a/tests/unit/tasks/test_send_configs_from_file.py +++ b/tests/unit/tasks/test_send_configs_from_file.py @@ -30,10 +30,10 @@ def mock_send_configs_from_file( configs = f.read().splitlines() responses = [] response = Response(host="fake_as_heck", channel_input=configs[0]) - response._record_response(b"") + response.record_response(b"") responses.append(response) response = Response(host="fake_as_heck", channel_input=configs[1]) - response._record_response(b"") + response.record_response(b"") responses.append(response) return responses diff --git a/tests/unit/tasks/test_send_interactive.py b/tests/unit/tasks/test_send_interactive.py index 598de20..194c3fd 100644 --- a/tests/unit/tasks/test_send_interactive.py +++ b/tests/unit/tasks/test_send_interactive.py @@ -15,7 +15,7 @@ def mock_send_interactive( host="fake_as_heck", channel_input=", ".join([event[0] for event in interact_events]), ) - response._record_response(b"clear logg\nClear logging buffer [confirm]\n\ncsr1000v#") + response.record_response(b"clear logg\nClear logging buffer [confirm]\n\ncsr1000v#") return response monkeypatch.setattr(IOSXEDriver, "open", mock_open) diff --git a/tests/unit/test_connection.py b/tests/unit/test_connection.py index 08b5d4c..a177a64 100644 --- a/tests/unit/test_connection.py +++ b/tests/unit/test_connection.py @@ -36,37 +36,37 @@ def resolve_ssh_config(ssh_config_file: str) -> str: def test_connection_core_setup(nornir, monkeypatch): - from scrapli.driver.driver import Scrape + from scrapli.driver.base.sync_driver import Driver def mock_open(cls): pass - monkeypatch.setattr(Scrape, "open", mock_open) + monkeypatch.setattr(Driver, "open", mock_open) scrapli_conn = nornir.inventory.hosts["sea-ios-1"].get_connection("scrapli", nornir.config) - assert scrapli_conn.transport.host == "172.18.0.11" - assert scrapli_conn.transport.port == 22 - assert scrapli_conn.transport.auth_username == "vrnetlab" - assert scrapli_conn.transport.auth_password == "VR-netlab9" - assert scrapli_conn.transport.auth_strict_key is False + assert scrapli_conn.host == "172.18.0.11" + assert scrapli_conn.port == 22 + assert scrapli_conn.auth_username == "vrnetlab" + assert scrapli_conn.auth_password == "VR-netlab9" + assert scrapli_conn.auth_strict_key is False def test_connection_core_community_platform(nornir_community, monkeypatch): # simple test to ensure scrapli community platforms load properly - from scrapli.driver.driver import Scrape + from scrapli.driver.base.sync_driver import Driver def mock_open(cls): pass - monkeypatch.setattr(Scrape, "open", mock_open) + monkeypatch.setattr(Driver, "open", mock_open) scrapli_conn = nornir_community.inventory.hosts["sea-ios-1"].get_connection( "scrapli", nornir_community.config ) - assert scrapli_conn.transport.host == "172.18.0.11" - assert scrapli_conn.transport.port == 22 - assert scrapli_conn.transport.auth_username == "vrnetlab" - assert scrapli_conn.transport.auth_password == "VR-netlab9" - assert scrapli_conn.transport.auth_strict_key is False + assert scrapli_conn.host == "172.18.0.11" + assert scrapli_conn.port == 22 + assert scrapli_conn.auth_username == "vrnetlab" + assert scrapli_conn.auth_password == "VR-netlab9" + assert scrapli_conn.auth_strict_key is False def test_connection_core_invalid_platform(): @@ -94,48 +94,48 @@ def test_connection_core_invalid_platform(): def test_connection_netconf_setup(nornir_netconf, monkeypatch): - from scrapli_netconf.driver import NetconfScrape + from scrapli_netconf.driver import NetconfDriver def mock_open(cls): pass - monkeypatch.setattr(NetconfScrape, "open", mock_open) + monkeypatch.setattr(NetconfDriver, "open", mock_open) scrapli_conn = nornir_netconf.inventory.hosts["sea-ios-1"].get_connection( "scrapli_netconf", nornir_netconf.config ) - assert scrapli_conn.transport.host == "172.18.0.11" - assert scrapli_conn.transport.port == 830 - assert scrapli_conn.transport.auth_username == "vrnetlab" - assert scrapli_conn.transport.auth_password == "VR-netlab9" - assert scrapli_conn.transport.auth_strict_key is False - assert isinstance(scrapli_conn, NetconfScrape) + assert scrapli_conn.host == "172.18.0.11" + assert scrapli_conn.port == 830 + assert scrapli_conn.auth_username == "vrnetlab" + assert scrapli_conn.auth_password == "VR-netlab9" + assert scrapli_conn.auth_strict_key is False + assert isinstance(scrapli_conn, NetconfDriver) def test_connection_global_ssh_config_setting_overridden(nornir_global_ssh, monkeypatch): - from scrapli_netconf.driver import NetconfScrape + from scrapli_netconf.driver import NetconfDriver def mock_open(cls): pass - monkeypatch.setattr(NetconfScrape, "open", mock_open) + monkeypatch.setattr(NetconfDriver, "open", mock_open) scrapli_conn = nornir_global_ssh.inventory.hosts["sea-ios-1"].get_connection( "scrapli_netconf", nornir_global_ssh.config ) assert nornir_global_ssh.config.ssh.config_file == "notarealfile!" - assert scrapli_conn._initialization_args["ssh_config_file"] == resolve_ssh_config("") + assert scrapli_conn.ssh_config_file == resolve_ssh_config("") def test_connection_global_ssh_config_setting_no_connection_option_ssh( nornir_global_ssh_no_connection_option_ssh, monkeypatch ): - from scrapli.driver.driver import Scrape + from scrapli.driver.base.sync_driver import Driver def mock_open(cls): pass - monkeypatch.setattr(Scrape, "open", mock_open) + monkeypatch.setattr(Driver, "open", mock_open) scrapli_conn = nornir_global_ssh_no_connection_option_ssh.inventory.hosts[ "sea-ios-1" ].get_connection("scrapli", nornir_global_ssh_no_connection_option_ssh.config) assert nornir_global_ssh_no_connection_option_ssh.config.ssh.config_file is False - assert scrapli_conn._initialization_args["ssh_config_file"] == "" + assert scrapli_conn.ssh_config_file == ""