3.4 KiB
3.4 KiB
cnMaestro Cleanup + Bulk Firmware Upgrade Tool
Date: 2026-05-09 Status: Approved, implementation starting
Goal
Build a Python CLI for cnMaestro Cloud (cloud.cambiumnetworks.com) that:
- Removes devices offline for > 24 hours (configurable threshold).
- Submits bulk firmware upgrade jobs to bring online devices to the latest firmware available for their model.
Non-Goals
- NetBox sync (separate concern).
- Daemon/scheduling (use cron +
--yes). - Firmware rollback (cnMaestro handles).
- Custom firmware uploads (use cnMaestro UI).
Architecture
cnmaestro/
├── README.md # Setup, API client creation, usage
├── .env.example # CNMAESTRO_CLIENT_ID / _SECRET / _BASE_URL
├── pyproject.toml # uv project
├── cnmaestro/
│ ├── __init__.py
│ ├── client.py # OAuth2 + paginated httpx wrapper
│ ├── devices.py # list, partition, delete
│ ├── firmware.py # latest-per-model, job submission
│ └── cli.py # click CLI
└── tests/
├── test_client.py
├── test_devices.py
└── test_firmware.py
API Reference (cnMaestro Cloud v2)
- Auth:
POST /api/v2/access/tokenwithclient_credentialsgrant → bearer token. Cache in-memory, re-fetch on 401. - List devices:
GET /api/v2/devices?limit=100&offset=N. Key fields:mac,name,network,tower,status("online"/"offline"),last_sync(epoch ms or ISO 8601),product,software_version. - Delete device:
DELETE /api/v2/devices/{mac}. - Firmware images:
GET /api/v2/firmware. Returns image metadata withproductandversionfields per image. - Submit job:
POST /api/v2/devices/jobs/firmwarewith{ "devices": ["MAC", ...], "package": "<image-name-or-version>" }. Limit 100 MACs per job.
Behavior
cnmaestro cleanup [--dry-run] [--yes] [--threshold-hours 24] [--force]
- Fetch all devices (paginated).
- Partition: online / offline-recent / offline >24h / never-synced.
- Removal candidates = offline >24h ∪ never-synced.
- Render
richtable: name, MAC, product, last_sync, tower. - Show counts. If candidates > 50% of fleet, refuse unless
--force. - Prompt; on confirm, DELETE each with progress bar; collect failures.
cnmaestro upgrade [--dry-run] [--yes] [--product P1,P2,...]
- Fetch all devices, keep
status=online. - Fetch firmware images list.
- For each product, pick highest semver as target.
- Build groups
(product, target_version) → [mac, ...]for devices wheresoftware_version != target. - Render summary table.
- Prompt; submit one POST per group, chunked to 100 MACs. Print job IDs.
Safety Rails
- Default to dry-run summary + interactive confirm.
- 50%-of-fleet sanity check on cleanup.
- Token never persisted.
- All destructive ops appended to
./cnmaestro.log(timestamped). - Tests use
respxto mock httpx; CI never hits live API.
Testing Strategy
- Unit tests for partition logic (no HTTP).
- Unit tests for "latest version per model" with fixture firmware list.
- HTTP-mocked tests for client pagination, 401 retry, delete.
- No live API tests in CI.
Setup (User)
- Log into cloud.cambiumnetworks.com.
- Manage Services → API Clients → Add Client.
- Copy client ID + secret into
.env. uv sync && uv run cnmaestro cleanup --dry-runto validate.