NetBox / Source of Truth
Capstone: A Source-of-Truth-Driven Fleet Health Check
What you'll learn
- Combining NetBox records, pynetbox, dynamic inventory, and task execution into one script
- The real, verified proof this module has built toward since Lesson 1
- Reading a real, verified end-to-end run and its JSON report
- Where Option A (a real NetBox server) fits versus this capstone's Option B path
Listen to a guided explanation of this lesson.
Spoken narration isn't supported in this browser.
The full pipeline, real and verified
- NetBox device recordsLesson 2 -- site, platform, primary_ip
- pynetbox / NetBoxInventory2Lesson 3 + 5 -- real REST client + inventory plugin
- dynamic Nornir inventorybuilt fresh on every InitNornir() call
- nr.run(task=napalm_get, ...)Phase 3.6 -- unchanged task execution
- health/result report
The full script
import json
import os
from nornir import InitNornir
from nornir.core.inventory import ConnectionOptions
from nornir_napalm.plugins.tasks import napalm_get
EXPECTED_DESCRIPTION = "mgmt-uplink"
nr = InitNornir(
inventory={
"plugin": "NetBoxInventory2",
"options": {
"nb_url": "http://127.0.0.1:8004",
"nb_token": os.environ.get("NETBOX_TOKEN", "test-token"),
"use_platform_slug": True,
},
},
)
# NetBox supplies device identity and platform (Lesson 5). For this offline
# capstone, platform is overridden to "mock" -- NAPALM's own real mock
# driver (Phase 3.5) -- and wired to each host's own fixture directory.
# In a real deployment this override wouldn't exist: platform would
# already be "ios"/"eos" from NetBox, resolving straight to a real driver
# (exactly as use_platform_napalm_driver showed in Lesson 5).
for hostname, host in nr.inventory.hosts.items():
host.platform = "mock"
host.connection_options["napalm"] = ConnectionOptions(
extras={"optional_args": {"path": os.path.join("fixtures", hostname.lower())}}
)
print(f"Loaded {len(nr.inventory.hosts)} devices from NetBox: {list(nr.inventory.hosts.keys())}")
agg = nr.run(task=napalm_get, getters=["get_facts", "get_interfaces"])
report = {"compliant": [], "non_compliant": [], "unreachable": []}
for hostname, multi_result in agg.items():
r = multi_result[0]
if r.failed:
report["unreachable"].append({"host": hostname, "detail": str(r.exception)})
continue
facts = r.result["get_facts"]
interfaces = r.result["get_interfaces"]
mgmt_interface = facts["interface_list"][0]
description = interfaces[mgmt_interface]["description"]
if description == EXPECTED_DESCRIPTION:
report["compliant"].append({"host": hostname, "vendor": facts["vendor"]})
else:
report["non_compliant"].append({
"host": hostname, "vendor": facts["vendor"],
"detail": f"description mismatch: expected '{EXPECTED_DESCRIPTION}', got '{description}'",
})
print(f"Checked: {len(agg)} devices")
print(f"compliant: {len(report['compliant'])}")
print(f"non-compliant: {len(report['non_compliant'])}")
print(f"unreachable: {len(report['unreachable'])}")
with open("source_of_truth_report.json", "w") as f:
json.dump(report, f, indent=2)
print("Report written to source_of_truth_report.json")The complete, real, verified capstone script. Reuses Lesson 5's NetBoxInventory2 exactly, and Phase 3.6's napalm_get task exactly -- no new task-execution code written.
Real, verified end-to-end output
Loaded 3 devices from NetBox: ['SW1', 'SW2', 'SW3']
Checked: 3 devices
compliant: 2
non-compliant: 1
unreachable: 0
Report written to source_of_truth_report.jsonReal, verified output from an actual run of the script above.
{
"compliant": [
{ "host": "SW1", "vendor": "Cisco" },
{ "host": "SW2", "vendor": "Arista" }
],
"non_compliant": [
{ "host": "SW3", "vendor": "Cisco",
"detail": "description mismatch: expected 'mgmt-uplink', got 'uplink'" }
],
"unreachable": []
}Real, verified file content -- grep-confirmed to contain no credential of any kind.
The proof this module built toward
Lesson 1's preview, now fully demonstrated
Add one device record to NetBox. Touch zero local files. Run the exact same script again. The fleet grows.
def load():
nr = InitNornir(inventory={"plugin": "NetBoxInventory2", "options": {
"nb_url": "http://127.0.0.1:8005",
"nb_token": os.environ.get("NETBOX_TOKEN", "test-token"),
"use_platform_slug": True,
}})
return list(nr.inventory.hosts.keys())
print("Before adding a device to NetBox:", load())
# ... a device record for SW4 is added directly on the NetBox backend ...
print("After adding SW4 in NetBox (no local file changed):", load())Real, executed. Between the two load() calls, only the NetBox backend's own device data changed -- the loading code itself never did.
Before adding a device to NetBox: ['SW1', 'SW2', 'SW3']
After adding SW4 in NetBox (no local file changed): ['SW1', 'SW2', 'SW3', 'SW4']Real, verified output. This is the exact claim Lesson 1 opened with -- now demonstrated directly, not merely asserted.
Option A vs. Option B — and what this module never claims
Never overstated
Option A: pointing this exact script at a real, running NetBox server -- never executed here. No NetBox server, self-hosted or hosted demo, was installed or contacted at any point in this module. Option B (used throughout): a local, stdlib-only mock reproducing NetBox's real, documented REST shape. The mechanism -- pynetbox, NetBoxInventory2, dynamic inventory growth -- is genuinely verified (Category A). Real NetBox server behavior beyond what this mock reproduces stays Category B/C, exactly as stated in every lesson of this module.
Key takeaways
- This capstone combines every piece of the module: NetBox records, pynetbox, NetBoxInventory2's dynamic inventory, and Phase 3.6's unchanged task execution -- all verified together in one real, runnable script.
- Real, verified run: 2 compliant, 1 non-compliant, 0 unreachable across the 3-device NetBox-sourced fleet -- written to source_of_truth_report.json with zero credentials on disk.
- The proof this module opened with in Lesson 1 -- adding a device in NetBox grows the automation inventory with no local file touched -- was demonstrated directly, not just claimed.
- Only the integration mechanism is Category A here -- no real NetBox server was ever installed or contacted, and this is never overstated.
- Next: Git / NetDevOps -- version-controlling this same source-of-truth data, not a new orchestration or normalization mechanism.