network/cnmaestro/docs/plans/2026-05-09-cnmaestro-cleanup-upgrade-design.md
2026-05-09 16:54:28 -05:00

94 lines
3.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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:
1. Removes devices offline for > 24 hours (configurable threshold).
2. 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/token` with `client_credentials` grant →
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
with `product` and `version` fields per image.
- **Submit job:** `POST /api/v2/devices/jobs/firmware` with
`{ "devices": ["MAC", ...], "package": "<image-name-or-version>" }`.
Limit 100 MACs per job.
## Behavior
### `cnmaestro cleanup [--dry-run] [--yes] [--threshold-hours 24] [--force]`
1. Fetch all devices (paginated).
2. Partition: online / offline-recent / offline >24h / never-synced.
3. Removal candidates = offline >24h never-synced.
4. Render `rich` table: name, MAC, product, last_sync, tower.
5. Show counts. If candidates > 50% of fleet, refuse unless `--force`.
6. Prompt; on confirm, DELETE each with progress bar; collect failures.
### `cnmaestro upgrade [--dry-run] [--yes] [--product P1,P2,...]`
1. Fetch all devices, keep `status=online`.
2. Fetch firmware images list.
3. For each product, pick highest semver as target.
4. Build groups `(product, target_version) → [mac, ...]` for devices
where `software_version != target`.
5. Render summary table.
6. 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 `respx` to 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)
1. Log into cloud.cambiumnetworks.com.
2. Manage Services → API Clients → Add Client.
3. Copy client ID + secret into `.env`.
4. `uv sync && uv run cnmaestro cleanup --dry-run` to validate.