NetAuto Academy

Real-world Projects

Project 1: Configuration Backup & Drift History

Advanced14 min read

What you'll learn

  • The business problem: undocumented config drift, with no historical record
  • Composing NAPALM's get_config() with GitPython's commit/diff -- for the first time in this curriculum
  • Real, verified output from two backup runs, one with drift and one without
  • How to present this project in a portfolio
🎓 Teach This Lesson

Listen to a guided explanation of this lesson.

Spoken narration isn't supported in this browser.

The business problem

Scenario

Nobody on the team can say, with confidence, what changed on SW1 last week -- or when. Configs get backed up somewhere, sometimes, by whoever remembers. There's no reliable way to answer "what's different between this backup and the last one" without opening two files side by side and reading carefully. This is PRODUCT_SPEC's own "Configuration Backup" project idea, finally buildable now that both halves of the answer -- pulling config, and keeping real history -- are already in your toolkit.

Requirements: every backup run should record each device's current running configuration; a backup should only produce a new, dated history entry when something actually changed (a no-op run shouldn't clutter history); and there must be a way to see exactly what changed between the two most recent backups, not just that "something" did.

Quick checkConcept check
Why should a backup run that finds zero configuration changes still avoid creating a new Git commit?

Skills reused

  1. Nornir orchestrationInitNornir(), nr.run() -- Nornir for Network Engineers
  2. NAPALM get_config()the running-config getter -- NAPALM for Network Engineers
  3. GitPython Repo.init() / index.add() / index.commit()Git / NetDevOps
  4. New here: reading iter_commits() diffs to build a drift report

Not re-taught here

How get_config() works, what NAPALM's mock driver is, and how Repo.init()/index.commit() work are all assumed knowledge from NAPALM for Network Engineers and Git / NetDevOps. Nothing below re-explains them.

The workflow

  1. Nornir pulls get_config() from every host
  2. Each host's running config is compared to its last saved file
  3. Changed files are committed; unchanged files are left alone
  4. The two most recent commits are diffed for a real drift report
Quick checkScenario
Why does this project use Nornir to orchestrate the config pull, rather than just looping over a Python list of hostnames and calling NAPALM directly on each one?

The real script

backup_and_drift.py
import os
import git
from nornir import InitNornir
from nornir_napalm.plugins.tasks import napalm_get

REPO_PATH = "backup_repo"


def load_inventory():
  return InitNornir(
      inventory={
          "plugin": "SimpleInventory",
          "options": {
              "host_file": "inventory/hosts.yaml",
              "group_file": "inventory/groups.yaml",
              "defaults_file": "inventory/defaults.yaml",
          },
      },
  )


def init_backup_repo():
  repo = git.Repo.init(REPO_PATH)
  with repo.config_writer() as cw:
      cw.set_value("user", "name", os.environ.get("GIT_AUTHOR_NAME", "Network Automation Curriculum"))
      cw.set_value("user", "email", os.environ.get("GIT_AUTHOR_EMAIL", "automation@example.local"))
  return repo


def _pull_config(task):
  task.run(task=napalm_get, getters=["get_config"])


def backup_fleet(nr, repo):
  agg = nr.run(task=_pull_config)
  changed_files = []
  for hostname, multi_result in agg.items():
      running_config = multi_result[1].result["get_config"]["running"]
      filename = f"{hostname}.cfg"
      filepath = os.path.join(repo.working_tree_dir, filename)
      before = None
      if os.path.exists(filepath):
          with open(filepath) as f:
              before = f.read()
      with open(filepath, "w") as f:
          f.write(running_config)
      if before != running_config:
          changed_files.append(filename)
  if changed_files:
      repo.index.add(changed_files)
      repo.index.commit(f"Backup: {', '.join(changed_files)} changed")
  return changed_files


def drift_report(repo):
  commits = list(repo.iter_commits())
  if len(commits) < 2:
      return "Only one backup on record -- nothing to compare yet."
  newest, previous = commits[0], commits[1]
  diff = repo.git.diff(f"{previous.hexsha}..{newest.hexsha}")
  return diff if diff else "No drift between the last two backups."

The complete, real, verified library. No credential is hardcoded -- author identity comes from environment variables with obviously-placeholder fallbacks, and device credentials come from inventory/defaults.yaml, exactly as in every prior module.

Quick checkIdentify the correct code
In backup_fleet(), why does the function read the existing file's content into `before` before overwriting it, instead of just comparing against what NAPALM returned last time?

Real, verified output

The script was run twice against the same live mock connections -- SW1's config was changed between the two pulls (an unauthorized permit tcp any any eq 23 line added to its management ACL); SW2's was left untouched.

terminal output — real, verified run
--- Backup run 1 (baseline) ---
Files committed: ['SW1.cfg', 'SW2.cfg']

--- Backup run 2 (drift check) ---
Files committed: ['SW1.cfg']

--- Git history ---
65fb731b  Backup: SW1.cfg changed
b137be86  Backup: SW1.cfg, SW2.cfg changed

--- Drift report (latest vs previous backup) ---
diff --git a/SW1.cfg b/SW1.cfg
index 6bfd694..5e54e85 100644
--- a/SW1.cfg
+++ b/SW1.cfg
@@ -3,3 +3,4 @@ interface GigabitEthernet0/1
description mgmt-uplink
ip access-list extended MGMT-ACL
permit tcp any any eq 22
+ permit tcp any any eq 23

Real, verified output. Run 1 creates the baseline (both hosts are "new," so both commit). Run 2 only commits SW1 -- SW2's config was identical, so backup_fleet() correctly left it alone. The drift report isolates exactly the one line that changed.

Quick checkPredict output
Why does the second backup run's commit only mention SW1.cfg, when the script ran against both SW1 and SW2 again?

What this does and doesn't prove

Never claimed here

This ran against NAPALM's real mock driver, not a real device or production fleet -- consistent with every mock-execution disclosure since NAPALM for Network Engineers. The drift detected here is real (a genuine content comparison and a genuine Git diff), but it's drift between two mock-fixture pulls, not evidence about any real network's actual configuration history.

Quick checkScenario
How should a learner honestly describe what this project's real, verified run proves?

How to present this in your portfolio

Portfolio guidance

Problem statement: "Config changes on our fleet were untracked -- no reliable way to see what changed or when." Architecture: Nornir for orchestration, NAPALM for config retrieval, Git for versioned history -- three tools, each doing the one job it's already good at. Evidence: the real terminal output above, plus your own repo's commit log, is your proof of correctness -- screenshot or paste it directly. Limitations to state honestly: runs against a mock fixture set here, not a live fleet; a real deployment would run this on a schedule (cron, a CI job) rather than manually. Extension ideas: add a per-device threshold ("alert if more than N lines changed"), or diff against a known-good baseline instead of just the previous backup.

Key takeaways

  • The business problem: undocumented config drift, with no reliable way to see what changed between backups.
  • The new integration: NAPALM's get_config() and GitPython's commit/diff, run together in one script for the first time in this curriculum.
  • Real, verified run: SW2's unchanged config correctly produced no new commit; SW1's genuinely changed config was committed, and the drift report isolated exactly the added line.
  • Every mechanic here -- get_config(), Repo.init(), index.commit() -- was already taught; nothing about NAPALM or Git fundamentals was re-explained.
  • Mock driver only, never claimed as real-device or production-fleet evidence.