Intermediate Project
Network Configuration Backup Analyzer
Objective
Combine multi-device text-config validation, structured fact extraction, and line-by-line comparison into one backup analysis pipeline that safely handles a missing or corrupted backup instead of crashing on it.
Builds on Netmiko Fundamentals and Reliable Automation and Real-world Projects.
Prerequisites
- Comfortable with basic string/line processing in Python
- Familiar with what a real Netmiko connection returns as config text (Running Show Commands with Netmiko)
- Familiar with structured, per-device results that isolate one device's problem from the rest (Multi-Device Partial Failure and Structured Results)
Deliverables
- A per-device backup validity check that safely flags an empty or malformed backup instead of crashing
- Structured facts extracted from every valid backup: hostname, interface count, and NTP presence
- A line-by-line comparison between each device's two most recent backups, naming exactly what was added and removed
- One fleet-wide backup analysis report classifying every device as unchanged, changed, or backup failed
The problem
Not the same task as 'Safe Configuration Backup Workflow'
That lab writes one device's config to a timestamped file and verifies the write succeeded -- a single device, a single snapshot, no comparison. This project starts from a fleet that already has backups on disk from two different days and asks the harder question: what actually changed, what backup silently failed, and what does a stakeholder need to know about all of it at once?
A small fleet's configs are backed up daily. Nobody reads every backup by hand -- the useful question isn't "did the backup job run," it's "which devices changed since yesterday, and did every backup even succeed." One device's backup came back empty this run (a real, common failure mode -- a timeout, a truncated write). Your job: validate every device's latest backup, extract useful facts from the ones that are valid, compare each valid device against its own backup from the day before, and produce one report that separates "nothing to see here" from "this needs a look" from "this backup itself is broken."
Starter data
BACKUPS = {
"RTR1": {
"yesterday": "hostname RTR1\ninterface Gi0/1\n ip address 10.40.10.1 255.255.255.0\nntp server 10.40.10.2\n",
"today": "hostname RTR1\ninterface Gi0/1\n ip address 10.40.10.1 255.255.255.0\nntp server 10.40.10.2\n",
},
"RTR2": {
"yesterday": "hostname RTR2\ninterface Gi0/1\n description wan-uplink\nntp server 10.40.10.2\n",
"today": "hostname RTR2\ninterface Gi0/1\n description wan-uplink\nip access-list extended GUEST-ACL\n permit tcp any any eq 443\n",
},
"SW1": {
"yesterday": "hostname SW1\ninterface Gi0/1\n description access-port\n",
"today": "",
},
"SW2": {
"yesterday": "hostname SW2\ninterface Gi0/1\n description old-label\n",
"today": "hostname SW2\ninterface Gi0/1\n description new-label\n",
},
}Fixed sample data written for this project. RTR1 is identical across both days; RTR2 gained an ACL and lost its NTP line; SW1's today snapshot is empty (a failed backup); SW2's interface description changed.
Milestones
- Load the fleet's paired backup snapshotsyesterday and today, per device
- Validate each device's today snapshotdetect an empty or malformed backup safely
- Extract structured factshostname, interface count, NTP presence
- Compare yesterday vs. today, line by linefor every device with a valid backup
- Classify each deviceunchanged, changed (with specifics), or backup failed
- Produce the final backup analysis reportone fleet-wide summary plus per-device detail
Task
Write five functions:
is_valid_backup(config_text)-- returnTrueonly ifconfig_textis non-empty and its first non-blank line starts with"hostname ".extract_facts(config_text)-- return{"hostname": ..., "interface_count": ..., "ntp_configured": ...}: the hostname from the first line, how many lines start with"interface ", and whether any line (after stripping leading whitespace) starts with"ntp server".compare_configs(yesterday_text, today_text)-- return(removed, added): the non-blank lines present in yesterday's text but not today's, and vice versa.analyze_device(hostname, snapshots)-- combine the three functions above into one per-device result. If today's snapshot failsis_valid_backup, return{"hostname": ..., "status": "backup failed", "reason": ...}immediately, without calling the other functions on it. Otherwise return{"hostname": ..., "status": "unchanged" or "changed", "facts": ..., "removed": ..., "added": ...}(only includeremoved/addedwhenstatusis"changed").build_backup_analysis(backups)-- runanalyze_devicefor every device inbackups, and combine the results into{"summary": {...}, "devices": [...]}.
BACKUPS = {
"RTR1": {
"yesterday": "hostname RTR1\ninterface Gi0/1\n ip address 10.40.10.1 255.255.255.0\nntp server 10.40.10.2\n",
"today": "hostname RTR1\ninterface Gi0/1\n ip address 10.40.10.1 255.255.255.0\nntp server 10.40.10.2\n",
},
"RTR2": {
"yesterday": "hostname RTR2\ninterface Gi0/1\n description wan-uplink\nntp server 10.40.10.2\n",
"today": "hostname RTR2\ninterface Gi0/1\n description wan-uplink\nip access-list extended GUEST-ACL\n permit tcp any any eq 443\n",
},
"SW1": {
"yesterday": "hostname SW1\ninterface Gi0/1\n description access-port\n",
"today": "",
},
"SW2": {
"yesterday": "hostname SW2\ninterface Gi0/1\n description old-label\n",
"today": "hostname SW2\ninterface Gi0/1\n description new-label\n",
},
}
def is_valid_backup(config_text):
# Your code here.
...
def extract_facts(config_text):
# Your code here.
...
def compare_configs(yesterday_text, today_text):
# Your code here.
...
def analyze_device(hostname, snapshots):
# Your code here.
...
def build_backup_analysis(backups):
# Your code here.
...
if __name__ == "__main__":
report = build_backup_analysis(BACKUPS)
print(report["summary"])Copy into a local .py file and run with plain python3 -- no install needed. The five functions are intentionally incomplete.
Expected output
--- Backup Analysis Summary ---
Total devices: 4
Unchanged: 1
Changed: 2
Backup failed: 1
RTR1: unchanged
RTR2: changed -- removed=['ntp server 10.40.10.2'] added=['ip access-list extended GUEST-ACL', ' permit tcp any any eq 443']
SW1: backup failed -- today's backup is empty or malformed
SW2: changed -- removed=[' description old-label'] added=[' description new-label']Real, verified output from a working solution against the exact starter data above. Your own report's formatting can differ, but the counts and per-device classifications must match.
Self-check only -- nothing here is submitted or graded. Compare against your own output manually.
Solution
Try the task yourself first -- the full solution and explanation stay hidden until you click above.
Key takeaways
- A backup pipeline needs a validity check before anything else touches the data -- a function that assumes well-formed input will crash on the one backup that actually failed.
- Comparing two snapshots doesn't require a real diff algorithm when neither side repeats a line -- simple list membership checks are enough, and are easier to read than a general-purpose diff.
- Separating 'the backup itself failed' from 'the backup succeeded but nothing changed' from 'the backup succeeded and something changed' are three genuinely different findings, not shades of the same one.
- This is fixed, offline sample data -- not a real fleet's backup history.
Related lessons
Running Show Commands with Netmiko
send_command() is exactly what would produce the raw text this project treats as a backup snapshot.
Read lessonPre-Check, Change, Post-Check
The before/after comparison mindset this project's yesterday-vs-today analysis extends to a full backup history.
Read lessonMulti-Device Partial Failure and Structured Results
The exact per-device isolation this project's 'one bad backup doesn't stop the others' handling reuses.
Read lessonProject 1: Configuration Backup & Drift History
A related but different backup project -- that one pulls live config with NAPALM and tracks drift in Git; this one validates and compares backup text you already have on hand.
Read lessonDone with this project?