NetAuto Academy

Multi-vendor Automation

Capstone: One Workflow, Three Vendors

Advanced17 min read

What you'll learn

  • Combining driver selection, batched getters, and failure isolation into one script
  • Why this demonstrates something neither Nornir nor NAPALM alone could show as clearly
  • Reading a real, verified end-to-end run and its JSON report
  • What's next: Nornir + NAPALM stay the pattern for any future vendor-aware module
🎓 Teach This Lesson

Listen to a guided explanation of this lesson.

Spoken narration isn't supported in this browser.

What "one workflow, three vendors" actually means

The one thing this capstone must show

Not another VLAN check, and not a second single-vendor NAPALM compliance report (Phase 3.5 already built that). This capstone's entire point: the SAME validation function, unmodified, correctly evaluates a Cisco-like host, an Arista-like host, and a Juniper-like host -- because the getter's shape is guaranteed and the driver was selected automatically from inventory, not because the code branches per vendor.

  1. 4-host inventorySW1/SW2/SW3 mock-driven "vendors", SW4 unreachable
  2. nr.run(task=napalm_get, getters=[...])Lesson 3 -- one call, every host
  3. get_network_driver(host.platform)Lesson 2 -- automatic, per host
  4. normalized get_facts() / get_interfaces()same shape, different real values
  5. one desired-state check, applied identically
  6. compliance classification -> JSON report
Quick checkConcept check
What does this capstone demonstrate that neither a pure-Nornir capstone (Phase 3.4) nor a pure-NAPALM capstone (Phase 3.5) could show as clearly?

The full script

multivendor_report.py
import json
import os
from nornir import InitNornir
from nornir_napalm.plugins.tasks import napalm_get

EXPECTED_DESCRIPTION = "mgmt-uplink"

nr = InitNornir(
  inventory={
      "plugin": "SimpleInventory",
      "options": {
          "host_file": "inventory/hosts.yaml",
          "group_file": "inventory/groups.yaml",
          "defaults_file": "inventory/defaults.yaml",
      },
  },
)
nr.inventory.defaults.username = os.environ.get("NAPALM_USERNAME", "")
nr.inventory.defaults.password = os.environ.get("NAPALM_PASSWORD", "")

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]
  vendor_label = nr.inventory.hosts[hostname]["vendor_label"]

  if r.failed:
      report["unreachable"].append({
          "host": hostname, "vendor_label": vendor_label, "detail": str(r.exception),
      })
      continue

  facts = r.result["get_facts"]
  interfaces = r.result["get_interfaces"]
  # Find the management interface WITHOUT hardcoding a per-vendor name --
  # Cisco calls it GigabitEthernet0/1, Arista Ethernet1, Juniper ge-0/0/0.
  # facts["interface_list"][0] works identically for all three.
  mgmt_interface = facts["interface_list"][0]
  description = interfaces[mgmt_interface]["description"]

  if description == EXPECTED_DESCRIPTION:
      report["compliant"].append({
          "host": hostname, "vendor_label": vendor_label,
          "vendor": facts["vendor"], "interface": mgmt_interface,
      })
  else:
      report["non_compliant"].append({
          "host": hostname, "vendor_label": vendor_label,
          "vendor": facts["vendor"], "interface": mgmt_interface,
          "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("multivendor_report.json", "w") as f:
  json.dump(report, f, indent=2)
print("Report written to multivendor_report.json")

The complete, real, verified capstone script. Note there is no if vendor == 'cisco' anywhere -- facts['interface_list'][0] finds the management interface identically regardless of naming convention.

Quick checkIdentify the correct code
Why does the script use facts['interface_list'][0] to find the management interface, instead of hardcoding 'GigabitEthernet0/1'?

Real, verified end-to-end output

terminal output — real, verified run
Checked: 4 devices
compliant: 2
non-compliant: 1
unreachable: 1
Report written to multivendor_report.json

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

multivendor_report.json — real, verified content
{
"compliant": [
  { "host": "SW1", "vendor_label": "Cisco-like", "vendor": "Cisco", "interface": "GigabitEthernet0/1" },
  { "host": "SW2", "vendor_label": "Arista-like", "vendor": "Arista", "interface": "Ethernet1" }
],
"non_compliant": [
  { "host": "SW3", "vendor_label": "Juniper-like", "vendor": "Juniper", "interface": "ge-0/0/0",
    "detail": "description mismatch: expected 'mgmt-uplink', got 'uplink'" }
],
"unreachable": [
  { "host": "SW4", "vendor_label": "Cisco-like", "detail": "You told me to do this" }
]
}

Real, verified file content -- grep-confirmed to contain no credential of any kind. Three different vendor labels, three different real interface names, one validation function.

Quick checkPredict output
Based on the verified report above, why does SW3 land in non_compliant while its interface field still correctly shows 'ge-0/0/0' -- a name the validation code never hardcoded?

Option A vs. Option B, and what B never means here

Never claim what wasn't tested

Option A -- pointing this exact script at real ios/eos/junos drivers against real, responding devices of each vendor -- was never executed. No multi-vendor lab was available in this authoring environment. The 'Cisco-like'/'Arista-like'/'Juniper-like' labels describe fixture content designed to resemble typical output, not verified real-device behavior. Only the mechanism (driver selection, batched getters, per-device failure isolation) is Category A -- genuinely executed. Do not read this capstone as "multi-vendor support verified."

Quick checkScenario
A learner wants to describe what this capstone actually proves, honestly. Which statement is correct?

What's next

The pattern, not a new one

Nornir + NAPALM (via nornir_napalm where vendor-neutral state matters, or nornir_netmiko where a single vendor's CLI is enough) is now the reusable pattern this curriculum expects going forward. NetBox / Source of Truth, the next planned module, is about modeling that same platform-aware inventory as external data rather than static YAML files -- not a new orchestration or normalization mechanism.

Quick checkConcept check
What is the one-sentence summary of what this module actually taught?

Key takeaways

  • This capstone combines every piece of the module: automatic driver selection from platform, batched napalm_get across a mixed fleet, and per-device failure isolation -- all verified together in one real, runnable script.
  • The validation function never hardcodes a vendor-specific interface name -- facts['interface_list'][0] works identically across all three fixture-driven 'vendors', the concrete payoff of normalization.
  • Real, verified run: 2 compliant, 1 non-compliant, 1 unreachable across the 4-device fleet -- written to multivendor_report.json with zero credentials on disk.
  • Only the integration mechanism is Category A here -- vendor-specific real-device behavior was never tested and is never claimed to be.
  • Next: NetBox / Source of Truth -- modeling this same platform-aware inventory as external data, not a new orchestration mechanism.