Skip to content

fix(controller): silently dropped cross-namespace releases in bootstrap chart#570

Open
dermorz wants to merge 1 commit into
mainfrom
fix/bootstrap-input-unique-name-key
Open

fix(controller): silently dropped cross-namespace releases in bootstrap chart#570
dermorz wants to merge 1 commit into
mainfrom
fix/bootstrap-input-unique-name-key

Conversation

@dermorz

@dermorz dermorz commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

What

Fix a bug where two same-named Releases from different namespaces silently overwrite each other in the bootstrap chart.

Why

buildBootstrapInput keyed the bootstrap Releases map on ri.name — the bare Kubernetes Release object name. With cross-namespace ReleaseBindings (#541) two Releases named my-release from different namespaces can both survive conflict resolution and target the same Target, colliding on the map key and dropping one deployment silently.

Fix: key on uniqueName instead — already guaranteed unique among accepted releases by the resolver. Also extract the derivation rule into a shared effectiveUniqueName() helper (was duplicated in both controllers), and add a guard that returns an explicit error if uniqueName is empty.

Testing

  • New buildBootstrapInput tests: cross-namespace collision (via full resolveReleaseConflicts path) and explicit error on empty uniqueName.
  • make test / make lint — clean.

Notes for reviewers

  • Upgrade path: bootstrap map keys change from the Release object name to uniqueName. Existing Targets where the two differ will have inner FluxCD HelmReleases recreated on next bootstrap render.
  • effectiveUniqueName(rel, cv) lives in helpers.go; both controllers use it.
  • Docs updated: rendering-pipeline.md and ADR-004 now document why uniqueName is the bootstrap identity and why the object name is unsuitable.

Checklist

  • Tests added/updated
  • No breaking changes (or upgrade path documented above)
  • Readable commit history (squashed and cleaned up as desired)
  • AI code review considered and comments resolved

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Prevented release overwrites when releases share the same Kubernetes name across namespaces by using a stable per-release key for bootstrap inputs.
  • Documentation
    • Clarified how releases are uniquely identified and how bootstrap/Helm release names are derived to avoid collisions.
  • Tests
    • Added tests verifying the uniqueness requirement and that bootstrap input generation no longer collides.

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds computation and propagation of a release "uniqueName" (via effectiveUniqueName), records it on internal releaseInfo entries during conflict resolution, requires it for buildBootstrapInput, and keys bootstrap chart inputs by that uniqueName; updates tests and docs to reflect the naming contract.

Changes

Bootstrap Release Keying by UniqueRelease

Layer / File(s) Summary
Release identity: helper, reconciler, and releaseInfo
pkg/controller/helpers.go, pkg/controller/release_controller.go, pkg/controller/target_controller.go
Adds effectiveUniqueName(rel, cv), uses it in Release reconciler to compute the effective unique name, and extends releaseInfo with a uniqueName field populated by resolveReleaseConflicts.
Bootstrap input keyed by uniqueName
pkg/controller/target_controller.go
buildBootstrapInput errors if any releaseInfo.uniqueName is empty and stores resolved releases keyed by uniqueName instead of the release object name.
Integration tests for bootstrap input keying
pkg/controller/target_controller_test.go
New tests assert buildBootstrapInput errors on empty uniqueName and that two cross-namespace same-named releases are disambiguated via derived uniqueName values so the releases map contains both entries.
Docs: ADR and rendering pipeline updates
docs/developer-guide/adrs/004-Unique-Release-Name.md, docs/developer-guide/rendering-pipeline.md
Clarifies that bootstrap chart per-release input maps and the inner HelmRelease <release-key> use uniqueName (or component name fallback) to avoid cross-namespace name collisions and documents the naming contract and suffix behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • trevex
  • yocaba

Poem

A rabbit hops through release names so fine,
When namespaces clash, I choose a new line.
I compute the uniqueName, tidy and true,
Bootstrap maps sing — each release gets through. 🐰

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly summarizes the main bug fix (cross-namespace releases silently dropped) and references the solution (bootstrap chart keying change).
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description follows the template structure with all required sections completed: What, Why, Testing, Notes for reviewers, and Checklist all filled out with relevant details.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/bootstrap-input-unique-name-key

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
pkg/controller/target_controller.go (1)

737-752: ⚡ Quick win

Fail fast on empty/duplicate bootstrap release keys.

This path can silently overwrite entries again if ri.uniqueName is empty or duplicated due to upstream regression. Add an explicit guard and return an error instead of relying only on invariants.

Proposed hardening patch
 func buildBootstrapInput(target *solarv1alpha1.Target, releases []releaseInfo) (solarv1alpha1.BootstrapInput, error) {
 	resolvedReleases := map[string]solarv1alpha1.ResourceAccess{}

 	for _, ri := range releases {
+		if ri.uniqueName == "" {
+			return solarv1alpha1.BootstrapInput{}, fmt.Errorf("release %s has empty uniqueName", ri.name)
+		}
+		if _, exists := resolvedReleases[ri.uniqueName]; exists {
+			return solarv1alpha1.BootstrapInput{}, fmt.Errorf("duplicate uniqueName in bootstrap input: %s", ri.uniqueName)
+		}
+
 		ref, err := ociname.ParseReference(ri.chartURL)
 		if err != nil {
 			return solarv1alpha1.BootstrapInput{}, fmt.Errorf("failed to parse chartURL %s: %w", ri.chartURL, err)
 		}
@@
 		resolvedReleases[ri.uniqueName] = solarv1alpha1.ResourceAccess{
 			Repository: strings.TrimPrefix(repo, "oci://"),
 			Tag:        ref.Identifier(),
 		}
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/controller/target_controller.go` around lines 737 - 752, The loop over
releases can silently overwrite entries when ri.uniqueName is empty or
duplicated; inside the loop in the code that builds resolvedReleases (iterate
releases, parse chartURL, build repo/tag), add a guard that checks ri.uniqueName
is non-empty and that resolvedReleases does not already contain that key, and if
either condition fails return a clear error (e.g., "empty bootstrap release key"
or "duplicate bootstrap release key: %s") instead of continuing; update the
block that populates resolvedReleases to validate ri.uniqueName before assigning
to resolvedReleases[ri.uniqueName] and return the error immediately when the
guard triggers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@pkg/controller/target_controller.go`:
- Around line 737-752: The loop over releases can silently overwrite entries
when ri.uniqueName is empty or duplicated; inside the loop in the code that
builds resolvedReleases (iterate releases, parse chartURL, build repo/tag), add
a guard that checks ri.uniqueName is non-empty and that resolvedReleases does
not already contain that key, and if either condition fails return a clear error
(e.g., "empty bootstrap release key" or "duplicate bootstrap release key: %s")
instead of continuing; update the block that populates resolvedReleases to
validate ri.uniqueName before assigning to resolvedReleases[ri.uniqueName] and
return the error immediately when the guard triggers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 750d9fa1-bd7c-4404-b634-5b33b2fa7f23

📥 Commits

Reviewing files that changed from the base of the PR and between 3f141ba and cf5190a.

📒 Files selected for processing (4)
  • docs/developer-guide/adrs/004-Unique-Release-Name.md
  • docs/developer-guide/rendering-pipeline.md
  • pkg/controller/target_controller.go
  • pkg/controller/target_controller_test.go

@coveralls

coveralls commented Jun 2, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 26817890237

Warning

No base build found for commit 3f141ba on main.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 72.569%

Details

  • Patch coverage: 16 of 16 lines across 3 files are fully covered (100%).

Uncovered Changes

No uncovered changes found.

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 3383
Covered Lines: 2455
Line Coverage: 72.57%
Coverage Strength: 52.5 hits per line

💛 - Coveralls

…espace collisions

buildBootstrapInput keyed the bootstrap Releases map on the Release object
name (ri.name), which is not unique across namespaces. With cross-namespace
ReleaseBindings (#541), two same-named Releases from different namespaces can
both survive conflict resolution (distinct uniqueNames) and target the same
Target. They collided on the shared map key and one was silently dropped from
the bootstrap chart — no error, just a missing deployment.

Key the map on uniqueName instead — the deduplication key resolveReleaseConflicts
already guarantees is unique among accepted releases. Store the computed
uniqueName back onto releaseInfo so buildBootstrapInput can reuse it.

Adds a buildBootstrapInput unit test with two same-named releases from
different namespaces that fails before this fix.

Also extract the uniqueName derivation rule into a shared effectiveUniqueName()
helper in helpers.go, replacing the duplicated inline logic in both
target_controller.go and release_controller.go.

Add a guard in buildBootstrapInput that returns an explicit error when
ri.uniqueName is empty, surfacing the invariant violation instead of silently
collapsing all releases onto the "" key.
@dermorz dermorz force-pushed the fix/bootstrap-input-unique-name-key branch from cf5190a to 72f690c Compare June 2, 2026 11:51
@dermorz

dermorz commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

3 participants