Skip to content

Enhance support for Argo Rollouts - #1380

Open
albertrdixon wants to merge 6 commits into
skyhook-io:mainfrom
albertrdixon:feat/argo-rollouts-actions
Open

Enhance support for Argo Rollouts#1380
albertrdixon wants to merge 6 commits into
skyhook-io:mainfrom
albertrdixon:feat/argo-rollouts-actions

Conversation

@albertrdixon

@albertrdixon albertrdixon commented Aug 7, 2026

Copy link
Copy Markdown

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.

  • Restart only executes a rolling restart of Pods, not a full Rollout with new Analysis (spec.restartAt mutation)
  • Rollback button 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.
  • Promote and Full Promote buttons to allow operators to manage Rollout stage promotion
  • Abort button to abort a Rollout

Type of change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

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.

  • Tested locally with minikube/kind
  • Tested against a remote cluster
  • Added/updated unit tests

Checklist

  • My code follows the project's coding standards
  • I have performed a self-review of my code
  • I have added comments where necessary
  • My changes generate no new warnings
  • Any dependent changes have been merged

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/rollouts implements abort/retry/promote/promote-full/skip-step/restart/undo with status-subresource patches (and unified fallback for older controllers). HTTP routes GET /api/rollouts/{ns}/{name}/capabilities and POST .../{action} gate buttons on separate patch rollouts vs patch rollouts/status SARs. Rollouts join Deployments/StatefulSets/DaemonSets on /api/workloads/... for revisions and rollback, with rollout-specific error mapping.

MCP: manage_rollout write tool; manage_workload accepts rollout (with guidance that rollback re-runs the strategy). get_resource include=revisions lists 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: diffRollout keeps canary step/weight/pause/abort events in the timeline; AI summaries explain why a Rollout is stuck (pause reasons, inconclusive analysis). CRD cache fallbacks and make rollouts-demo for local kind fixtures.

Reviewed by Cursor Bugbot for commit d2d8231. Bugbot is set up for automated code reviews on this repo. Configure here.

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.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 4 potential issues.

Fix All in Cursor

❌ 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 })

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d2d8231. Configure here.

Comment thread pkg/k8score/workload.go
return nil
}

// ScaleWorkloadDirect scales a Deployment or StatefulSet without requiring a WorkloadManager.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d2d8231. Configure here.

Comment thread internal/server/server.go
if strings.ToLower(kind) == "rollouts" {
s.writeRolloutError(w, err, "rollback", namespace, name)
return
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P.S. so I think just require patch rollouts/status for modern installs

@nadaverell

Copy link
Copy Markdown
Contributor

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 nadaverell left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work - thanks for this PR!

A few small things before merging, see the comments

@nadaverell

Copy link
Copy Markdown
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enhance support for Argo Rollouts

2 participants