feat(component): add OrphanWhen to release a resource without deleting it#145
Merged
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a new component.OrphanWhen(cond) resource option that releases a managed resource by removing the component's controller owner reference from the live object, leaving the object in the cluster. This supports migrating resources to a new owner without triggering garbage collection. The change introduces a separate orphanResources slice on the Component, a dedicated reconcile pass (orphanResources) that fetch-and-updates only metadata.ownerReferences, and runs the pass in all three reconcile paths (normal, suspended, feature-gate-disabled).
Changes:
- New
OrphanWhenresource option with OR-semantics and exclusivity validation againstDelete/DeleteWhen/GatedBy/ReadOnly. - New
orphanResourcesreconcile pass using fetch-and-update + retry-on-conflict, idempotent and not-found tolerant. - Builder partitions orphan-marked resources into their own slice (excluded from
allManagedResources()), andReconcileinvokes the pass in normal, suspended, and feature-gate-disabled paths.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/component/resource_options.go | Adds OrphanWhen, Orphan resolved option, and exclusivity validation. |
| pkg/component/resource_options_test.go | Unit tests for orphan resolution and exclusivity errors. |
| pkg/component/orphan.go | New orphanResources pass: fetch live object, strip owner UID from refs, update, retry on conflict. |
| pkg/component/orphan_test.go | Fake-client tests covering removal, idempotency, not-found, and selective UID removal. |
| pkg/component/orphan_integration_test.go | Builder-level partition test and end-to-end Reconcile path test. |
| pkg/component/component.go | Adds orphanResources field and invokes the pass in all three reconcile paths. |
| pkg/component/builder.go | Routes orphan-marked resources to the new slice via a switch on resolved options. |
| e2e/component/orphan_test.go | Kind-cluster E2E proving the orphaned object survives owner GC while a control object does not. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds a
component.OrphanWhen(cond)resource option that releases a managed resource instead of deleting it: when the condition is true, the component removes the controller owner reference it set and stops managing the resource, leaving the object in the cluster. The motivating use is migrating a resource to a new owner without deleting it, since removing the owner reference both stops garbage-collection with the old owner and unblocks adoption by a new one.Changes
component.OrphanWhen(condition bool) ResourceOption(additive, OR semantics). It is a build-time configuration error to combine it withDelete,DeleteWhen,GatedBy, orReadOnly.orphanResources): for each orphaned resource it fetches the live object and removes only this owner's controller owner reference (matched by UID) via a fetch-and-update, preserving all other fields. It never applies desired state, never deletes, contributes no health, and is idempotent (a missing object or already-absent reference is a no-op; conflicts are retried).allManagedResources(), so a disabled component feature gate never deletes an orphan-marked resource. The pass runs in all three reconcile paths (normal, suspended, feature-gate-disabled).Challenges
Ownership is removed with a fetch-and-update of the live object rather than a Server-Side Apply. Because the component applies with forced ownership, an SSA of a stripped-down object would drop the spec fields its field manager owns and revert the resource's content; editing only
metadata.ownerReferenceson the fetched object preserves everything else.Related
Documentation (the
component.mdresource-options table and the IncludeWhen-vs-GatedBy guidance) is updated separately on the docs site branch.Testing
Unit tests cover option resolution and the four exclusivity errors; fake-client tests cover the pass (owner reference removed, object survives, idempotent, only this owner's reference removed, not-found no-op) and the wired
Reconcilepath. A new E2E test on a real kind cluster proves the payoff end to end: it garbage-collects a control resource after the owner is deleted (confirming GC is active) and asserts the orphaned resource survives owner deletion with its owner reference gone.make allpasses;make e2e-componentpasses (13/13).