Repository navigation
fix(retirement): recognize controller-completed cleanup #5041
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -807,6 +807,23 @@ func unchangedOwner(a, b, chart map[string]any) bool { | |
| } | ||
| return true | ||
| } | ||
|
|
||
| func (e engine) controllerLifecycleStarted(before, after snapshot) bool { | ||
| if !sameStoredContent(before.release, after.release) { | ||
| return false | ||
| } | ||
| if after.owner != nil { | ||
| if !identityComplete(after.owner) || identity(after.owner) != identity(before.owner) || field(after.owner, "metadata", "uid") != field(before.owner, "metadata", "uid") || field(after.owner, "metadata", "generation") != field(before.owner, "metadata", "generation") || !reflect.DeepEqual(after.owner["spec"], before.owner["spec"]) || text(field(after.owner, "metadata", "deletionTimestamp")) == "" { | ||
| return false | ||
| } | ||
| } | ||
| if after.chart != nil { | ||
| if !identityComplete(after.chart) || identity(after.chart) != identity(before.chart) || field(after.chart, "metadata", "uid") != field(before.chart, "metadata", "uid") || !reflect.DeepEqual(after.chart["spec"], before.chart["spec"]) || e.chartOwned(after) != nil { | ||
| return false | ||
| } | ||
| } | ||
| return true | ||
| } | ||
| func targetResource(r map[string]any) bool { | ||
| name := text(field(r, "metadata", "name")) | ||
| kind := text(r["kind"]) | ||
|
|
@@ -1070,13 +1087,26 @@ func run(args []string, out io.Writer, execute command) int { | |
| if err != nil || !reflect.DeepEqual(before, kept) { | ||
| return hold("HOLD_MUTATION") | ||
| } | ||
| controllerLifecycle := false | ||
| if e.admissionControls(ctx, current) != nil { | ||
| return hold("HOLD_ADMISSION") | ||
| } | ||
| // Kubernetes owns the ongoing finalizer once DELETE may have been submitted. | ||
| // A workflow error, timeout or process stop cannot cancel its retries. | ||
| if nativeSubmitted, err = e.remove(ctx, current.owner, "/apis/helm.toolkit.fluxcd.io/v2/namespaces/backstage/helmreleases/backstage"); err != nil { | ||
| return hold("HOLD_MUTATION") | ||
| // The GitOps deletion can reach the Helm controller immediately after the | ||
| // durable journal unlocks the fence. If that happens while the admission | ||
| // probes run, observe the already-started exact lifecycle; never replay it. | ||
| observed, observeErr := e.collect(ctx) | ||
|
devantler marked this conversation as resolved.
Resolved
|
||
| if observeErr != nil || !e.controllerLifecycleStarted(current, observed) || e.deployed(observed) != nil { | ||
| return hold("HOLD_ADMISSION") | ||
| } | ||
| kept, observeErr = preserved(observed) | ||
| if observeErr != nil || !reflect.DeepEqual(before, kept) { | ||
| return hold("HOLD_ADMISSION") | ||
| } | ||
| controllerLifecycle = true | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When this observation path proves that the controller lifecycle has already started but a later postcondition fails or times out, Useful? React with 👍 / 👎. |
||
| } else { | ||
| // Kubernetes owns the ongoing finalizer once DELETE may have been submitted. | ||
| // A workflow error, timeout or process stop cannot cancel its retries. | ||
| if nativeSubmitted, err = e.remove(ctx, current.owner, "/apis/helm.toolkit.fluxcd.io/v2/namespaces/backstage/helmreleases/backstage"); err != nil { | ||
| return hold("HOLD_MUTATION") | ||
|
Comment on lines
+1104
to
+1108
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If Flux begins pruning after the final admission probe succeeds but before Useful? React with 👍 / 👎. |
||
| } | ||
| } | ||
| var after snapshot | ||
| // The native uninstall is allowed five minutes. A two-minute observation | ||
|
|
@@ -1105,6 +1135,9 @@ func run(args []string, out io.Writer, execute command) int { | |
| return hold("HOLD_POSTCONDITION") | ||
| } | ||
| if field(after.chart, "metadata", "deletionTimestamp") == nil { | ||
| if controllerLifecycle { | ||
| return hold("HOLD_POSTCONDITION") | ||
| } | ||
| if _, err = e.remove(ctx, after.chart, "/apis/source.toolkit.fluxcd.io/v1/namespaces/backstage/helmcharts/backstage-backstage"); err != nil { | ||
| return hold("HOLD_MUTATION") | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If an admission probe fails for a reason other than target disappearance—for example, a retained-object negative control is unexpectedly accepted because webhook enforcement is ineffective—concurrent Flux deletion can still make
controllerLifecycleStartedreturn true, so this branch converts the failed mandatory control into observation and eventual success. The fresh issue is that this new branch treats every admission error identically even though the DELETE probes now carry a correct dry-run body; only a failure specifically attributable to the captured retiring object disappearing should be adoptable, while ineffective or incomplete admission evidence must remain a hold.AGENTS.md reference: AGENTS.md:L600-L603
Useful? React with 👍 / 👎.