A modular, profile-driven Python orchestrator for network device firmware upgrades. Currently supports Cisco IOS-XE.
Project description
simple-upgrade
A modular, profile-driven Python orchestrator for network device firmware upgrades. Built for Cisco IOS-XE with a dynamic JSON device-profile architecture that eliminates hardcoded CLI strings.
Architecture Overview
┌─────────────────────────────────────────────────┐
│ UpgradePackage │
│ (Orchestrator — runs stages sequentially) │
├──────────┬──────────┬───────────┬───────────────┤
│ sync │readiness │ pre_check │ distribute │
│ │ │ │ │
│ activate │post_acti-│post_check │ verification │
│ │vation_wait│ │ diff │
├──────────┴──────────┴───────────┴───────────────┤
│ ExecutionContext (ctx) │
│ Carries: device_info, golden_image, file_server │
│ device_profile, stage_results │
├─────────────────────────────────────────────────┤
│ Device Profiles (JSON) │ TaskRegistry │
│ groups/ → inheritance │ @register_stage │
│ models → regex matching │ manufacturer map │
└─────────────────────────────────────────────────┘
Key Features
- Profile-Driven Execution — All CLI commands (show, copy, verify, install, boot) are defined in JSON device profiles, not hardcoded in Python
- Group Inheritance — Device profiles inherit commands from shared group templates (e.g.
install_mode.json) - Automatic Profile Matching — Hardware model detected via
show versionis regex-matched to the correct JSON profile - Configuration Integrity Validation — JSON profiles are statically linted at startup for schema errors and overlapping model patterns
- Smart Download — Skips re-download if file already exists with matching size and MD5
- Flash Cleanup — Automatically runs
install remove inactivebefore file transfer - TCP Socket Polling — Post-reload wait uses intelligent SSH port probing instead of blind sleep timers
- Dual Logging — Structured JSON execution log + timestamped CLI text log capturing all Unicon/Scrapli output
- Diagnostic Bundling — Pre/post check diffs packaged into a ZIP archive for audit
Pipeline Stages
| # | Stage | Engine | Description |
|---|---|---|---|
| 1 | sync |
Scrapli | Discover device hardware, match to JSON profile |
| 2 | readiness |
Scrapli | Validate flash space, compare running version |
| 3 | pre_check |
Scrapli | Snapshot device state (commands from JSON profile) |
| 4 | distribute |
Unicon | Transfer firmware image with MD5 verification |
| 5 | activate |
Unicon | Execute install command with reload dialog handling |
| 6 | post_activation_wait |
Socket | TCP sweep on SSH port until device returns online |
| 7 | post_check |
Scrapli | Snapshot post-upgrade state |
| 8 | verification |
Scrapli | Confirm running version matches golden image |
| 9 | diff |
— | Generate pre/post diffs and ZIP bundle |
Installation
pip install simple-upgrade
Quick Start
from simple_upgrade import UpgradePackage
pkg = UpgradePackage(
# ── Required Parameters ──
host="172.20.20.11",
username="admin",
password="admin",
platform="cisco_iosxe",
# ── Optional Parameters ──
port=22, # SSH port (default: 22)
manufacturer="cisco", # Manufacturer key (default: "cisco")
connection_mode="normal", # "normal" | "mock" | "dry_run"
enable_password="admin", # Enable/privilege-exec secret
source_interface="GigabitEthernet0/0", # Source interface for file transfers
source_vrf="Mgmt-vrf", # VRF for routing file transfers
# ── Post-Activation Wait Timers ──
post_wait_delay=30, # Seconds before starting SSH probe (default: 600)
post_wait_retries=60, # Max SSH probe attempts at 15s intervals (default: 120)
post_wait_convergence=60, # STP/routing settle time after SSH returns (default: 30)
# ── Golden Image ──
golden_image={
"version": "17.13.1", # Target software version
"image_name": "cat9k_iosxe.17.13.01.SPA.bin", # Filename (no flash: prefix)
"image_size": 1234567890, # Expected file size in bytes
"md5": "abc123def456...", # Expected MD5 checksum
"sha256": "def456ghi789...", # Optional SHA-256 checksum
},
# ── File Server ──
file_server={
"protocol": "http", # http | https | tftp | ftp | scp
"ip": "192.168.29.73", # File server IP
"port": 80, # Server port (optional)
"base_path": "/Cisco/C9XXX/", # URL path on server
"username": "ftpuser", # FTP/SCP username (optional)
"password": "ftppassword", # FTP/SCP password (optional)
}
)
# Run all stages interactively
for stage in pkg.STAGES:
result = pkg.run_stage(stage)
print(f"{stage}: {'✓' if result.success else '✗'} — {result.message}")
# Or run the full pipeline automatically
# results = pkg.execute()
Constructor Parameters
Required
| Parameter | Type | Description |
|---|---|---|
host |
str |
Device IP address or hostname |
username |
str |
SSH username |
password |
str |
SSH password |
platform |
str |
Software platform: cisco_iosxe, cisco_xe, iosxe |
Optional
| Parameter | Type | Default | Description |
|---|---|---|---|
port |
int |
22 |
SSH port |
manufacturer |
str |
"cisco" |
Manufacturer key for profile lookup |
connection_mode |
str |
"normal" |
normal, mock, or dry_run |
enable_password |
str |
None |
Enable / privilege-exec secret |
source_interface |
str |
None |
Source interface for file transfer routing (e.g. GigabitEthernet0/0) |
source_vrf |
str |
None |
VRF name for file transfer routing (e.g. Mgmt-vrf) |
post_wait_delay |
int |
600 |
Seconds to wait before starting SSH sweep after activation |
post_wait_retries |
int |
120 |
Max SSH probe attempts (at 15-second intervals) |
post_wait_convergence |
int |
30 |
Seconds to wait for routing/STP convergence after SSH restores |
golden_image Dictionary
| Key | Required | Type | Description |
|---|---|---|---|
version |
Yes | str |
Target software version (e.g. 17.13.1) |
image_name |
Yes | str |
Filename only — no flash: prefix |
image_size |
Yes | int |
Expected file size in bytes |
md5 |
Yes | str |
Expected MD5 checksum |
sha256 |
No | str |
Optional SHA-256 checksum |
file_server Dictionary
| Key | Required | Type | Description |
|---|---|---|---|
ip |
Yes | str |
File server IP address |
protocol |
No | str |
Transfer protocol: http, https, tftp, ftp, scp (default: http) |
base_path |
Yes | str |
URL path on the server (e.g. /Cisco/C9XXX/) |
port |
No | int |
Server port (omit to use protocol default) |
username |
No | str |
FTP/SCP username |
password |
No | str |
FTP/SCP password |
Note:
source_interfaceandsource_vrfcan be specified either as top-level parameters or insidefile_server. Top-level values take priority.
Connection Modes
| Mode | SSH | Show Commands | Upgrade Commands | Use Case |
|---|---|---|---|---|
normal |
Real | Real | Real | Production upgrades |
mock |
None | Simulated | Simulated | CI/CD testing |
dry_run |
Real | Real | Simulated | Pre-flight validation |
Device Profile Architecture
Device profiles are JSON files in device_profiles/cisco/ that define all commands the orchestrator will execute. No CLI strings are hardcoded in Python.
Structure
device_profiles/
└── cisco/
├── groups/
│ ├── install_mode.json # Shared template for install-mode devices
│ └── lab_vios.json # Lab-specific overrides
├── catalyst_9300x.json # Device profile → inherits from install_mode
└── catalyst_VIOS.json # Device profile → inherits from lab_vios
Profile Fields
| Field | Type | Description |
|---|---|---|
manufacturer |
str |
"Cisco" |
model |
str |
Profile identifier |
models |
list[str] |
Regex patterns matched against show version model |
platform |
list[str] |
Platform identifiers (e.g. ["IOS-XE", "cisco_xe"]) |
group |
str |
Parent group template to inherit from |
commands |
dict |
Show commands for pre/post checks |
upgrade_commands |
dict |
Copy, verify, install templates with {placeholders} |
boot_commands |
list[str] |
Boot config commands applied before activation |
default_image_location |
str |
Target filesystem (e.g. flash:/) |
Template Placeholders
The upgrade_commands block supports dynamic placeholders:
| Placeholder | Resolved From |
|---|---|
{protocol} |
file_server.protocol |
{server} |
file_server.ip:port |
{path} |
file_server.base_path |
{image} |
golden_image.image_name |
{md5} |
golden_image.md5 |
Output Files
After execution, the following files are generated in output/<hostname>/:
| File | Format | Description |
|---|---|---|
execution_log.json |
JSON | Structured pipeline results with all stage data |
execution_cli.log |
Text | Timestamped CLI transcript of all terminal output |
precheck/*.txt |
Text | Individual pre-check command outputs |
postcheck/*.txt |
Text | Individual post-check command outputs |
diff/*.txt |
Text | Per-command pre/post diffs |
<hostname>_upgrade_report.zip |
ZIP | Bundled diagnostic archive |
Supported Devices
| Profile | Models Matched | Group |
|---|---|---|
catalyst_9300 |
C9300.*, C9300X.*, C9300L.*, C9KV-UADP-8P |
install_mode |
catalyst_VIOS |
IOSv |
lab_vios |
Additional profiles can be added by creating a new JSON file in
device_profiles/cisco/with appropriatemodelsregex patterns and an optionalgroupreference.
Requirements
- Python 3.12+
scrapli— SSH connections (readiness, checks, verification)genie/pyats/unicon— File distribution and activation (interactive dialogs)netutils— Version comparison utilitiespydantic— Data model validation
License
Apache License 2.0
Author
Tarani Debnath
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file simple_upgrade-1.0.0.tar.gz.
File metadata
- Download URL: simple_upgrade-1.0.0.tar.gz
- Upload date:
- Size: 187.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5e177ea0da4e53f7fc6e2d9ce2c5a5b572e1bf6dd402bb907a032a093d5ee622
|
|
| MD5 |
d16b786386cba1a8762229104b098344
|
|
| BLAKE2b-256 |
2ff8f5d37aed821b8e880035130ec329b3938903573f7b6c3b654f04810bf5a2
|
Provenance
The following attestation bundles were made for simple_upgrade-1.0.0.tar.gz:
Publisher:
release.yaml on tkdebnath/simple-upgrade
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
simple_upgrade-1.0.0.tar.gz -
Subject digest:
5e177ea0da4e53f7fc6e2d9ce2c5a5b572e1bf6dd402bb907a032a093d5ee622 - Sigstore transparency entry: 1238592522
- Sigstore integration time:
-
Permalink:
tkdebnath/simple-upgrade@5555e58c67d54b833211c43e6103d424f5bbaf76 -
Branch / Tag:
refs/tags/1.0.0 - Owner: https://github.com/tkdebnath
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yaml@5555e58c67d54b833211c43e6103d424f5bbaf76 -
Trigger Event:
push
-
Statement type:
File details
Details for the file simple_upgrade-1.0.0-py3-none-any.whl.
File metadata
- Download URL: simple_upgrade-1.0.0-py3-none-any.whl
- Upload date:
- Size: 45.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d41b69e5a6025fff990020284e976fbcd5e9754cabf39bfb563a9e4b015c3353
|
|
| MD5 |
1e2460574d71f121c49827d07cbb1f40
|
|
| BLAKE2b-256 |
f84d290a2459ecdce01a36cd33cf3723225edfa44047c52da0e261f6971a9ea8
|
Provenance
The following attestation bundles were made for simple_upgrade-1.0.0-py3-none-any.whl:
Publisher:
release.yaml on tkdebnath/simple-upgrade
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
simple_upgrade-1.0.0-py3-none-any.whl -
Subject digest:
d41b69e5a6025fff990020284e976fbcd5e9754cabf39bfb563a9e4b015c3353 - Sigstore transparency entry: 1238592566
- Sigstore integration time:
-
Permalink:
tkdebnath/simple-upgrade@5555e58c67d54b833211c43e6103d424f5bbaf76 -
Branch / Tag:
refs/tags/1.0.0 - Owner: https://github.com/tkdebnath
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yaml@5555e58c67d54b833211c43e6103d424f5bbaf76 -
Trigger Event:
push
-
Statement type: