Enhance support for Argo Rollouts - #1380
Conversation
pkg/rollouts implements the five status verbs (abort, retry, promote, promote-full, skip-step) plus revision history and rollback, over the dynamic client with no internal/ imports. Three mechanics worth knowing, all verified against a live cluster: - The verbs patch the `rollouts/status` subresource and fall back to the main resource on NotFound, because Rollouts <= v0.9 have no status subresource. `patch rollouts` does not imply `patch rollouts/status`, so callers must authorize both. - Rollback strips `rollouts-pod-template-hash` from the restored template; the controller derives that hash from template contents and a stale one wedges the rollout. - Promoting past an inconclusive analysis needs an explicit step-index advance — clearing the pause alone leaves the controller on the same step. Restart goes through `spec.restartAt`, which evicts pods older than the timestamp: a rolling pod restart, not a new revision and not a re-run of the canary steps or analysis. Also caches AnalysisRun, AnalysisTemplate, ClusterAnalysisTemplate, and Experiment so the analysis surfaces have data to read.
POST /api/rollouts/{ns}/{name}/{abort,retry,promote,promote-full,skip-step} plus
a capabilities probe the UI reads to decide which verbs to offer.
Rollback and revision history deliberately stay on the existing
/api/workloads/{kind}/... routes: same operation shape, shared revision UI. That
meant normalizing the rollbackable-kind gate so a singular kind can no longer 400
on a technicality.
The capabilities endpoint runs a SelfSubjectAccessReview for `rollouts` and
`rollouts/status` separately, since the subresource grant is independent, and
reports the strategy so the UI can hide step-relative verbs on blueGreen.
Registers a Rollout diff function so mid-canary transitions become timeline events: phase, abort, promoteFull, step index, pod hash, stable RS, blueGreen selectors, canary/stable weights, pause conditions, replica counts. Pause reasons are sorted before comparison so the controller reordering them is not a change. Registering a kind means empty diffs get dropped, so the coverage has to be complete or updates vanish silently — hence a subtest per transition. Topology gains the active AnalysisRuns as nodes hanging off their Rollout, labelled by trigger (step, background, pre-promotion, post-promotion). Only current runs: every historical AnalysisRun would grow the graph without bound. They are excluded from the generic owner-ref pass so there is exactly one producer, and the edge is EdgeManages — EdgeUses would file the Rollout under "Autoscaler" on its own AnalysisRun's detail page.
The Rollout verbs join manage_workload rather than becoming new tools, keeping them inside the description budget and the existing write-tool annotations. Also updates the setup-dialog catalog, which CI pins against the registry. AI context minification learns why a Rollout is blocked — abort takes precedence, then pause reasons, then named analysis verdicts — and summarizes AnalysisRuns by naming only the failing or inconclusive metrics, so an agent gets the deciding metric instead of "InconclusiveAnalysisRun".
The Rollout detail page gains an action row and an Analysis section; AnalysisRun gets its own renderer. Both are built around one question: why is this rollout stuck, and what do I do about it. Gating distinguishes the two reasons a verb is unavailable. A verb the capability probe denied is absent entirely — a greyed button reads as "you could do this" and hides whether RBAC or state is the cause. A verb blocked by state is present and carries the reason. The revision table separates `Current` from `Stable`, which diverge mid-canary: revision 5 can be rolling out while 3 still serves traffic. Rolling back re-enters the strategy — canary replays every step, blueGreen parks the revision in preview with the active Service untouched — so the dialog offers promoting straight through. That option is kind-gated, not strategy-gated, and hidden when the probe denies promote-full. AnalysisRun detail splits the three verdicts an operator must act on differently: Error could not run, Failed will abort the rollout, Inconclusive is waiting on a human. Each metric is paired with its condition from the spec, so a stuck rollout shows `latest: 2` against `success if: result == 1`. Problem banners use AlertBanner rather than hand-rolled colors, which wash out in light mode, and dedupe on message — an aborted Rollout is also Degraded and both render status.message.
`make rollouts-demo` bootstraps a kind cluster parked in the five states the control surface has to handle: a manual canary pause, an inconclusive analysis, a blueGreen pause, a canary aborted by a failing analysis, and a workloadRef canary whose template lives on a Deployment. Generating a deterministic Inconclusive without Prometheus needed a lever: with both successCondition and failureCondition set, a result matching neither evaluates Inconclusive. So the metric provider is an nginx serving static JSON — `2` against `result == 1` / `result == 0`. The parking checks run before waiting for Healthy so re-running against an already-parked cluster is idempotent rather than timing out.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 4 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit d2d8231. Configure here.
| { kind, namespace, name, revision }, | ||
| { | ||
| onSuccess: () => { | ||
| if (canPromoteAfterRollback && promoteAfterRollback) onRolloutPromoteFull?.({ namespace, name }) |
There was a problem hiding this comment.
Silent promote-full after rollback
Medium Severity
Checking “Promote fully after rollback” fires promote-full only inside the rollback onSuccess handler as a fire-and-forget mutate, then closes the dialog immediately. A failed or rejected promote-full never surfaces, so operators can believe the hotfix path applied when only the template rollback did.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit d2d8231. Configure here.
| // Rollback dialog state | ||
| const [showRevisions, setShowRevisions] = useState(false) | ||
| const isRollbackKind = ['deployments', 'statefulsets', 'daemonsets'].includes(kind) | ||
| const isRollbackKind = ['deployments', 'statefulsets', 'daemonsets', 'rollouts'].includes(kind) |
There was a problem hiding this comment.
Ungated rollout restart and rollback
Medium Severity
Restart and Rollback for Rollouts are shown whenever the host callbacks exist, but the new capabilities probe’s restart, rollback, and terminating flags are never consulted. Users without patch rollouts, or with a deleting Rollout, still see those buttons and hit 403/409 on click, unlike the detail-row verbs that omit denied actions.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit d2d8231. Configure here.
| return nil | ||
| } | ||
|
|
||
| // ScaleWorkloadDirect scales a Deployment or StatefulSet without requiring a WorkloadManager. |
There was a problem hiding this comment.
Rollout scale still needs discovery
Low Severity
ScaleWorkload now accepts Rollouts but still requires discovery.GetGVR, while Restart, revision list, rollback, and ScaleWorkloadDirect use the hardcoded rollouts.GVR. MCP and the HTTP scale handler go through ScaleWorkload, so Rollout scale can fail with “unknown resource kind” / discovery-not-ready even when other Rollout verbs work.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit d2d8231. Configure here.
| if strings.ToLower(kind) == "rollouts" { | ||
| s.writeRolloutError(w, err, "rollback", namespace, name) | ||
| return | ||
| } |
There was a problem hiding this comment.
Rollback error mapping misses singular kind
Low Severity
Rollout sentinel errors are routed to writeRolloutError only when the raw path kind lowercases to exactly rollouts. The allowlist above uses NormalizeWorkloadKind, so a singular rollout passes validation but then maps ErrTemplateUnchanged / terminating conflicts to 500 instead of 409.
Reviewed by Cursor Bugbot for commit d2d8231. Configure here.
| statusPatch = []byte(fmt.Sprintf(clearPauseConditionsAndControllerPausePatch, next)) | ||
| unifiedPatch = []byte(fmt.Sprintf(unpauseAndClearPauseConditionsPatchWithStep, next)) | ||
| default: | ||
| statusPatch = []byte(clearPauseConditionsPatch) |
There was a problem hiding this comment.
I think this diverges from the Argo Rollouts CLI behavior: when the current step is a running analysis, the CLI advances to the next step. this would return success but leave the Rollout on the same step. Reproduced on the demo cluster.
| strategy := rollouts.StrategyOf(ro) | ||
| terminating := ro.GetDeletionTimestamp() != nil && !ro.GetDeletionTimestamp().IsZero() | ||
|
|
||
| // The status verbs fall back to a main-resource patch on Rollouts <= v0.9, so |
There was a problem hiding this comment.
argo rollouts 0.9 is from 2020 and EOL, no need to support so far back, just adds code complexity here. unless you have reason to expect people to still be running rollouts so old?
There was a problem hiding this comment.
P.S. so I think just require patch rollouts/status for modern installs
|
Promote full and Abort are one-click production traffic changes. Can we put those two behind ConfirmDialog? Normal Promote and Skip Step can stay direct. we have various examples of ConfirmDialog in the codebase for sensitive actions |
nadaverell
left a comment
There was a problem hiding this comment.
Great work - thanks for this PR!
A few small things before merging, see the comments
P.S. also I recommend checking if the BugBot findings are valid or not, usually it's pretty decent at spotting potential issues, just always double-chck. |


Description
This adds enhanced support for Argo Rollouts in the UI. The following is a list of buttons added to Argo Rollouts details pages and their effect.
Restartonly executes a rolling restart of Pods, not a full Rollout with new Analysis (spec.restartAtmutation)Rollbackbutton and modal. Shows a popup with versions that can be rolled back to. Optional 'Full promotion' check box to push the rollback through without a full Rollout.PromoteandFull Promotebuttons to allow operators to manage Rollout stage promotionAbortbutton to abort a RolloutType of change
How has this been tested?
Describe the tests you ran to verify your changes.
I included scripting and artifacts that I used for running a local demo/testing in kind. Also tested against out own remote clusters. Added unit tests were it made sense.
Checklist
Related issues
Fixes #1379
Note
Medium Risk
New mutating paths patch Rollout status and workload rollback semantics differ from Deployments (rollback restarts the full strategy); mistakes could affect production traffic, but changes are RBAC-gated and heavily tested.
Overview
Adds first-class Argo Rollouts support across API, MCP, UI, and change history—not just listing Rollouts.
Control plane: New
pkg/rolloutsimplements abort/retry/promote/promote-full/skip-step/restart/undo with status-subresource patches (and unified fallback for older controllers). HTTP routesGET /api/rollouts/{ns}/{name}/capabilitiesandPOST .../{action}gate buttons on separatepatch rolloutsvspatch rollouts/statusSARs. Rollouts join Deployments/StatefulSets/DaemonSets on/api/workloads/...for revisions and rollback, with rollout-specific error mapping.MCP:
manage_rolloutwrite tool;manage_workloadacceptsrollout(with guidance that rollback re-runs the strategy).get_resource include=revisionslists rollback targets including stable vs current for Rollouts.UI: Rollout detail gets capability-gated action row, analysis-run links, richer problem detection; new AnalysisRun list columns and detail renderer. Revision history shows Stable/Rolling out badges and optional promote fully after rollback for hotfixes.
Observability / AI:
diffRolloutkeeps canary step/weight/pause/abort events in the timeline; AI summaries explain why a Rollout is stuck (pause reasons, inconclusive analysis). CRD cache fallbacks andmake rollouts-demofor local kind fixtures.Reviewed by Cursor Bugbot for commit d2d8231. Bugbot is set up for automated code reviews on this repo. Configure here.