From f68252137e566610294a6860ec25c8f92f677998 Mon Sep 17 00:00:00 2001 From: Adam Byczkowski Date: Tue, 17 Aug 2021 13:51:10 -0500 Subject: [PATCH 1/5] Added document page for lib_mappings --- docs/source/conf.py | 3 +- docs/source/netutils/index.rst | 1 + docs/source/netutils/lib_mapping/index.rst | 115 +++++++++++++++++++++ docs/source/sphinxext/exec.py | 34 ++++++ netutils/lib_mapper.py | 2 +- 5 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 docs/source/netutils/lib_mapping/index.rst create mode 100644 docs/source/sphinxext/exec.py 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..5c8dc67d --- /dev/null +++ b/docs/source/netutils/lib_mapping/index.rst @@ -0,0 +1,115 @@ +***************** +Library Mappings +***************** + +These dictionaries provide mappings in expected vendor names between Netmiko, NAPALM, pyntc, ntc-templates, pyats, and scrapli. + +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..073c57ad --- /dev/null +++ b/docs/source/sphinxext/exec.py @@ -0,0 +1,34 @@ +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): + oldStdout, 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)) + text = sys.stdout.getvalue() + lines = statemachine.string2lines(text, tab_width, convert_whitespace=True) + self.state_machine.insert_input(lines, source) + return [] + except Exception: + 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 = oldStdout + +def setup(app): + app.add_directive('exec', ExecDirective) \ No newline at end of file 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", From eaf206c7e72178358127547e1fa83d12cf275d5b Mon Sep 17 00:00:00 2001 From: Adam Byczkowski Date: Tue, 17 Aug 2021 14:04:22 -0500 Subject: [PATCH 2/5] Updated for linting --- docs/source/sphinxext/exec.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/docs/source/sphinxext/exec.py b/docs/source/sphinxext/exec.py index 073c57ad..f6929b15 100644 --- a/docs/source/sphinxext/exec.py +++ b/docs/source/sphinxext/exec.py @@ -1,3 +1,4 @@ +"""File used to define sphinx exec directive.""" import sys from os.path import basename @@ -9,26 +10,37 @@ 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""" + """Execute the specified python code and insert the output into the document.""" + has_content = True def run(self): - oldStdout, sys.stdout = sys.stdout, StringIO() + """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) + 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)) + 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: - 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])))] + 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 = oldStdout + sys.stdout = old_stdoutout + def setup(app): - app.add_directive('exec', ExecDirective) \ No newline at end of file + """Adds class as sphinx directive.""" + app.add_directive("exec", ExecDirective) From f08759954bbc985a5555ecb80f3d2db4153874ad Mon Sep 17 00:00:00 2001 From: Adam Byczkowski Date: Tue, 17 Aug 2021 14:31:14 -0500 Subject: [PATCH 3/5] Updated for bandit --- .bandit.yml | 1 + 1 file changed, 1 insertion(+) 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/" From 904582d268e84fad2ff59a5210b84cedd3d40a1a Mon Sep 17 00:00:00 2001 From: Adam Byczkowski Date: Fri, 27 Aug 2021 00:58:08 -0500 Subject: [PATCH 4/5] Updated lib mapping description and added README for pypi --- docs/source/netutils/lib_mapping/index.rst | 40 ++++++++++++++++++++-- pyproject.toml | 1 + 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/docs/source/netutils/lib_mapping/index.rst b/docs/source/netutils/lib_mapping/index.rst index 5c8dc67d..4953717e 100644 --- a/docs/source/netutils/lib_mapping/index.rst +++ b/docs/source/netutils/lib_mapping/index.rst @@ -2,10 +2,46 @@ Library Mappings ***************** -These dictionaries provide mappings in expected vendor names between Netmiko, NAPALM, pyntc, ntc-templates, pyats, and scrapli. +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 diff --git a/pyproject.toml b/pyproject.toml index 20fffc0c..c7ddfeae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ classifiers = [ include = [ "CHANGELOG.md", "LICENSE", + "README.md" ] [tool.poetry.dependencies] From c2bf74a039c79c5e8ff03195aef2265d47cebcbd Mon Sep 17 00:00:00 2001 From: Adam Byczkowski Date: Tue, 31 Aug 2021 14:23:14 -0500 Subject: [PATCH 5/5] Added missing comma --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c50363fb..b2c9b13c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,8 +21,8 @@ classifiers = [ include = [ "CHANGELOG.md", "LICENSE", - "README.md" - "netutils/protocols.json", + "README.md", + "netutils/protocols.json" ] [tool.poetry.dependencies]