Skip to content
Log in Sign up My account

NAPALM: Getters, Config Replace and Rollback

NAPALM (Network Automation and Programmability Abstraction Layer with Multivendor support) gives you one Python API across several network operating systems. You get normalised getters that return the same data structure whatever the vendor, and a configuration workflow with candidate configs, diffs, commit and rollback. This article covers the getters, the difference between merge and replace, how compare_config and rollback behave, and when to pick NAPALM over Netmiko.

What NAPALM is

NAPALM is a library of drivers. Each driver speaks to its platform in whatever way works best: the IOS driver uses SSH through Netmiko, the Junos driver uses NETCONF, the EOS driver uses eAPI, and the NX-OS driver can use NX-API. You write against one interface and the driver handles the transport.

graph LR
    S["Your script"] --> N["NAPALM API"]
    N --> I["ios driver<br/>SSH via Netmiko"]
    N --> J["junos driver<br/>NETCONF"]
    N --> E["eos driver<br/>eAPI"]
    N --> X["nxos driver<br/>NX-API or SSH"]

Install it into a virtual environment:

pip install napalm

Which drivers ship in the core package and which are community packages has changed between releases, so check the project documentation for your version. For Cisco IOS and IOS XE the driver name is ios.

Connecting

import os
from napalm import get_network_driver

driver = get_network_driver("ios")
device = driver(
    hostname="192.0.2.10",
    username=os.environ["NET_USER"],
    password=os.environ["NET_PASS"],
    optional_args={"secret": os.environ.get("NET_ENABLE", "")},
)

device.open()
try:
    facts = device.get_facts()
    print(facts["hostname"], facts["os_version"], facts["uptime"])
finally:
    device.close()

optional_args carries driver-specific settings such as the enable secret, a non-default port, or dest_file_system for config replace on IOS. The driver also works as a context manager: with driver(...) as device:.

Getters

Getters return plain dictionaries and lists with a documented structure. The same getter gives the same keys on every supported platform, which is the whole point.

interfaces = device.get_interfaces()
for name, data in interfaces.items():
    print(name, data["is_up"], data["is_enabled"], data["speed"])

arp = device.get_arp_table()
bgp = device.get_bgp_neighbors()
lldp = device.get_lldp_neighbors()

Commonly used getters include:

Getter Returns
get_facts Hostname, vendor, model, OS version, serial, uptime, interface list
get_interfaces Per-interface state, speed, MTU, MAC, description
get_interfaces_ip IPv4 and IPv6 addresses per interface
get_arp_table ARP entries
get_mac_address_table Learned MAC addresses
get_lldp_neighbors Neighbours by local port
get_bgp_neighbors Peers per VRF with state and prefix counts
get_environment Fans, power, temperature, CPU, memory
get_config Running, startup or candidate config

Not every getter is implemented on every driver. Calling an unimplemented getter raises NotImplementedError, so wrap calls when you work across platforms. The support matrix in the NAPALM documentation lists what each driver covers.

For anything without a getter, device.cli(["show ip route summary"]) returns raw text for each command. That is Netmiko-level work, so you lose the portability.

Merge versus replace

NAPALM loads configuration into a candidate and only applies it when you call commit_config(). There are two ways to load:

  • Merge (load_merge_candidate): the candidate is a snippet. It is applied on top of the running config, like typing the lines in config mode. Lines you do not mention stay as they are. Removal needs explicit no commands.
  • Replace (load_replace_candidate): the candidate is the complete intended config. The device ends up matching the file exactly. Anything on the device that is not in the file is removed.
# Merge: add a snippet
device.load_merge_candidate(config="""
interface Loopback100
 description NAPALM test
 ip address 10.255.100.1 255.255.255.255
""")

# Replace: converge the whole device to a file
device.load_replace_candidate(filename="golden/sw1.cfg")

Replace is the stronger model because the file in Git becomes the truth and drift is removed on every run. It is also the riskier one. A replace file that is missing the management interface, the AAA lines or the SSH settings can cut you off. Always generate replace files from a complete, reviewed source, and test them on a lab device first.

