mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-10 18:56:53 +00:00
fix(fleet): resolve Stop-by-label stack labels across all nodes (#1382)
* fix(fleet): resolve Stop-by-label stack labels across all nodes The Fleet Actions "Stop by label" card only ever saw the control node's own stack labels. The stack-label routes are proxied, so each node stores its labels in its own database and the control holds no mirror for remote nodes. The suggestions, match-preview, and fleet-stop endpoints all read that nonexistent mirror, so remote-only labels were invisible and remote stacks were skipped before the remote was ever asked. Make all three authoritative across the fleet with a new collectFleetLabelSummaries helper: the local node reads its own database, and each remote is queried live through its labels and label-assignments endpoints over the proxy, with fail-closed parsing and per-node reachability. fleet-stop drops the mirror pre-check and always calls each reachable remote's local-stop receiver, reporting unreachable nodes at the node level so they never block the reachable ones. The picker now surfaces remote-only labels, aggregates shared names once with combined counts and the carrying node names, and flags incomplete coverage when a node is unreachable. The preview groups matches per node, lists unreachable nodes separately, and distinguishes no matching stacks from "label exists but no stacks" from "remote unavailable". * fix(fleet): harden Stop-by-label card against malformed responses Guard the match-preview and fleet-stop response bodies so a malformed but 200 reply degrades instead of crashing or misreporting. match-preview now validates the per-node shape before rendering (the preview reads it outside any try/catch) and logs a malformed body; fleet-stop distinguishes a non-array results body (a server bug, now logged and surfaced as an unexpected-response error) from a genuine empty fleet, and guards per-node stackResults. Docs: an unreachable or errored remote is reported once per node, not as a per-stack error row.
This commit is contained in:
@@ -46,7 +46,7 @@ Stop every stack assigned a given **stack label** on every node where that stack
|
||||
### Step by step
|
||||
|
||||
1. Open **Fleet → Actions**.
|
||||
2. Type a name in the **Stack label** field. The picker suggests stack labels from across the fleet, each with its stack and node counts, so the scope is unmistakable. Suggestions are read from the control instance, so stack labels on offline nodes appear too. You can also type a name by hand.
|
||||
2. Type a name in the **Stack label** field. The picker queries each reachable node for its own stack labels and lists each name once with its combined stack and node counts (and the carrying node names), so the scope is unmistakable. A stack label that exists only on a remote node still appears. When a node cannot be reached, the picker notes that the suggestions may be incomplete. You can also type a name by hand.
|
||||
3. Click **Stop fleet**.
|
||||
4. A confirmation appears with the kicker **Fleet stop** and the title `Stop all stacks with the stack label "<name>"?`. Click **Stop fleet** to commit.
|
||||
|
||||
@@ -66,7 +66,7 @@ A few quirks worth knowing:
|
||||
|
||||
- A node that has no stack label by that name appears as `<node> (no matching stack label)` and is counted in the **failed** badge. This is not a transport failure, it just means the stack label was not present on that node.
|
||||
- A node where the label exists but no stacks are assigned to it appears with a matched count of zero stacks. No per-stack rows render.
|
||||
- When the control instance reaches a remote node, the per-stack result you see comes from the remote node's own response. If the remote returns a non-2xx for the whole label, every stack on that node renders with the same error message.
|
||||
- When the control instance reaches a remote node, the per-stack results you see come from the remote node's own response. A remote the control cannot reach, or that returns a non-2xx, is reported once as a single `<node> (unreachable)` row carrying the reason, not as a per-stack error, and it never blocks the stops on the reachable nodes.
|
||||
|
||||
### Behaviour and partial-failure semantics
|
||||
|
||||
@@ -130,7 +130,7 @@ Scope is a segmented control with two options:
|
||||
|
||||
| Requirement | Why it matters |
|
||||
|---|---|
|
||||
| **Configured remote nodes in Settings → Nodes** | The two fan-out cards iterate the configured node list. A node missing its `api_url` or `api_token` shows up in the results with *Remote node not configured* per stack or per target. |
|
||||
| **Configured remote nodes in Settings → Nodes** | The two fan-out cards iterate the configured node list. A node missing its `api_url` or `api_token`, or one that cannot be reached, is reported once per node as unreachable (Stop fleet by label) or per target (Prune), and never blocks the reachable nodes. |
|
||||
| **Labels you intend to target** | Stop fleet by label and the autocomplete depend on labels existing on at least one node. See [Stack Labels](/features/stack-labels) for the authoring flow. |
|
||||
|
||||
## Behaviour and lifecycle
|
||||
@@ -173,7 +173,7 @@ Run **Prune Docker resources fleet-wide** with **Images** selected and **Managed
|
||||
The fleet-stop match is by **stack label name**, not label ID. Confirm the stack label name on the affected node under **Settings → Labels**; a typo, a case mismatch, or a trailing space will leave the node out. Stack labels are scoped per node, so renaming the label on one node does not propagate to the others.
|
||||
</Accordion>
|
||||
<Accordion title="The autocomplete didn't suggest a stack label I know exists">
|
||||
The picker reads stack labels from the control instance across every configured node, including offline ones, so a name is missing only when no node has a **stack label** by that name. Node labels never appear here, because this action targets stack labels only. You can always type a name by hand; the fleet-stop request still iterates every configured node.
|
||||
The picker queries each reachable node for its own stack labels, so a name is missing when no reachable node has a **stack label** by that name, or when the node that owns it could not be reached when the picker loaded (the picker flags this case). Node labels never appear here, because this action targets stack labels only. You can always type a name by hand; the fleet-stop request still iterates every node and asks each one authoritatively.
|
||||
</Accordion>
|
||||
<Accordion title="Bulk label assign reports 'Invalid stack name' for one row">
|
||||
Stack names must match the standard validator: alphanumeric plus dash and underscore, no spaces, no path separators. The endpoint validates each assignment independently, so a single bad name does not block the rest of the batch. Fix the offending entry and re-run; the rows that already succeeded won't be re-applied.
|
||||
@@ -190,8 +190,8 @@ Run **Prune Docker resources fleet-wide** with **Images** selected and **Managed
|
||||
<Accordion title="The Apply button returns an error toast">
|
||||
Fleet Actions runs admin-only. Confirm the active user has the admin role under **Settings → Users**. Operator and viewer roles see the cards but cannot apply them.
|
||||
</Accordion>
|
||||
<Accordion title="A node returns a transport error row for every stack or every target">
|
||||
The node is in **Settings → Nodes** but its `api_url` or `api_token` is missing, expired, or unreachable. Open **Settings → Nodes** on the control instance and click **Test connection** for the remote; fix the credential or the reachability, then re-run the action.
|
||||
<Accordion title="A node is reported as unreachable (or shows a transport error per target)">
|
||||
The node is in **Settings → Nodes** but its `api_url` or `api_token` is missing, expired, or unreachable. Stop fleet by label reports it once as a single `<node> (unreachable)` row; Prune reports it per target. Open **Settings → Nodes** on the control instance and click **Test connection** for the remote; fix the credential or the reachability, then re-run the action.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
|
||||
@@ -93,9 +93,9 @@ Two cards in the **Fleet · Actions** tab use labels to drive cross-node operati
|
||||
|
||||
### Stop fleet by label
|
||||
|
||||
Type a stack label name; Sencho fans the request out to every node and stops every stack on that node assigned a stack label with the same name. This action targets stack labels only, never node labels. The picker suggests stack labels from across the fleet, read from the control instance so labels on offline nodes appear too, each with its stack and node counts so the scope is unmistakable. The result list shows a per-node breakdown with success and failure counts, and the helper line under the input states the scope: `Stops stacks assigned to this stack label across matching nodes. Node labels are not used by this action.` A confirmation modal titled `Stop all stacks with the stack label "<name>"?` with the **Stop fleet** primary action runs the action.
|
||||
Type a stack label name; Sencho fans the request out to every node and stops every stack on that node assigned a stack label with the same name. This action targets stack labels only, never node labels. The picker queries each reachable node for its own stack labels, so a label that exists only on a remote node still appears, listed once with its combined stack and node counts (and the carrying node names) so the scope is unmistakable. When a node cannot be reached, the picker notes that the suggestions may be incomplete. The result list shows a per-node breakdown with success and failure counts, and the helper line under the input states the scope: `Stops every stack assigned this stack label on every reachable node across the fleet. Node labels are not used by this action.` A confirmation modal titled `Stop all stacks with the stack label "<name>"?` with the **Stop fleet** primary action runs the action.
|
||||
|
||||
A node with no stack label by that name is reported as a failure during the run, so a partial-fleet stop is observable rather than silent.
|
||||
A node that carries no matching stack label is shown as such, and a node Sencho could not reach is reported as unreachable, so a partial-fleet stop is observable rather than silent. Unreachable nodes never block the stop on the reachable ones.
|
||||
|
||||
### Bulk label assign
|
||||
|
||||
@@ -129,7 +129,7 @@ The **Bulk label assign** card is per-node only by design. To re-tag stacks on a
|
||||
The Tags filter aggregates labels across every node in the fleet by name. If the new label only exists on one node and that node was offline at the moment the page loaded, the dropdown may not include it. Refresh **Fleet · Overview** with the **Refresh** button in the toolbar to repull node state.
|
||||
</Accordion>
|
||||
<Accordion title="`Stop fleet by label` reports `No node carries a stack label by that name`">
|
||||
Stack labels are per-node, so the fleet-stop matches by name across nodes. If the stack label you typed only exists on one node and you typed the wrong case (`prod` versus `Prod`), no node will match. The picker suggests stack labels read from the control instance across every configured node, including offline ones; node labels never appear there. Pick from the suggestion list rather than typing freehand to avoid case mistakes.
|
||||
Stack labels are per-node, so the fleet-stop matches by name across nodes. If the stack label you typed only exists on one node and you typed the wrong case (`prod` versus `Prod`), no node will match. The picker queries each reachable node for its own stack labels; node labels never appear there. A label on a node Sencho cannot currently reach will not be suggested, and the picker flags that the list may be incomplete. Pick from the suggestion list rather than typing freehand to avoid case mistakes.
|
||||
</Accordion>
|
||||
<Accordion title="`Bulk label assign` cleared every label on my stacks unexpectedly">
|
||||
The card replaces, it does not merge. Selecting no labels and clicking **Apply to N stack(s)** is the documented way to clear assignments, and the confirmation copy on the **Bulk label assign** modal restates this: `No labels selected, this will clear existing assignments on the selected stacks.` Re-pick the labels you want and run the action again to restore them.
|
||||
|
||||
Reference in New Issue
Block a user