Multivendor configuration with NAPALM¶
The Python article introduced NAPALM as "one API across vendors" and turning CLI output into structured data used its getters to read facts with a fixed schema. This article picks up the other half of NAPALM's job — changing configuration — and the small, disciplined workflow that makes it safe: stage a candidate, review the diff, then commit or discard, with a rollback if a committed change turns out to be wrong.
One API, many vendors¶
NAPALM (Network Automation and Programmability Abstraction Layer) wraps each platform's native config mechanism behind the same method names, so a script written against the abstraction runs unchanged on IOS XE, Arista EOS, Juniper Junos, Cisco NX-OS, and IOS XR. You pick a driver by name and open a connection:
from napalm import get_network_driver
driver = get_network_driver("ios") # eos, junos, nxos, iosxr, ...
device = driver(hostname="192.0.2.11",
username="automation",
password=PASSWORD) # from the environment, never hard-coded
device.open()
# ... work ...
device.close()
That uniformity is the point: the same commit_config() call drives a Junos commit on one box and a
configure replace on another, and your automation doesn't need to know which. Where NAPALM differs
from pushing CLI lines with Netmiko is that it treats a config
change as a transaction you preview before you apply, not a stream of commands sent blind.
The candidate-config workflow¶
Every NAPALM config change follows the same three steps: load a candidate, compare it against what's running, then commit it or discard it. The candidate is staged — nothing touches the device until you commit.
device.open()
try:
device.load_merge_candidate(filename="vlan10.cfg")
diff = device.compare_config() # preview before anything is applied
if diff:
print(diff)
device.commit_config() # apply the staged change
else:
device.discard_config() # nothing to do; drop the candidate
finally:
device.close()
load_merge_candidate (and its sibling load_replace_candidate) accept either a filename= or an
inline config= string. compare_config returns a unified diff of the candidate versus the running
config as a string — empty when the candidate matches the device, which is your built-in
idempotency check: no diff, nothing to commit. commit_config
applies the candidate; discard_config throws it away without touching the device. Reviewing the diff
before committing is the habit that makes this safe — it is the same stage-and-review gate that
Terraform's plan gives you, applied to a live device.
Merge versus replace¶
The choice of loader decides what "configured" means, and it is the most important decision in the whole workflow.
| Loader | Behaviour | Result |
|---|---|---|
load_merge_candidate |
Additive. Applies the lines in the candidate on top of the running config. | Anything not mentioned is left untouched. |
load_replace_candidate |
Declarative. Makes the running config match the candidate exactly. | Anything in the candidate but not on the device is added; anything on the device but not in the candidate is removed. |
Merge is the right tool when you own one slice of the config — add three VLANs, adjust one interface — and want to leave everything else alone. Replace is what you want when the candidate is the complete intended state for the device, rendered from a source of truth: it converges the box to exactly that and deletes drift, which is far stronger than merge but also far more dangerous if the candidate is incomplete.
One caveat worth knowing before you lean on the diff: for a replace, compare_config produces a
real line-by-line diff of what will change. For a merge, the diff is much simpler — on several
platforms it reflects little more than the candidate lines themselves, because the device computes the
delta only at commit time. Treat a merge diff as "here is what I'm adding," not "here is the net effect
on the device," and check the
per-platform caveats for your driver.
Undoing a committed change¶
Preview catches most mistakes before they land. For the ones that only show up after the change is live — a session that drops, a neighbour that won't come up — NAPALM gives you two safety nets.
rollback() reverts the most recent commit, restoring the configuration that was running before it.
On IOS XE this is backed by the config-archive / configure replace machinery; on Junos and NX-OS it
maps to their native rollback. It is the manual "undo the last change" you reach for when validation
fails after a commit:
device.load_replace_candidate(filename="intended.cfg")
print(device.compare_config())
device.commit_config()
if not change_is_healthy(): # your pyATS / ping / API check
device.rollback() # back to the pre-commit config
Commit-confirm automates that revert so a change that cuts your own management session doesn't
strand the device. commit_config(revert_in=300) applies the change but schedules an automatic
rollback in 300 seconds unless you call confirm_commit() first — so if the box becomes unreachable,
it heals itself without anyone logging in. Native commit-confirm support varies by platform (Junos and
NX-OS have it directly), so confirm it is available for your driver before depending on it. Pair either
mechanism with change validation using pyATS to decide, from data,
whether to confirm or roll back.
Writing it safely¶
NAPALM removes the fragile parts of pushing config, but the secure coding practices from the rest of this site still apply:
- Keep credentials out of the code. Read the username and password from the environment or a
secrets store, exactly as you would for Netmiko or
requests. - Always close the session. Wrap
open()/close()intry/finally(as above) so a failure mid-change doesn't leak a connection or leave a candidate loaded. - Prefer replace from a source of truth. A full declarative push that converges the device is idempotent and self-correcting; a stack of merges drifts over time.
- Canary, then batch. When you fan a change out with concurrency, prove it on one device first — a bad replace committed to the whole fleet at once is exactly the blast radius you don't want.
Key takeaways¶
- NAPALM wraps each vendor's native config mechanism behind one API, so the same stage → diff → commit code runs across IOS XE, EOS, Junos, NX-OS, and IOS XR.
- The workflow is always load a candidate →
compare_config→commit_configordiscard_config. Nothing touches the device until you commit, and an empty diff is your idempotency check. load_merge_candidateis additive;load_replace_candidateis declarative and removes drift — stronger and riskier. The merge diff is simplistic on many platforms; the replace diff is exact.rollback()undoes the last commit, andcommit_config(revert_in=…)+confirm_commit()give you commit-confirm so a change that breaks reachability reverts itself. Commit-confirm support is platform-dependent.- Credentials from the environment,
try/finallyaround the session, replace-from-source-of-truth, and canary-before-batch keep multivendor config changes safe at scale.
Sources: NAPALM documentation, NAPALM — changing the configuration, NAPALM supported devices and caveats, NAPALM on GitHub.