Skip to content

Commit d47a3d7

Browse files
Add ability to get the lib mapper in a primary dictionary and a function to munge the data in a os centric view versus a lib centric view. (#644)
1 parent c41a402 commit d47a3d7

File tree

3 files changed

+123
-5
lines changed

3 files changed

+123
-5
lines changed

docs/user/lib_use_cases_lib_mapper.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ sot_driver = device.platform.napalm_driver
2020

2121

2222
# Connect to device via Napalm
23-
driver = napalm.get_network_driver("ios")
23+
driver = napalm.get_network_driver(sot_driver)
2424

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

3939
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.
4040

41+
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`:
42+
43+
```python
44+
{
45+
"cisco_ios": {
46+
"ansible": "cisco.ios.ios",
47+
"napalm": "ios",
48+
},
49+
"cisco_nxos": {
50+
"ansible": "cisco.nxos.nxos",
51+
"napalm": "nxos",
52+
}
53+
}
54+
```
4155

4256
## Aerleon Mapper
4357

netutils/lib_mapper.py

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,20 +125,26 @@
125125
}
126126

127127
# DNA Center | Normalized
128-
DNA_CENTER_LIB_MAPPER = {
128+
DNACENTER_LIB_MAPPER = {
129129
"IOS": "cisco_ios",
130130
"IOS-XE": "cisco_ios",
131131
"NX-OS": "cisco_nxos",
132132
"IOS-XR": "cisco_xr",
133133
}
134134

135+
# REMOVE IN 2.X, kept for backward compatibility
136+
DNA_CENTER_LIB_MAPPER = copy.deepcopy(DNACENTER_LIB_MAPPER)
137+
135138
# Normalized | DNA Center
136-
DNA_CENTER_LIB_MAPPER_REVERSE = {
139+
DNACENTER_LIB_MAPPER_REVERSE = {
137140
"cisco_ios": "IOS",
138141
"cisco_nxos": "NX-OS",
139142
"cisco_xr": "IOS-XR",
140143
}
141144

145+
# REMOVE IN 2.X, kept for backward compatibility
146+
DNA_CENTER_LIB_MAPPER_REVERSE = copy.deepcopy(DNACENTER_LIB_MAPPER_REVERSE)
147+
142148
# Normalized | Netmiko
143149
NETMIKO_LIB_MAPPER: t.Dict[str, str] = {
144150
"a10": "a10",
@@ -651,3 +657,56 @@
651657
_MAIN_LIB_MAPPER["watchguard_firebox"] = "watchguard_firebox"
652658
_MAIN_LIB_MAPPER["windows"] = "windows"
653659
MAIN_LIB_MAPPER: t.Dict[str, str] = {key: _MAIN_LIB_MAPPER[key] for key in sorted(_MAIN_LIB_MAPPER)}
660+
661+
NAME_TO_LIB_MAPPER: t.Dict[str, t.Dict[str, str]] = {
662+
"aerleon": AERLEON_LIB_MAPPER,
663+
"ansible": ANSIBLE_LIB_MAPPER,
664+
"capirca": CAPIRCA_LIB_MAPPER,
665+
"dna_center": DNACENTER_LIB_MAPPER,
666+
"forward_networks": FORWARDNETWORKS_LIB_MAPPER,
667+
"hier_config": HIERCONFIG_LIB_MAPPER,
668+
"napalm": NAPALM_LIB_MAPPER,
669+
"netmiko": NETMIKO_LIB_MAPPER,
670+
"netutils_parser": NETUTILSPARSER_LIB_MAPPER,
671+
"nist": NIST_LIB_MAPPER,
672+
"ntc_templates": NTCTEMPLATES_LIB_MAPPER,
673+
"pyats": PYATS_LIB_MAPPER,
674+
"pyntc": PYNTC_LIB_MAPPER,
675+
"scrapli": SCRAPLI_LIB_MAPPER,
676+
}
677+
678+
679+
NAME_TO_LIB_MAPPER_REVERSE: t.Dict[str, t.Dict[str, str]] = {
680+
"aerleon": AERLEON_LIB_MAPPER_REVERSE,
681+
"ansible": ANSIBLE_LIB_MAPPER_REVERSE,
682+
"capirca": CAPIRCA_LIB_MAPPER_REVERSE,
683+
"dna_center": DNACENTER_LIB_MAPPER_REVERSE,
684+
"forward_networks": FORWARDNETWORKS_LIB_MAPPER_REVERSE,
685+
"hier_config": HIERCONFIG_LIB_MAPPER_REVERSE,
686+
"napalm": NAPALM_LIB_MAPPER_REVERSE,
687+
"netmiko": NETMIKO_LIB_MAPPER_REVERSE,
688+
"netutils_parser": NETUTILSPARSER_LIB_MAPPER_REVERSE,
689+
"nist": NIST_LIB_MAPPER_REVERSE,
690+
"ntc_templates": NTCTEMPLATES_LIB_MAPPER_REVERSE,
691+
"pyats": PYATS_LIB_MAPPER_REVERSE,
692+
"pyntc": PYNTC_LIB_MAPPER_REVERSE,
693+
"scrapli": SCRAPLI_LIB_MAPPER_REVERSE,
694+
}
695+
696+
697+
# Creates a structure like this:
698+
# {
699+
# "cisco_ios": {
700+
# "ansible": "cisco.ios.ios",
701+
# "napalm": "ios",
702+
# },
703+
# "cisco_nxos": {
704+
# "ansible": "cisco.nxos.nxos",
705+
# "napalm": "nxos",
706+
# },
707+
NAME_TO_ALL_LIB_MAPPER: t.Dict[str, t.Dict[str, str]] = {}
708+
709+
for tool_name, mappings in NAME_TO_LIB_MAPPER_REVERSE.items():
710+
for normalized_name, mapped_name in mappings.items():
711+
NAME_TO_ALL_LIB_MAPPER.setdefault(normalized_name, {})
712+
NAME_TO_ALL_LIB_MAPPER[normalized_name][tool_name] = mapped_name

tests/unit/test_lib_mapper.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,24 @@
2020
"SCRAPLI",
2121
]
2222

23+
MAPPERS = {}
24+
REVERSE_MAPPERS = {}
25+
26+
# Collect all variables ending with _LIB_MAPPER and _LIB_MAPPER_REVERSE
27+
for name in dir(lib_mapper):
28+
value = getattr(lib_mapper, name)
29+
30+
if not isinstance(value, dict) or any(
31+
name.startswith(prefix) for prefix in ["NAME_TO", "KEY_TO", "_", "MAIN", "DNA_CENTER"]
32+
):
33+
continue
34+
if name.endswith("_LIB_MAPPER") and isinstance(value, dict):
35+
lib_name = name.replace("_LIB_MAPPER", "").lower()
36+
MAPPERS[lib_name] = value
37+
elif name.endswith("_LIB_MAPPER_REVERSE") and isinstance(value, dict):
38+
lib_name = name.replace("_LIB_MAPPER_REVERSE", "").lower()
39+
REVERSE_MAPPERS[lib_name] = value
40+
2341

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

98116

117+
def test_name_to_all_lib_mapper():
118+
"""Test that the data structure returns as expected in NAME_TO_ALL_LIB_MAPPER."""
119+
assert lib_mapper.NAME_TO_ALL_LIB_MAPPER["arista_eos"]["ansible"] == "arista.eos.eos"
120+
assert lib_mapper.NAME_TO_ALL_LIB_MAPPER["arista_eos"]["pyntc"] == "arista_eos_eapi"
121+
assert lib_mapper.NAME_TO_ALL_LIB_MAPPER["cisco_ios"]["dna_center"] == "IOS"
122+
123+
99124
@pytest.mark.parametrize("lib", LIBRARIES)
100125
def test_lib_mapper_alpha(lib):
101126
original = list(getattr(lib_mapper, f"{lib}_LIB_MAPPER").keys())
@@ -117,5 +142,25 @@ def test_lib_mapper_normalized_name(lib):
117142
"""Ensure that MAIN_LIB_MAPPER is kept up to date."""
118143
for key in getattr(lib_mapper, f"{lib}_LIB_MAPPER_REVERSE").keys():
119144
assert key in lib_mapper.MAIN_LIB_MAPPER
120-
for value in getattr(lib_mapper, f"{lib}_LIB_MAPPER").values():
121-
assert value in lib_mapper.MAIN_LIB_MAPPER
145+
for attr in getattr(lib_mapper, f"{lib}_LIB_MAPPER").values():
146+
assert attr in lib_mapper.MAIN_LIB_MAPPER
147+
148+
149+
def test_all_mappers_included():
150+
"""Ensure NAME_TO_LIB_MAPPER includes all _LIB_MAPPER dictionaries."""
151+
expected_libs = set(MAPPERS.keys())
152+
actual_libs = {lib.replace("_", "") for lib in lib_mapper.NAME_TO_LIB_MAPPER.keys()}
153+
154+
# Check for missing libraries
155+
missing = expected_libs - actual_libs
156+
assert len(missing) == 0, f"NAME_TO_LIB_MAPPER is missing libraries: {missing}"
157+
158+
159+
def test_all_reverse_mappers_included():
160+
"""Ensure NAME_TO_LIB_MAPPER_REVERSE includes all _LIB_MAPPER_REVERSE dictionaries."""
161+
expected_libs = set(MAPPERS.keys())
162+
actual_libs = {lib.replace("_", "") for lib in lib_mapper.NAME_TO_LIB_MAPPER_REVERSE.keys()}
163+
164+
# Check for missing libraries
165+
missing = expected_libs - actual_libs
166+
assert len(missing) == 0, f"NAME_TO_LIB_MAPPER_REVERSE is missing libraries: {missing}"

0 commit comments

Comments
 (0)