Capstone
From Source of Truth to an Actionable Problem
What you'll learn
- The one scenario this entire module builds toward: an NTP server migration
- NetBox-sourced inventory feeding directly into NAPALM state collection
- Classifying a real fleet as compliant vs. non-compliant against one requirement
- What this stage does NOT re-teach, and why
Listen to a guided explanation of this lesson.
Spoken narration isn't supported in this browser.
One scenario, five lessons, one system
The Capstone in one sentence
The organization is migrating its NTP configuration to a new server, 10.10.10.2. This module builds one coherent, cumulative system that detects which devices haven't migrated, generates and Git-evidences the exact fix, rolls it out through a canary gate, and produces one auditable report -- across five lessons, ending in one complete script. No new automation framework appears anywhere in this module.
What this stage integrates
- NetBox-shaped source of truthNetBox / Source of Truth, Real-world Projects
- NetBoxInventory2 -> dynamic Nornir inventoryuse_platform_napalm_driver, unchanged
- napalm_get(get_config) across the fleetMulti-vendor Automation, NAPALM
- Classify: compliant vs. non-compliantthis stage's one new output
Not re-taught here
How NetBoxInventory2 resolves platforms, how nornir_napalm selects a driver, and how NAPALM's mock driver serves fixture data are all assumed knowledge from NetBox / Source of Truth, Multi-vendor Automation, and Real-world Projects' own Multi-Vendor Configuration Compliance Report. Nothing below re-explains them.
The fleet and the requirement
Four devices, three vendor platforms, one requirement: ntp server 10.10.10.2 must be present in each device's running configuration.
DEVICES = [
{"id": 101, "name": "RTR1", "platform": {"id": 1, "slug": "cisco_ios", "name": "Cisco IOS"}, "site": {"id": 1, "slug": "hq"}, "primary_ip": None},
{"id": 102, "name": "RTR2", "platform": {"id": 1, "slug": "cisco_ios", "name": "Cisco IOS"}, "site": {"id": 1, "slug": "hq"}, "primary_ip": None},
{"id": 103, "name": "SW-EOS1", "platform": {"id": 2, "slug": "arista_eos", "name": "Arista EOS"}, "site": {"id": 1, "slug": "hq"}, "primary_ip": None},
{"id": 104, "name": "SW-JUNOS1", "platform": {"id": 3, "slug": "junos", "name": "Juniper Junos"}, "site": {"id": 1, "slug": "hq"}, "primary_ip": None},
]Same freshly-built, stdlib-only NetBox-shaped mock pattern as Real-world Projects (two endpoints: /api/dcim/platforms/, /api/dcim/devices/). Three vendors across four devices -- rebuilt fresh for this module, not reused from any earlier phase's file.
The code
import os
from nornir import InitNornir
from nornir.core.inventory import ConnectionOptions
from nornir_napalm.plugins.tasks import napalm_get
NEW_NTP_SERVER = "10.10.10.2"
REQUIRED_NTP_LINE = f"ntp server {NEW_NTP_SERVER}"
def load_fleet_from_netbox(netbox_url, fixtures_dir):
nr = InitNornir(
inventory={
"plugin": "NetBoxInventory2",
"options": {
"nb_url": netbox_url,
"nb_token": "fake-token-for-mock-server",
"use_platform_napalm_driver": True,
"group_file": "inventory/groups.yaml",
"defaults_file": "inventory/defaults.yaml",
},
},
)
for host in nr.inventory.hosts.values():
host.connection_options["napalm"] = ConnectionOptions(
extras={"optional_args": {"path": os.path.join(fixtures_dir, host.name)}}
)
return nr
def _pull_config(task):
task.run(task=napalm_get, getters=["get_config"])
def detect_noncompliance(nr):
agg = nr.run(task=_pull_config)
compliant = []
non_compliant = []
for hostname, multi_result in agg.items():
running_config = multi_result[1].result["get_config"]["running"]
host = nr.inventory.hosts[hostname]
vendor = host.data.get("platform", {}).get("slug", "unknown")
if REQUIRED_NTP_LINE in running_config:
compliant.append({"host": hostname, "vendor": vendor})
else:
non_compliant.append({"host": hostname, "vendor": vendor})
return {"compliant": compliant, "non_compliant": non_compliant}Real, verified code. load_fleet_from_netbox() reuses NetBoxInventory2 exactly as configured in Real-world Projects; detect_noncompliance() reuses the same get_config()-substring compliance check, this time producing a result the next lesson consumes directly.
Real, verified output
--- Detection (fleet loaded from NetBox) ---
compliant: [{'host': 'SW-JUNOS1', 'vendor': 'junos'}]
non_compliant: [
{'host': 'RTR1', 'vendor': 'cisco_ios'},
{'host': 'RTR2', 'vendor': 'cisco_ios'},
{'host': 'SW-EOS1', 'vendor': 'arista_eos'}
]Real, verified. SW-JUNOS1 already carries "ntp server 10.10.10.2" and is correctly classified compliant; the other three devices, across two other vendors, are correctly detected as non-compliant -- both outcomes genuinely produced by the same run, not asserted separately.
What this does and doesn't prove
Never claimed here
This ran against a freshly-built, stdlib-only NetBox-shaped mock and NAPALM's real mock driver -- never real NetBox, never a real device. The detection logic itself is genuinely verified; whether any real fleet's NTP configuration matches this result is a separate, untested question.
Key takeaways
- One scenario drives the entire module: migrating the fleet's NTP configuration to 10.10.10.2.
- load_fleet_from_netbox() and detect_noncompliance() reuse already-verified mechanisms from NetBox / Source of Truth, Multi-vendor Automation, and Real-world Projects -- nothing here is a new automation primitive.
- Real, verified run: 1 compliant device and 3 non-compliant devices across three vendor platforms, both outcomes genuinely produced by the same execution.
- The detection result is not a final report here -- it is the input the next four lessons build on.
- Mock NetBox and mock NAPALM driver only -- never claimed as evidence about real infrastructure.
Done with this lesson?
Next lesson
Generating Remediation from Detected State