-
Notifications
You must be signed in to change notification settings - Fork 75
fix(recipe,client): reject duplicate ComponentRef names, omit disable… #1917
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 1 commit
70b865d
486b11a
59de9b7
2109ea2
dc5a9d6
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 |
|---|---|---|
|
|
@@ -304,3 +304,65 @@ func TestPrepareAndValidate_RejectsReservedDeployerName(t *testing.T) { | |
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestPrepareAndValidate_RejectsDuplicateNames(t *testing.T) { | ||
|
Member
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. 🔵 Nitpick — Worth one more edge case: reserved-key-vs-duplicate precedence Two refs both named Blast radius: Test completeness only. Fix: Add the precedence case. (Three-way duplicates exercise no new path, and whitespace-padded names are treated as distinct — consistent with the total absence of name normalization anywhere in the codebase — so both of those are skippable.) |
||
| tests := []struct { | ||
| name string | ||
| refs []ComponentRef | ||
| wantErr bool | ||
| }{ | ||
| { | ||
|
Member
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. 🔵 Nitpick — Positional struct literals where every sibling table in the file uses keyed fields
Blast radius: Readability only. Keyed literals survive a field insertion into the anonymous struct without silently reassigning values, which is why the file already prefers them. Fix: Add the |
||
| "adjacent duplicate", | ||
| []ComponentRef{ | ||
| {Name: "gpu-operator", Type: ComponentTypeHelm, Source: "https://charts.example.com", Version: "1.0.0"}, | ||
| {Name: "gpu-operator", Type: ComponentTypeHelm, Source: "https://charts.example.com", Version: "1.0.0"}, | ||
| }, | ||
| true, | ||
| }, | ||
| { | ||
| "non-adjacent duplicate", | ||
| []ComponentRef{ | ||
| {Name: "a", Type: ComponentTypeHelm, Source: "https://charts.example.com", Version: "1.0.0"}, | ||
| {Name: "b", Type: ComponentTypeHelm, Source: "https://charts.example.com", Version: "1.0.0"}, | ||
| {Name: "a", Type: ComponentTypeHelm, Source: "https://charts.example.com", Version: "1.0.0"}, | ||
| }, | ||
| true, | ||
| }, | ||
| { | ||
| "disabled duplicate still rejected", | ||
| []ComponentRef{ | ||
| {Name: "a", Type: ComponentTypeHelm, Source: "https://charts.example.com", Version: "1.0.0"}, | ||
| { | ||
| Name: "a", | ||
| Overrides: map[string]any{"enabled": false}, | ||
| }, | ||
| }, | ||
| true, | ||
| }, | ||
| { | ||
| "empty names not flagged", | ||
|
Member
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. 🔵 Nitpick — Both refs are disabled, so Blast radius: Test only. The configuration that actually reaches the deployers is two enabled nameless refs, and that has no coverage. Fix: Swap the fixture to two enabled refs with |
||
| []ComponentRef{ | ||
| {Name: "", Overrides: map[string]any{"enabled": false}}, | ||
| {Name: "", Overrides: map[string]any{"enabled": false}}, | ||
| }, | ||
| false, | ||
| }, | ||
| { | ||
| "unique names ok", | ||
| []ComponentRef{ | ||
| {Name: "a", Type: ComponentTypeHelm, Source: "https://charts.example.com", Version: "1.0.0"}, | ||
| {Name: "b", Type: ComponentTypeHelm, Source: "https://charts.example.com", Version: "1.0.0"}, | ||
| }, | ||
| false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| r := &RecipeResult{ComponentRefs: tt.refs} | ||
| err := r.PrepareAndValidate() | ||
| if (err != nil) != tt.wantErr { | ||
|
Member
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. 🔵 Nitpick — Test asserts only The check is currently pinned — mutation-tested via Blast radius: Test only. The sibling Fix: Behind the existing |
||
| t.Fatalf("PrepareAndValidate() error = %v, wantErr %v", err, tt.wantErr) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -563,6 +563,20 @@ func (r *RecipeResult) PrepareAndValidate() error { | |
| fmt.Sprintf("recipe component %q uses the reserved deployer override key as its name; %q is reserved for --set deployer:<key> Argo deployer options", r.ComponentRefs[i].Name, ReservedDeployerKey)) | ||
| } | ||
| } | ||
| // Reject duplicate component-ref names (enabled or disabled) BEFORE | ||
|
Member
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. 🟡 Minor — Criteria-resolve path still bypasses the duplicate check
Blast radius: Limited. Every deploy path still fails closed — Fix: Follow-up is fine: extract a shared |
||
| // registry back-fill. See #1874. | ||
| seen := make(map[string]bool, len(r.ComponentRefs)) | ||
| for i := range r.ComponentRefs { | ||
| name := r.ComponentRefs[i].Name | ||
| if name == "" { | ||
|
Member
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. 🔵 Nitpick — Empty-name exemption leaves the same collision class open (pre-existing) Nothing rejects an empty Blast radius: Entirely pre-existing — two empty-named refs collided identically before this PR — and outside #1874's scope, so not a defect this PR owns. Noted only because the PR is already adding a name-validity loop. Fix: Optional: make the exemption an outright rejection ( |
||
| continue | ||
| } | ||
| if seen[name] { | ||
| return errors.New(errors.ErrCodeInvalidRequest, | ||
| fmt.Sprintf("recipe has duplicate component ref name %q", name)) | ||
|
Member
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. 🔵 Nitpick — Error names the duplicate but not the colliding positions Fail-fast is right here — it matches the reserved-key loop immediately above, and the split between cheap pre-flight name gates and Blast radius: On a 20-component machine-generated recipe the author can't tell which two entries collided or which one is the stray. Fix: Make |
||
| } | ||
| seen[name] = true | ||
| } | ||
| if err := r.backfillComponentTypes(); err != nil { | ||
| return err | ||
| } | ||
|
|
||
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.
🟡 Minor — Facade
Componentsnarrowing isn't reflected in the integrator docsTo be clear up front, this change is better than advertised:
applyRegistryDefaultspopulatesTypeon disabled refs too, so pre-PRClient.BundleComponentsreturned fully-populated Helm bundles for components that must never deploy — diverging fromDefaultBundler.Make'sfilterEnabledComponents. This is a genuine bug fix. Thetypes.go:271godoc ("lists the deployable components") already supports it, and theBundleComponents"1:1 — same order, same length" promise is intact because bundles derive from the already-filteredr.Components.Blast radius: Docs only, but observable:
recipes/overlays/ocp.yamldisables ~10 componentRefs, so an SDK caller resolving an OCP recipe sees a materially shorterComponentsslice after upgrade.docs/integrator/go-library.md#L276anddocs/integrator/public-api.md:67both describe the field with no enabled-only qualifier.Fix: Add the qualifier to both doc pages — e.g. "
Components— enabled (deployable) components only; disabled refs remain visible viaResolved().ComponentRefs" — and point thetypes.go:271godoc atResolved()for the full set.