Skip to content

feat(component): Unowned() resource option to suppress owner reference#159

Merged
sourcehawk merged 4 commits into
mainfrom
feat/unowned-resource-option
Jun 25, 2026
Merged

feat(component): Unowned() resource option to suppress owner reference#159
sourcehawk merged 4 commits into
mainfrom
feat/unowned-resource-option

Conversation

@sourcehawk

@sourcehawk sourcehawk commented Jun 25, 2026

Copy link
Copy Markdown
Owner

Description

Adds Unowned(), a new ResourceOption for resources that must outlive their owner CR. The component creates and updates the resource normally but does not set a controller owner reference, so Kubernetes will not garbage-collect it when the owner is deleted. The primary use case is CRs that should persist after the application CR is removed.

Changes

  • Unowned() ResourceOption constructor and Unowned bool field on resourceOptions
  • mutateResource and applyResource accept a skipOwnerRef bool parameter; the suspension path (suspendResources, suspendResource, managedResources) was updated to carry reconcileEntry instead of bare Resource so the flag flows correctly through suspension mutations
  • applyResources updated to accept []reconcileEntry for the same reason
  • docs/component.md updated with the new option in the resource registration table and a prose explanation of its semantics

Challenges

The Unowned flag needed to reach the suspension path, which previously worked with []Resource and had no per-resource option access. The fix was to thread reconcileEntry through managedResources(), suspendResources, suspendResource, and applyResources — mechanical but touching several signatures. Existing tests updated accordingly.

Testing

All tests pass (make all clean). New tests cover: option resolution (Unowned sets the flag, composes with Auxiliary), mutateResource with skipOwnerRef=true returns no owner ref and does not report a scope skip, reconcileResources end-to-end confirms the created object has no owner references, and suspendResource confirms the same for the suspension path.

Add Unowned() ResourceOption for resources that must outlive their owner
CR, such as backup records. The component creates and updates the resource
normally but omits the controller owner reference, so Kubernetes GC does
not cascade-delete it when the owner is removed.

Explicit deletion paths (Delete(), DeleteWhen(), GatedBy() when disabled,
and suspension's DeleteOnSuspend()) are unaffected — only GC is suppressed.

The Unowned flag is propagated through the suspension path so that
suspension mutations applied to an Unowned resource also skip setting the
owner reference.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 25, 2026 15:13
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Copilot AI 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.

Pull request overview

Adds a new component.Unowned() ResourceOption to allow managed resources to be created/updated without a controller owner reference, preventing Kubernetes garbage collection on owner CR deletion. To ensure the option applies consistently, the PR threads per-resource options through the suspension path by carrying reconcileEntry instead of bare Resource.

Changes:

  • Introduces Unowned() option, adds Unowned to resolved resourceOptions, and documents the option’s semantics.
  • Threads reconcileEntry through suspension/application helpers so per-resource options (including Unowned) are available during suspension.
  • Updates and adds tests to cover option resolution and end-to-end “no owner refs” behavior in both normal and suspension paths.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
pkg/component/suspend.go Updates suspension flow to operate on []reconcileEntry so options can affect suspension apply.
pkg/component/suspend_test.go Updates suspension tests for reconcileEntry and adds a new Unowned suspension-path test.
pkg/component/resource_options.go Adds Unowned() option and resolves it into resourceOptions.
pkg/component/resource_options_test.go Adds test cases validating Unowned resolution and composition with Auxiliary.
pkg/component/create.go Threads skipOwnerRef/Unowned into apply/mutate path; skips setting controller owner ref when configured.
pkg/component/create_test.go Updates apply tests for reconcileEntry and adds tests for Unowned behavior in mutate/reconcile.
pkg/component/component.go Adjusts managed-resource selection for suspension to return []reconcileEntry.
docs/component.md Documents Unowned() in the resource options table and adds semantic explanation.

Comment thread pkg/component/create.go
Comment on lines +256 to +258
if skipOwnerRef {
return false, nil
}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in f6cbbc8. Added obj.SetOwnerReferences(nil) before returning in the skipOwnerRef branch so any owner reference cached from a previous reconcile is cleared. SSA then removes the entry this field manager previously owned. Test added: "should clear a previously-cached owner reference when skipOwnerRef is true".

Comment thread docs/component.md Outdated
| `component.ReadOnly()` | **Read-only**: fetched but never modified; health still contributes |
| `component.Delete()` / `component.DeleteWhen(cond)` | **Delete**: removed from the cluster (unconditionally, or when `cond` is true); does not contribute to health |
| `component.GatedBy(gate)` | Deletes the resource when the feature gate is disabled; managed when enabled |
| `component.Unowned()` | **Unowned**: created and updated normally, but no owner reference is set; not garbage-collected on owner CR deletion |

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in f6cbbc8. Changed to "no controller owner reference is set" to match the implementation, which only skips ctrl.SetControllerReference.

…s wording

When `Unowned()` is set and an owner reference was present from a previous
reconcile (the DesiredObject pointer retains the server response), SSA would
re-apply the cached reference.  Clear it explicitly before patching so SSA
removes any entry this field manager previously owned.

Also corrects the resource-options table in docs/component.md: "no owner
reference is set" → "no controller owner reference is set", matching the
actual implementation which only skips `ctrl.SetControllerReference`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 25, 2026 17:07

Copilot AI 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.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Comment thread pkg/component/create.go Outdated
Comment on lines +259 to +262
// owner ref set before Unowned() was added). Sending nil via SSA removes
// any entry this field manager previously owned.
obj.SetOwnerReferences(nil)
return false, nil

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 8819491. Replaced SetOwnerReferences(nil) with an in-place filter that removes only the entry whose UID matches the component owner, so owner references set by Mutate() for other objects are preserved. Test added: "should preserve ownerReferences to other objects when skipOwnerRef is true".

SetOwnerReferences(nil) cleared every owner reference on the object,
including refs that Mutate() may have set for a different owner. Replace
it with an in-place filter that removes only the entry whose UID matches
the component owner, preserving any other owner references intact.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@sourcehawk sourcehawk merged commit 9df87b9 into main Jun 25, 2026
5 checks passed
@sourcehawk sourcehawk deleted the feat/unowned-resource-option branch June 25, 2026 17:38
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.

2 participants