Warning

On IOS and IOS XE the replace workflow relies on configure replace. The driver copies the candidate file to the device (it uses SCP, so the device needs ip scp server enable and an account allowed to use it) and runs configure replace against it. Check the driver documentation for the exact requirements and the dest_file_system option. Whether your platform supports configure replace cleanly depends on the release, so lab it before depending on it.

compare_config

After loading a candidate, ask for the diff before you commit anything.

device.load_merge_candidate(filename="changes/vlan-update.cfg")
diff = device.compare_config()

if diff:
    print(diff)
else:
    print("No changes needed")
    device.discard_config()

An empty string means the candidate would change nothing. That is how you get idempotent behaviour: run the script every hour and it only acts when the device has drifted.

The diff format depends on the driver. Junos returns its own diff format, and IOS shows lines to add and remove. Treat the diff as something to display and log, not as data to parse across vendors.

Commit and rollback

device.load_replace_candidate(filename="golden/sw1.cfg")
diff = device.compare_config()
print(diff)

answer = input("Commit? [y/N] ")
if answer.lower() == "y":
    device.commit_config()
else:
    device.discard_config()
  • commit_config() applies the candidate.
  • discard_config() throws the candidate away without touching the device.
  • rollback() returns the device to the configuration it had before the last commit.
device.commit_config()
# post-change validation
if not device.get_bgp_neighbors()["global"]["peers"]:
    device.rollback()

The mechanism differs per platform. On IOS the driver keeps a rollback copy of the config on the device's file system before it commits, and rollback() replaces the running config with that copy. On Junos the platform's own commit history does the work. Do not assume a rollback undoes anything other than configuration: it does not reset BGP sessions that flapped or bring back traffic that was dropped in between.

Some drivers support commit_config(revert_in=<seconds>), a commit-confirmed style safety net where the device reverts by itself unless you confirm. Check the driver support table before relying on it, and note that it is not implemented everywhere.

Tip

Run validation getters after every commit. A change is only done when get_interfaces and get_bgp_neighbors look the way you expect, not when commit_config() returned.

Putting it together

A small drift-correction script for a set of switches, using replace and showing the diff:

import os, sys
from napalm import get_network_driver

def converge(host, golden_path, apply=False):
    driver = get_network_driver("ios")
    with driver(hostname=host,
                username=os.environ["NET_USER"],
                password=os.environ["NET_PASS"]) as dev:
        dev.load_replace_candidate(filename=golden_path)
        diff = dev.compare_config()
        if not diff:
            print(f"{host}: in sync")
            dev.discard_config()
            return
        print(f"{host}: drift detected\n{diff}")
        if apply:
            dev.commit_config()
        else:
            dev.discard_config()

converge(sys.argv[1], f"golden/{sys.argv[1]}.cfg", apply="--apply" in sys.argv)

Without --apply it is a read-only drift report, which is a safe first step to put in front of a team.

NAPALM or Netmiko

Question Netmiko NAPALM
Works on anything with an SSH CLI Yes Only platforms with a driver
Normalised, vendor-neutral data No (parse it yourself) Yes, through getters
Diff before applying You build it Built in (compare_config)
Rollback You build it Built in, platform dependent
Run arbitrary commands Yes, easily Possible with cli(), but not the focus
Learning curve Small Small to moderate
Config style Line-by-line pushes Merge or full replace

Use Netmiko when you need to reach odd platforms, run one-off commands, or do interactive things such as upgrades with confirmation prompts. Use NAPALM when you manage a mixed-vendor estate, want a consistent data model for audits, or want a diff-and-commit workflow without writing it yourself. Many teams use both: NAPALM for the routine config lifecycle, Netmiko for the odd commands NAPALM does not model.

Neither tool decides what the right configuration is. Pair them with templates and a source of truth, and put the output in Git as described in Git and CI/CD for network configs.

References