NAPALM: One API for Cisco, Arista, and Juniper

Netmiko sends commands and reads text. That is perfect when you know each platform’s CLI. But what if you want to ask “give me the interfaces” and get the same structured answer whether the device is a Cisco IOS-XE router, an Arista switch, or a Juniper firewall — without learning three command syntaxes? That is NAPALM: Network Automation and Programmability Abstraction Layer with Multivendor support. One Python API, many vendors, structured data out, and — the headline feature — safe configuration changes with diffs and rollback.

NAPALM sits one level of abstraction above Netmiko. It is the right tool when you operate a multi-vendor network or when you want config changes you can preview and undo.

Install and the Driver Concept

pip install napalm

NAPALM uses drivers — one per platform. You ask for a driver by name, then open a connection:

from napalm import get_network_driver

driver = get_network_driver("ios")        # ios, eos, junos, nxos, iosxr
device = driver(
    hostname="192.168.1.1",
    username="admin",
    password="cisco123",
    optional_args={"secret": "enablepass"},
)

device.open()
print("Connected")
device.close()

Supported drivers cover the big four: ios, eos (Arista), junos (Juniper), nxos (Nexus), plus iosxr. The point is that everything below works identically across all of them.

Getters: Structured Facts, Same Shape Everywhere

NAPALM’s “getters” are read methods that return normalized dictionaries. The same call returns the same structure regardless of vendor:

device.open()

facts = device.get_facts()
print(facts["hostname"], facts["model"], facts["os_version"])
print(facts["uptime"], "seconds")

interfaces = device.get_interfaces()
for name, data in interfaces.items():
    state = "up" if data["is_up"] else "down"
    print(f"{name}: {state}, MAC {data['mac_address']}")

device.close()

Run the exact same code against an Arista box (driver eos) and you get the same keys — is_up, mac_address, description. That normalization is the whole value proposition. Other common getters: get_interfaces_ip(), get_bgp_neighbors(), get_lldp_neighbors(), get_arp_table(), get_environment().

The Killer Feature: Config Diff and Rollback

This is why people adopt NAPALM. You load a candidate configuration, ask NAPALM what would change, and only then commit — or discard. On platforms without native candidate configs, NAPALM emulates the workflow for you.

device.open()

# Stage a config change (merge = add to running config)
device.load_merge_candidate(config="ntp server 10.0.0.1\nlogging host 10.0.0.50")

# Preview EXACTLY what will change — before touching anything
diff = device.compare_config()
print(diff)
# +ntp server 10.0.0.1
# +logging host 10.0.0.50

if diff:
    device.commit_config()      # apply it
    print("Committed")
else:
    device.discard_config()     # nothing to do
    print("No changes")

device.close()

Read that flow again, because it is a different mindset from Netmiko. You never blindly push. You stage, you see the diff, you decide. For change windows and peer review, compare_config() is gold — you can paste the exact diff into the change ticket.

Merge vs. Replace

Two ways to load config, and the difference matters enormously:

  • load_merge_candidate() — adds your lines to the existing config. Like typing commands at the CLI. Safe and incremental.
  • load_replace_candidate() — replaces the entire running config with the file you provide. Powerful for enforcing a golden config, dangerous if your file is incomplete. The diff preview is your seatbelt here.
# Enforce a full golden config from a file, but LOOK before you leap
device.load_replace_candidate(filename="golden_r1.cfg")
print(device.compare_config())     # review every add AND removal
# commit only if the diff is what you expect

Rollback

If a committed change turns out wrong, NAPALM can roll back to the previous config on platforms that support it:

device.rollback()     # revert the last commit

Cisco Context: A Multi-Vendor Health Snapshot

Because getters are normalized, one function can inventory a mixed fleet — IOS and EOS side by side — with zero per-vendor branching:

from napalm import get_network_driver

