Add a supervisor-readiness audit to the hourly balena fleet job - #3326
Open
vpetersson-bot wants to merge 1 commit into
Open
vpetersson-bot wants to merge 1 commit into
vpetersson-bot wants to merge 1 commit into
Conversation
The hourly fleet job already unpins, HUPs and bumps supervisors, but it reports only a pinned-release histogram — so there is no way to answer "how much of the fleet is on a supervisor new enough to update itself?" without ad-hoc API queries. That number now matters. balena supervisor v19 (2026-07-20) is the first release whose `core-next` service retrieves a queued host OS update on its own; every older supervisor relies on balenaCloud pushing it over Cloudlink through the device-actions service, which is a balenaCloud-only component. A device below that line therefore cannot be host-updated at all once it is pointed at a self-hosted backend, and the uplift can only be driven while balenaCloud is still in the picture. Add a read-only `--audit` phase that reports, per fleet, the balenaOS and supervisor version spread plus the count and share of devices already at supervisor major >= 19, with a fleet-wide rollup at the end. Wire it into the hourly workflow on both the dry-run and apply branches so the burndown is visible every run. The audit lists every device, not just the online ones: the offline tail is exactly the population the uplift has to wait for, so excluding it would flatter the number. It also never selects `uuid` — there is no per-device output mode to gate, so not fetching identifiers at all keeps them out of the world-readable workflow logs by construction. Behaviour of the existing phases is unchanged; `--audit` is additive, off by default, and issues only GETs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai>
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Why
The hourly fleet job already unpins, HUPs and bumps supervisors, but it reports only a pinned-release histogram. There is currently no way to answer "how much of the fleet is on a supervisor new enough to update itself?" without ad-hoc API queries.
That number has become load-bearing. balena supervisor v19 (2026-07-20) is the first release whose
core-nextservice retrieves a queued host OS update on its own. Every older supervisor depends on balenaCloud pushing the update over Cloudlink via the device-actions service (https://actions.<deviceUrlsBase>/v2/<uuid>/resinhup, which this script already calls) — and device-actions is a balenaCloud-only component with no self-hosted equivalent.The consequence: a device still below supervisor 19 cannot be host-updated at all once it is pointed at a self-hosted backend, and there is no way to recover it remotely afterwards. The uplift can therefore only be driven while balenaCloud is still in the picture, which makes "what fraction of the fleet is already there, and how fast is it moving?" the number that paces any such plan.
This PR adds the measurement. It does not change what the job does to devices.
What
A read-only
--auditphase that reports, per fleet:plus a fleet-wide rollup line at the end. It is wired into the hourly workflow on both the dry-run and apply branches, so the burndown shows up every run.
Sample output (synthetic data, real shape):
Two deliberate choices worth reviewing
The audit lists every device, not just online ones.
list_fleet_devices()filtersis_online eq truebecause a resinhup needs a reachable device. The audit uses a separatelist_fleet_inventory()with no such filter: the offline tail is exactly the population an uplift has to wait for, so excluding it would flatter the burndown and make the plan look shorter than it is.The audit never selects
uuid. Actions logs on this repo are world-readable. The existing phases gate per-device detail behind--verbose; the audit has no per-device output mode at all, so it does not fetch identifiers in the first place — the privacy property holds by construction rather than by discipline. There is a test asserting the$selectstays uuid-free.Risk
Additive and off by default.
--auditissues only GETs and never mutates, regardless of--apply. Existing phase behaviour is untouched. An audit listing failure is counted and logged but does not stop the maintenance phases from running.Extra API load is one paged device listing per fleet per hour (~10-15 requests).
Testing
uv run pytest tests/test_balena_unpin_audit.py— 16 new tests pass (threshold behaviour incl. missing/blank supervisor versions, histogram bucketing and tail collapse, totals accumulation, empty fleet, and the uuid-free$selectassertion)uv run ruff check .— cleanuv run ruff format --check .— clean (250 files)uv run mypy .— clean for the changed files; the only findings project-wide are two pre-existingimport-not-foundforpygit2/python_on_whalesintools/image_builder/__main__.py, which are unrelated and absent from the venv used here--helprenders, missing-token path still exits 2, audit output verified against a stubbed APINot exercised against the live balena API — the audit path has no token here, so the first real numbers will come from the next scheduled run (or a
workflow_dispatchwith dry-run left ticked, which now also prints the burndown).🤖 Generated with Claude Code