Skip to content

Add ability to get the lib mapper in a primary dictionary and a funct… #644

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion docs/user/lib_use_cases_lib_mapper.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ sot_driver = device.platform.napalm_driver


# Connect to device via Napalm
driver = napalm.get_network_driver("ios")
driver = napalm.get_network_driver(sot_driver)

device = driver(
hostname="device.name",
Expand All @@ -38,6 +38,20 @@ net_con = NTC(host=device.name, username="demo", password="secret", device_type=

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.

There is also a dynamically built mapping that gives you all of the libraries given a normalized name, here is a condensed snippet to understand the data structure of `NAME_TO_ALL_LIB_MAPPER`:

```python
{
"cisco_ios": {
"ansible": "cisco.ios.ios",
"napalm": "ios",
},
"cisco_nxos": {
"ansible": "cisco.nxos.nxos",
"napalm": "nxos",
}
}
```

## Aerleon Mapper

Expand Down
63 changes: 61 additions & 2 deletions netutils/lib_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,20 +125,26 @@
}

# DNA Center | Normalized
DNA_CENTER_LIB_MAPPER = {
DNACENTER_LIB_MAPPER = {
"IOS": "cisco_ios",
"IOS-XE": "cisco_ios",
"NX-OS": "cisco_nxos",
"IOS-XR": "cisco_xr",
}

# REMOVE IN 2.X, kept for backward compatibility
DNA_CENTER_LIB_MAPPER = copy.deepcopy(DNACENTER_LIB_MAPPER)

# Normalized | DNA Center
DNA_CENTER_LIB_MAPPER_REVERSE = {
DNACENTER_LIB_MAPPER_REVERSE = {
"cisco_ios": "IOS",
"cisco_nxos": "NX-OS",
"cisco_xr": "IOS-XR",
}

# REMOVE IN 2.X, kept for backward compatibility
DNA_CENTER_LIB_MAPPER_REVERSE = copy.deepcopy(DNACENTER_LIB_MAPPER_REVERSE)

# Normalized | Netmiko
NETMIKO_LIB_MAPPER: t.Dict[str, str] = {
"a10": "a10",
Expand Down Expand Up @@ -641,3 +647,56 @@
_MAIN_LIB_MAPPER["watchguard_firebox"] = "watchguard_firebox"
_MAIN_LIB_MAPPER["windows"] = "windows"
MAIN_LIB_MAPPER: t.Dict[str, str] = {key: _MAIN_LIB_MAPPER[key] for key in sorted(_MAIN_LIB_MAPPER)}

NAME_TO_LIB_MAPPER: t.Dict[str, t.Dict[str, str]] = {
"aerleon": AERLEON_LIB_MAPPER,
"ansible": ANSIBLE_LIB_MAPPER,
"capirca": CAPIRCA_LIB_MAPPER,
"dna_center": DNACENTER_LIB_MAPPER,
"forward_networks": FORWARDNETWORKS_LIB_MAPPER,
"hier_config": HIERCONFIG_LIB_MAPPER,
"napalm": NAPALM_LIB_MAPPER,
"netmiko": NETMIKO_LIB_MAPPER,
"netutils_parser": NETUTILSPARSER_LIB_MAPPER,
"nist": NIST_LIB_MAPPER,
"ntc_templates": NTCTEMPLATES_LIB_MAPPER,
"pyats": PYATS_LIB_MAPPER,
"pyntc": PYNTC_LIB_MAPPER,
"scrapli": SCRAPLI_LIB_MAPPER,
}


NAME_TO_LIB_MAPPER_REVERSE: t.Dict[str, t.Dict[str, str]] = {
"aerleon": AERLEON_LIB_MAPPER_REVERSE,
"ansible": ANSIBLE_LIB_MAPPER_REVERSE,
"capirca": CAPIRCA_LIB_MAPPER_REVERSE,
"dna_center": DNACENTER_LIB_MAPPER_REVERSE,
"forward_networks": FORWARDNETWORKS_LIB_MAPPER_REVERSE,
"hier_config": HIERCONFIG_LIB_MAPPER_REVERSE,
"napalm": NAPALM_LIB_MAPPER_REVERSE,
"netmiko": NETMIKO_LIB_MAPPER_REVERSE,
"netutils_parser": NETUTILSPARSER_LIB_MAPPER_REVERSE,
"nist": NIST_LIB_MAPPER_REVERSE,
"ntc_templates": NTCTEMPLATES_LIB_MAPPER_REVERSE,
"pyats": PYATS_LIB_MAPPER_REVERSE,
"pyntc": PYNTC_LIB_MAPPER_REVERSE,
"scrapli": SCRAPLI_LIB_MAPPER_REVERSE,
}


# Creates a structure like this:
# {
# "cisco_ios": {
# "ansible": "cisco.ios.ios",
# "napalm": "ios",
# },
# "cisco_nxos": {
# "ansible": "cisco.nxos.nxos",
# "napalm": "nxos",
# },
NAME_TO_ALL_LIB_MAPPER: t.Dict[str, t.Dict[str, str]] = {}

for tool_name, mappings in NAME_TO_LIB_MAPPER_REVERSE.items():
for normalized_name, mapped_name in mappings.items():
NAME_TO_ALL_LIB_MAPPER.setdefault(normalized_name, {})
NAME_TO_ALL_LIB_MAPPER[normalized_name][tool_name] = mapped_name
49 changes: 47 additions & 2 deletions tests/unit/test_lib_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,24 @@
"SCRAPLI",
]

MAPPERS = {}
REVERSE_MAPPERS = {}

# Collect all variables ending with _LIB_MAPPER and _LIB_MAPPER_REVERSE
for name in dir(lib_mapper):
value = getattr(lib_mapper, name)

if not isinstance(value, dict) or any(
name.startswith(prefix) for prefix in ["NAME_TO", "KEY_TO", "_", "MAIN", "DNA_CENTER"]
):
continue
if name.endswith("_LIB_MAPPER") and isinstance(value, dict):
lib_name = name.replace("_LIB_MAPPER", "").lower()
MAPPERS[lib_name] = value
elif name.endswith("_LIB_MAPPER_REVERSE") and isinstance(value, dict):
lib_name = name.replace("_LIB_MAPPER_REVERSE", "").lower()
REVERSE_MAPPERS[lib_name] = value


def test_lib_mapper():
assert len(lib_mapper.MAIN_LIB_MAPPER.keys()) > 40
Expand Down Expand Up @@ -96,6 +114,13 @@ def test_lib_mapper_ntctemplates_reverse_only():
assert lib_mapper.NTCTEMPLATES_LIB_MAPPER["cisco_xe"] == "cisco_xe"


def test_name_to_all_lib_mapper():
"""Test that the data structure returns as expected in NAME_TO_ALL_LIB_MAPPER."""
assert lib_mapper.NAME_TO_ALL_LIB_MAPPER["arista_eos"]["ansible"] == "arista.eos.eos"
assert lib_mapper.NAME_TO_ALL_LIB_MAPPER["arista_eos"]["pyntc"] == "arista_eos_eapi"
assert lib_mapper.NAME_TO_ALL_LIB_MAPPER["cisco_ios"]["dna_center"] == "IOS"


@pytest.mark.parametrize("lib", LIBRARIES)
def test_lib_mapper_alpha(lib):
original = list(getattr(lib_mapper, f"{lib}_LIB_MAPPER").keys())
Expand All @@ -117,5 +142,25 @@ def test_lib_mapper_normalized_name(lib):
"""Ensure that MAIN_LIB_MAPPER is kept up to date."""
for key in getattr(lib_mapper, f"{lib}_LIB_MAPPER_REVERSE").keys():
assert key in lib_mapper.MAIN_LIB_MAPPER
for value in getattr(lib_mapper, f"{lib}_LIB_MAPPER").values():
assert value in lib_mapper.MAIN_LIB_MAPPER
for attr in getattr(lib_mapper, f"{lib}_LIB_MAPPER").values():
assert attr in lib_mapper.MAIN_LIB_MAPPER


def test_all_mappers_included():
"""Ensure NAME_TO_LIB_MAPPER includes all _LIB_MAPPER dictionaries."""
expected_libs = set(MAPPERS.keys())
actual_libs = {lib.replace("_", "") for lib in lib_mapper.NAME_TO_LIB_MAPPER.keys()}

# Check for missing libraries
missing = expected_libs - actual_libs
assert len(missing) == 0, f"NAME_TO_LIB_MAPPER is missing libraries: {missing}"


def test_all_reverse_mappers_included():
"""Ensure NAME_TO_LIB_MAPPER_REVERSE includes all _LIB_MAPPER_REVERSE dictionaries."""
expected_libs = set(MAPPERS.keys())
actual_libs = {lib.replace("_", "") for lib in lib_mapper.NAME_TO_LIB_MAPPER_REVERSE.keys()}

# Check for missing libraries
missing = expected_libs - actual_libs
assert len(missing) == 0, f"NAME_TO_LIB_MAPPER_REVERSE is missing libraries: {missing}"
Loading