diff --git a/.bandit.yml b/.bandit.yml index 55c6741b..90c05dbc 100644 --- a/.bandit.yml +++ b/.bandit.yml @@ -3,3 +3,4 @@ skips: [] # No need to check for security issues in the test scripts! exclude_dirs: - "./tests/" + - "./docs/" diff --git a/docs/source/conf.py b/docs/source/conf.py index 8826b521..f784fc10 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -15,6 +15,7 @@ import toml sys.path.insert(0, os.path.abspath("../..")) +sys.path.append(os.path.abspath("sphinxext")) toml_dict = toml.load("../../pyproject.toml") @@ -33,7 +34,7 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ["sphinx.ext.autodoc", "sphinx.ext.napoleon", "m2r2"] +extensions = ["sphinx.ext.autodoc", "sphinx.ext.napoleon", "m2r2", "exec"] autodoc_default_options = { "members": True, diff --git a/docs/source/netutils/index.rst b/docs/source/netutils/index.rst index 5fa9baca..6e49521a 100644 --- a/docs/source/netutils/index.rst +++ b/docs/source/netutils/index.rst @@ -10,6 +10,7 @@ Netutils Functions dns/index interface/index ip/index + lib_mapping/index mac/index password/index ping/index diff --git a/docs/source/netutils/lib_mapping/index.rst b/docs/source/netutils/lib_mapping/index.rst new file mode 100644 index 00000000..4953717e --- /dev/null +++ b/docs/source/netutils/lib_mapping/index.rst @@ -0,0 +1,151 @@ +***************** +Library Mappings +***************** + +These dictionaries provide mappings in expected vendor names between Netmiko, NAPALM, pyntc, ntc-templates, pyats, and scrapli. For each non-reversed mapper, the keys of the dictionary represent the driver used for that library while the values represent the "normalized" driver based on netmiko. + +These dictionaries allow you to keep your Source of Truth platform data consistent and still easily switch between automation libraries. For example, you may be storing your device platform data in Nautobot. In a Nautobot platform, you can store the NAPALM driver needed for that platform. What if you wanted to write +a python script to leverage the backup capabilities of pyntc? Here's an example of how you could use the following dictionaries to perform mappings from your stored Nautobot NAPALM driver to the pyntc driver needed for your script. + +.. code-block:: python + + import pynautobot + from netutils.lib_mapper import NAPALM_LIB_MAPPER, PYNTC_LIB_MAPPER_REVERSE + from pyntc import ntc_device as NTC + + + # Get device from Nautobot + nautobot = pynautobot.api(url="http://mynautobotinstance.com",token="mytoken") + + # Get Napalm driver and save for later use. + device = nautobot.dcim.devices.get(name="mydevice") + sot_driver = device.platform.napalm_driver + + + # Connect to device via Napalm + driver = napalm.get_network_driver("ios") + + device = driver( + hostname="device.name", + username="demo", + password="secret" + ) + + # Do Napalm tasks + + pyntc_driver = PYNTC_LIB_MAPPER_REVERSE.get(NAPALM_LIB_MAPPER.get(sot_driver)) + net_con = NTC(host=device.name, username="demo", password="secret", device_type=pyntc_driver) + + # Do pyntc tasks + +Another use case could be using an example like the above in an Ansible filter. That would allow you to write a filter utilizing whichever automation library you needed without having to store the driver for each one in your Source of Truth. + +Napalm Mapper +=================== +.. exec:: + import json + from netutils.lib_mapper import NAPALM_LIB_MAPPER + json_obj = json.dumps(NAPALM_LIB_MAPPER, sort_keys=True, indent=4) + json_obj = json_obj[:-1] + " }" + print(f".. code-block:: JavaScript\n\n {json_obj}\n\n") + +Reverse Napalm Mapper +===================== +.. exec:: + import json + from netutils.lib_mapper import NAPALM_LIB_MAPPER_REVERSE + json_obj = json.dumps(NAPALM_LIB_MAPPER_REVERSE, sort_keys=True, indent=4) + json_obj = json_obj[:-1] + " }" + print(f".. code-block:: JavaScript\n\n {json_obj}\n\n") + +PyNTC Mapper +============== +.. exec:: + import json + from netutils.lib_mapper import PYNTC_LIB_MAPPER + json_obj = json.dumps(PYNTC_LIB_MAPPER, sort_keys=True, indent=4) + json_obj = json_obj[:-1] + " }" + print(f".. code-block:: JavaScript\n\n {json_obj}\n\n") + +Reverse PyNTC Mapper +==================== +.. exec:: + import json + from netutils.lib_mapper import PYNTC_LIB_MAPPER_REVERSE + json_obj = json.dumps(PYNTC_LIB_MAPPER_REVERSE, sort_keys=True, indent=4) + json_obj = json_obj[:-1] + " }" + print(f".. code-block:: JavaScript\n\n {json_obj}\n\n") + +Ansible Mapper +============== +.. exec:: + import json + from netutils.lib_mapper import ANSIBLE_LIB_MAPPER + json_obj = json.dumps(ANSIBLE_LIB_MAPPER, sort_keys=True, indent=4) + json_obj = json_obj[:-1] + " }" + print(f".. code-block:: JavaScript\n\n {json_obj}\n\n") + +Reverse Ansible Mapper +====================== +.. exec:: + import json + from netutils.lib_mapper import ANSIBLE_LIB_MAPPER_REVERSE + json_obj = json.dumps(ANSIBLE_LIB_MAPPER_REVERSE, sort_keys=True, indent=4) + json_obj = json_obj[:-1] + " }" + print(f".. code-block:: JavaScript\n\n {json_obj}\n\n") + +PyATS Mapper +============== +.. exec:: + import json + from netutils.lib_mapper import PYATS_LIB_MAPPER + json_obj = json.dumps(PYATS_LIB_MAPPER, sort_keys=True, indent=4) + json_obj = json_obj[:-1] + " }" + print(f".. code-block:: JavaScript\n\n {json_obj}\n\n") + +Reverse PyATS Mapper +==================== +.. exec:: + import json + from netutils.lib_mapper import PYATS_LIB_MAPPER_REVERSE + json_obj = json.dumps(PYATS_LIB_MAPPER_REVERSE, sort_keys=True, indent=4) + json_obj = json_obj[:-1] + " }" + print(f".. code-block:: JavaScript\n\n {json_obj}\n\n") + +Scrapli Mapper +============== +.. exec:: + import json + from netutils.lib_mapper import SCRAPLI_LIB_MAPPER + json_obj = json.dumps(SCRAPLI_LIB_MAPPER, sort_keys=True, indent=4) + json_obj = json_obj[:-1] + " }" + print(f".. code-block:: JavaScript\n\n {json_obj}\n\n") + +Reverse Scrapli Mapper +====================== +.. exec:: + import json + from netutils.lib_mapper import SCRAPLI_LIB_MAPPER_REVERSE + json_obj = json.dumps(SCRAPLI_LIB_MAPPER_REVERSE, sort_keys=True, indent=4) + json_obj = json_obj[:-1] + " }" + print(f".. code-block:: JavaScript\n\n {json_obj}\n\n") + +NTC Templates Mapper +==================== +.. exec:: + import json + from netutils.lib_mapper import NTCTEMPLATES_LIB_MAPPER + json_obj = json.dumps(NTCTEMPLATES_LIB_MAPPER, sort_keys=True, indent=4) + json_obj = json_obj[:-1] + " }" + print(f".. code-block:: JavaScript\n\n {json_obj}\n\n") + +Reverse NTC Templates Mapper +============================ +.. exec:: + import json + from netutils.lib_mapper import NTCTEMPLATES_LIB_MAPPER_REVERSE + json_obj = json.dumps(NTCTEMPLATES_LIB_MAPPER_REVERSE, sort_keys=True, indent=4) + json_obj = json_obj[:-1] + " }" + print(f".. code-block:: JavaScript\n\n {json_obj}\n\n") + + diff --git a/docs/source/sphinxext/exec.py b/docs/source/sphinxext/exec.py new file mode 100644 index 00000000..f6929b15 --- /dev/null +++ b/docs/source/sphinxext/exec.py @@ -0,0 +1,46 @@ +"""File used to define sphinx exec directive.""" +import sys +from os.path import basename + +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + +from docutils.parsers.rst import Directive +from docutils import nodes, statemachine + + +class ExecDirective(Directive): + """Execute the specified python code and insert the output into the document.""" + + has_content = True + + def run(self): + """Function used when adding the directive to an index.rst.""" + old_stdoutout, sys.stdout = sys.stdout, StringIO() + + tab_width = self.options.get("tab-width", self.state.document.settings.tab_width) + source = self.state_machine.input_lines.source(self.lineno - self.state_machine.input_offset - 1) + + try: + exec("\n".join(self.content)) # pylint: disable=exec-used + text = sys.stdout.getvalue() + lines = statemachine.string2lines(text, tab_width, convert_whitespace=True) + self.state_machine.insert_input(lines, source) + return [] + except Exception: # pylint: disable=W0703 + return [ + nodes.error( + None, + nodes.paragraph(text="Unable to execute python code at %s:%d:" % (basename(source), self.lineno)), + nodes.paragraph(text=str(sys.exc_info()[1])), + ) + ] + finally: + sys.stdout = old_stdoutout + + +def setup(app): + """Adds class as sphinx directive.""" + app.add_directive("exec", ExecDirective) diff --git a/netutils/lib_mapper.py b/netutils/lib_mapper.py index 4b5b6b09..5eb2b939 100644 --- a/netutils/lib_mapper.py +++ b/netutils/lib_mapper.py @@ -180,7 +180,7 @@ "viptela": "cisco_viptella", } -SCRAPLI = { +SCRAPLI_LIB_MAPPER = { "cisco_iosxe": "cisco_ios", "cisco_iosxr": "cisco_xr", "cisco_nxos": "cisco_nxos", diff --git a/pyproject.toml b/pyproject.toml index ebba924c..b2c9b13c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,8 @@ classifiers = [ include = [ "CHANGELOG.md", "LICENSE", - "netutils/protocols.json", + "README.md", + "netutils/protocols.json" ] [tool.poetry.dependencies]