fleet = [
    {"driver": "ios", "host": "192.168.1.1"},
    {"driver": "eos", "host": "192.168.1.10"},
]
USER, PW = "admin", "cisco123"

for entry in fleet:
    drv = get_network_driver(entry["driver"])
    dev = drv(hostname=entry["host"], username=USER, password=PW)
    try:
        dev.open()
        f = dev.get_facts()
        down = [n for n, d in dev.get_interfaces().items() if not d["is_up"]]
        print(f"{f['hostname']:12} {f['model']:14} up {f['uptime']//86400}d, "
              f"{len(down)} interfaces down")
    except Exception as e:
        print(f"{entry['host']}: ERROR {e}")
    finally:
        dev.close()

That is the dream: one report across vendors, written once. No if device_type == ... anywhere.

Netmiko or NAPALM? A Quick Rule

Use Netmiko when you need arbitrary CLI commands, vendor-specific features, or fine control over exactly what is sent. Use NAPALM when you want normalized facts across vendors, or safe config changes with diff and rollback. Many shops use both — NAPALM for getters and config management, Netmiko for the one weird command NAPALM does not expose. They are friends, not rivals.

Exercises

  1. Warm-up. Open a connection with the ios driver, print the hostname and model from get_facts(), and close cleanly with try/finally.
  2. Interface report. Use get_interfaces() to print a table of interface name, up/down, and description.
  3. Safe change. Stage an NTP server line with load_merge_candidate, print the diff, and commit only if the diff is non-empty (otherwise discard).
  4. BGP check. Use get_bgp_neighbors() to print each neighbor IP and whether the session is up.
  5. Challenge. Write enforce_golden(host, driver, golden_file) that loads a replace candidate from a file, prints the diff, and — only if the diff contains no removal lines (no lines starting with -) — commits; otherwise it discards and warns that the change would remove config. This is a guardrail against an incomplete golden file wiping a device.

Answers

Show answers

1. Warm-up

from napalm import get_network_driver
dev = get_network_driver("ios")(
    hostname="192.168.1.1", username="admin", password="cisco123")
try:
    dev.open()
    f = dev.get_facts()
    print(f["hostname"], f["model"])
finally:
    dev.close()

2. Interface report

dev.open()
for name, d in dev.get_interfaces().items():
    print(f"{name:22} {'up' if d['is_up'] else 'down':5} {d['description']}")
dev.close()

3. Safe change

dev.open()
dev.load_merge_candidate(config="ntp server 10.0.0.1")
diff = dev.compare_config()
print(diff)
if diff:
    dev.commit_config()
else:
    dev.discard_config()
dev.close()

4. BGP check

dev.open()
bgp = dev.get_bgp_neighbors()
for vrf, data in bgp.items():
    for peer, info in data["peers"].items():
        print(f"{peer}: {'up' if info['is_up'] else 'down'}")
dev.close()

5. Challenge

from napalm import get_network_driver

def enforce_golden(host, driver, golden_file):
    dev = get_network_driver(driver)(
        hostname=host, username="admin", password="cisco123")
    try:
        dev.open()
        dev.load_replace_candidate(filename=golden_file)
        diff = dev.compare_config()
        removals = [l for l in diff.splitlines() if l.startswith("-")]
        if removals:
            print(f"{host}: REFUSING — change would remove "
                  f"{len(removals)} lines:")
            print("\n".join(removals))
            dev.discard_config()
        else:
            print(f"{host}: safe additive change, committing")
            dev.commit_config()
    finally:
        dev.close()

Inspecting the diff text for lines starting with - turns NAPALM’s preview into an automated guardrail. This is the kind of safety net that lets you sleep during a change window — and a fitting end to a week of talking to real devices.


Previously: Multi-Vendor SSH with Netmiko. Coming next week we move up the stack — YAML and JSON for inventories and configs, then Jinja2 templating, REST APIs, NETCONF, TextFSM, and two end-to-end mini-projects.

Leave a Reply