NetAuto Academy

NetBox / Source of Truth

Capstone: A Source-of-Truth-Driven Fleet Health Check

Advanced18 min read

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
🎓 Teach This Lesson

Listen to a guided explanation of this lesson.

Spoken narration isn't supported in this browser.

The full pipeline, real and verified

  1. NetBox device recordsLesson 2 -- site, platform, primary_ip
  2. pynetbox / NetBoxInventory2Lesson 3 + 5 -- real REST client + inventory plugin
  3. dynamic Nornir inventorybuilt fresh on every InitNornir() call
  4. nr.run(task=napalm_get, ...)Phase 3.6 -- unchanged task execution
  5. health/result report
Quick checkConcept check
What does this capstone combine that no single prior lesson (in this module or Phase 3.4-3.6) demonstrated alone?

The full script

capstone_source_of_truth_health_check.py
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.

Quick checkIdentify the correct code
Why does the script override host.platform to 'mock' after loading the inventory, rather than relying on NetBox's real platform values directly?

Real, verified end-to-end output

terminal output — real, verified run
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.json

Real, verified output from an actual run of the script above.

source_of_truth_report.json — real, verified content
{
"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.

verify_dynamic_inventory_growth.py -- real, executed
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.

output — real, verified
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.

Quick checkPredict output
Based on the real, verified output above, what would a team need to do to onboard a new device into every script that uses this capstone's inventory-loading pattern?

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.

Quick checkScenario
How should a learner honestly describe what this capstone proves?

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.