Expose fleet-wide agent updates with host subset selection #43
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Admins can trigger an agent update from an individual host page, but there is no discoverable UI workflow for updating agents across the fleet or selecting a controlled subset. This makes routine releases unnecessarily repetitive and makes staged rollouts difficult.
There is already substantial backend support that should be reused:
POST /api/fleet/updatestarts the rolling fleet-update worker.host_idslist derives all online, out-of-date hosts.host_idslist already represents a subset./settings/fleet-updatealready shows rollout progress, cancellation, and the previous result.The primary gaps are discoverability and host selection: the fleet-update page is not linked from the Settings navigation, and its start form always submits the complete eligible set.
Proposed solution
Turn the existing page into a first-class admin Agent updates workflow under Settings.
Entry points
Agent updatestab or clearly labelled card in Settings linking to/settings/fleet-update.Host selection
Present all update candidates in a selectable table with:
Select all eligibleandClear selection;Default to all eligible hosts so the common fleet-wide action remains quick. Filtering followed by “select all visible” provides the subset workflow for tags or staged cohorts without inventing a second rollout mechanism.
Review and start
Before dispatch, show a compact rollout summary:
Submit the selected IDs through the existing
host_idsAPI field. Server-side validation must deduplicate IDs, reject unknown or ineligible hosts with structured reasons, and ensure the recorded rollout membership exactly matches the reviewed selection.Progress and recovery
Reuse the current progress panel, per-host states, cancellation, audits, and alerts. Add actions after a halted rollout to:
Do not automatically queue offline hosts in the first iteration; display them as excluded with a clear reason. Deferred/offline rollout scheduling can be considered separately.
Acceptance criteria
Non-goals
Agree with this as specified — reusing
POST /api/fleet/updateand the existing progress page rather than adding a second rollout mechanism is the right call, and showing ineligible hosts disabled-with-reason instead of omitting them avoids the usual "why isn't my host listed" confusion.The discoverability premise matches experience: the endpoint was only findable by reading the route table in
server.go, and the operator role then gets a403 insufficient_rolewith no indication that an admin UI exists for it.Three additions from a v1.1.0 → v1.1.1 rollout, all observed rather than hypothetical.
1. Verification by
agent_versionis unsound while report delivery can failThis is the one worth acting on before building the UI.
internal/server/fleetupdate/worker.goconfirms a host succeeded by polling the host row's recordedagent_versionuntil it matches the target:That row is refreshed over the agent→server reporting channel — which is exactly the channel that can be severed by the WebSocket read limit in #44. The failure mode is:
halt.Because the worker halts the entire rollout on first failure, one host that updated fine but failed to report blocks every host queued behind it. The operator sees a halted rollout and a host that is, in reality, already on the target version.
This gets more likely as #43 makes fleet updates routine and larger batches run at once.
Two options that avoid depending on projection/report delivery:
versioninagent starting/ hello, and a self-updating agent must reconnect to be considered healthy — so the reconnect itself carries a trustworthy version without a separate report.failed_reason. The current message (timeout waiting for <host> to reach <version>) reads as the former when it may be the latter.2. Real self-update timing, for the worst-case timeout estimate
The issue proposes showing an estimated worst-case timeout based on host count. A measured data point for that:
~6 seconds end-to-end, including the 5s systemd restart delay, on a Linux amd64 host. Against
hostTimeout = 95 * time.Secondthat is roughly 15x headroom, so the default looks well chosen and the estimate shown to the operator can be honest about it being dominated by the timeout ceiling, not by expected duration.Worth noting the restart delay is a systemd unit property, so the floor is environment-specific — a host with a longer
RestartSecwill be slower without anything being wrong.3. Make canary-first a named workflow, not just an achievable one
Subset selection technically permits "select one host, then select the rest", but the pattern worth designing for is update one → verify → continue. In this rollout that sequence caught a real bug (#44) on the first host before it reached the remaining four.
The machinery already exists — the worker is sequential, halts, and the issue proposes resume actions. What is missing is making the intent expressible in one action rather than two rollouts, e.g. a "pause after first host for confirmation" checkbox on the start form, reusing the existing halted-state resume path.
This fits the stated non-goals: it is not parallel waves or percentage-based deployment, just an explicit stop after host 1. It is also the safest default for an operator updating a fleet whose agents all run the same backup-critical code path.
Related: #44 (read limit — the concrete delivery failure behind point 1), #41 (stuck
runningjobs — why a failed rollout step can be silent).Implementation is in #48. Bobby’s rollout feedback is addressed without changing this issue:
hellopersisted byMarkHostHello; the worker now performs one final authoritative read at the deadline and reports the last observed versionThe PR also adds discoverability, filtered subset selection, disabled exclusion reasons, and server-side membership validation/deduplication.