Skip to content

🐛 fix(go/v4): resolve create webhook target when several share a GVK - #5932

Open
prash2512 wants to merge 1 commit into
kubernetes-sigs:masterfrom
prash2512:fix-webhook-ambiguous-resource
Open

🐛 fix(go/v4): resolve create webhook target when several share a GVK#5932
prash2512 wants to merge 1 commit into
kubernetes-sigs:masterfrom
prash2512:fix-webhook-ambiguous-resource

Conversation

@prash2512

@prash2512 prash2512 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

create webhook matched a recorded resource by Group, Version and Kind and took the first hit. When a project records more than one external resource that shares a GVK under different domains, it silently bound the webhook to whichever entry the PROJECT file listed first, and passing --external-api-domain to select another one failed outright.

Collect every Group/Version/Kind match instead of taking the first. With a single match, behave as before. With several, use --external-api-domain to select one, and return an error when it is missing or matches none so the command never guesses and leaves the PROJECT file unchanged.

Fixes #5931

Change

updateResourceFromConfig now collects every Group/Version/Kind match instead of taking the first:

  • one match → behaves as before (recovers the recorded config);
  • several matches → uses --external-api-domain to select one, and returns an
    error when it is missing or matches none — so the command never guesses and
    leaves the PROJECT file unchanged.

create api is unaffected; it already looks resources up by the full GVK.

Tests

Unit specs in webhook_test.go:

  • ambiguous command (no domain) → refuses, both entries keep their path/domain;
  • same scenario with entries in reverse order → still refuses (order-independent);
  • naming one with --external-api-domain → works on that resource, leaves the other alone;
  • --external-api-domain matching none → refuses;
  • single match still works (existing coverage).

@kubernetes-prow kubernetes-prow Bot added the do-not-merge/invalid-commit-message Indicates that a PR should not merge because it has an invalid commit message. label Jul 31, 2026
@kubernetes-prow kubernetes-prow Bot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Jul 31, 2026
@kubernetes-prow

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: prash2512
Once this PR has been reviewed and has the lgtm label, please assign varshaprasad96 for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kubernetes-prow kubernetes-prow Bot added the needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. label Jul 31, 2026
@kubernetes-prow

Copy link
Copy Markdown
Contributor

Hi @prash2512. Thanks for your PR.

I'm waiting for a kubernetes-sigs member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work.

Tip

We noticed you've done this a few times! Consider joining the org to skip this step and gain /lgtm and other bot rights. We recommend asking approvers on your previous PRs to sponsor you.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@kubernetes-prow kubernetes-prow Bot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Jul 31, 2026
@prash2512
prash2512 force-pushed the fix-webhook-ambiguous-resource branch from f148488 to d039a51 Compare July 31, 2026 07:03
@kubernetes-prow kubernetes-prow Bot removed the do-not-merge/invalid-commit-message Indicates that a PR should not merge because it has an invalid commit message. label Jul 31, 2026
@prash2512
prash2512 force-pushed the fix-webhook-ambiguous-resource branch 2 times, most recently from 9f285a7 to 0dfbd2d Compare July 31, 2026 07:32
@camilamacedo86
camilamacedo86 requested a review from Copilot August 1, 2026 06:59
Comment thread pkg/plugins/golang/v4/webhook.go Outdated
if !found {
return fmt.Errorf(
"group %q, version %q and kind %q match more than one resource: "+
"pass --external-api-domain to choose the one to work on",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

we should only return it for external types

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch!!
I missed to handle the fallback case to core type when external domain is not passed. Fixed in latest commit.


Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("match more than one resource"))
})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Have we coverage that it works well without pass the domain when is a core type?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Covered it in latest update

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

This PR fixes kubebuilder create webhook selecting the wrong recorded external resource when multiple entries share the same Group/Version/Kind under different domains, by refusing ambiguous matches and requiring --external-api-domain to select the intended resource.

Changes:

  • Update updateResourceFromConfig to collect all Group/Version/Kind matches and require --external-api-domain when ambiguous.
  • Add unit tests to cover ambiguous selection, order-independence, correct selection by domain, and no-match domain refusal.

Reviewed changes

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

File Description
pkg/plugins/golang/v4/webhook.go Updates resource lookup to avoid “first match wins” and require explicit domain selection when multiple external resources share a G/V/K.
pkg/plugins/golang/v4/webhook_test.go Adds unit coverage for multi-match ambiguity and selection by --external-api-domain.

Comment thread pkg/plugins/golang/v4/webhook.go Outdated
Comment thread pkg/plugins/golang/v4/webhook.go Outdated
@prash2512
prash2512 force-pushed the fix-webhook-ambiguous-resource branch from 0dfbd2d to ee64539 Compare August 1, 2026 18:34
@prash2512

Copy link
Copy Markdown
Contributor Author

Hi @camilamacedo86

Thanks for the review!

This is the gist of the logic, please check if it matches your thoughts on the fix.

Which recorded resource a create webhook refers to is resolved by matching on Group/Version/Kind, then:

  • no match → nothing recorded; res keeps the flag values.
  • one match → use it (nothing to disambiguate with a single match, and it's the path that records a second external variant).
  • several matches → only external APIs can share a G/V/K across domains, so:
    • with --external-api-domain → the entry carrying that domain (error if none matches),
    • without it → the non-external (core/project) entry, refusing only when every match is external.

Errors list the recorded domains and distinguish "no domain given" from "domain given but no match".

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 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (1)

pkg/plugins/golang/v4/webhook.go:297

  • The ambiguous-resource error lists recorded domains without quoting them. If a recorded resource has an empty domain, the message becomes hard to read (e.g., domains: , cert-manager.io) and it’s unclear that one of the candidates is the empty string. Quoting each domain makes the output unambiguous and consistent with the rest of the message’s %q formatting.
			domains := make([]string, len(matches))
			for i, m := range matches {
				domains[i] = m.Domain
			}

Comment thread pkg/plugins/golang/v4/webhook.go Outdated
} else {
// No domain named: use the non-external (core/project) entry, if any.
for _, m := range matches {
if !m.External {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Core resources also have External == false. A recorded core
apps//v1/Deployment and project API apps.example.com/v1/Deployment can
therefore both match. This loop again chooses whichever appears first in
PROJECT, preserving the order-dependent wrong-resource bug. Prefer the match
whose domain equals res.Domain, or require the non-external match to be
unique.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed. It no longer takes the first non-external match - on a project+core collision it keeps the one whose domain matches res.Domain (the project's), so it's order-independent. It only errors when every match is external.

Comment thread pkg/plugins/golang/v4/webhook.go Outdated
for i, m := range matches {
domains[i] = m.Domain
}
if domain != "" {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Once two external resources share a G/V/K, a valid command supplying a new
--external-api-domain and --external-api-path is rejected because the domain
is not already recorded. This makes adding the second variant work but
prevents adding a third. An unmatched domain should only be rejected when no
external path was supplied; otherwise the flags fully describe a new
resource.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point - fixed. An unmatched --external-api-domain is only rejected when there's no --external-api-path; with a path the flags describe a new resource, so it proceeds. Adding a third variant works now.

res = &resValue
if err != nil {
if !p.resource.External && !p.resource.Core {
return fmt.Errorf(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

--external-api-domain now selects among recorded resources, but its help
text only mentions RBAC generation. Suggested wording: “Domain for the
external API (e.g., cert-manager.io); selects the recorded resource when
multiple resources share a group, version, and kind, and is used to generate
RBAC markers.”

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

updated

…e a GVK

create webhook matched a recorded resource by Group, Version and Kind and took
the first hit. When a project records more than one external resource sharing a
GVK under different domains, the webhook was bound to whichever entry the PROJECT
file listed first, and passing --external-api-domain to select another failed
outright.

updateResourceFromConfig now collects every Group/Version/Kind match: a single
match is recovered as before; with several, --external-api-domain selects one,
otherwise the non-external (core/project) entry is used, preferring the project
when a core and project entry collide; it refuses and names the recorded domains
when it still cannot tell which was meant, leaving the PROJECT file unchanged. An
unmatched --external-api-domain is only rejected when no --external-api-path is
given, so a further variant can still be added.

create api is unaffected; it already looks resources up by the full GVK.
@prash2512
prash2512 force-pushed the fix-webhook-ambiguous-resource branch from ac3800f to 8b27fcd Compare August 2, 2026 21:44
@prash2512

Copy link
Copy Markdown
Contributor Author

Hi @camilamacedo86

Thank you for the detailed review! All three comments addressed.

Additionally tightened two related things while here

  • single match - a lone recorded resource whose domain didn't match a passed --external-api-domain used to be picked silently; it now errors the same way unless a path defines a new one.
  • spoke validation (isValidVersion) matched on the short group, so a version of an unrelated type sharing the short group + kind under another domain could be accepted as a spoke; now matched on the qualified group (group + domain). Kept as a separate commit, happy to pull it into its own PR if you'd prefer.

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 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (1)

pkg/plugins/golang/v4/webhook.go:267

  • Spoke-version validation runs before options.UpdateResource applies --external-api-domain to the resource (that only happens when --external-api-path is set). With the new isValidVersion matching on QualifiedGroup (group+domain), this can validate spokes against the project domain for a new external resource (no PROJECT record yet), and then later change res.Domain to the external domain—accepting spokes that don't exist for the intended API.
	domain := p.options.ExternalAPIDomain
	var selected *resource.Resource

@camilamacedo86

Copy link
Copy Markdown
Member

/ok-to-test

@kubernetes-prow kubernetes-prow Bot added ok-to-test Indicates a non-member PR verified by an org member that is safe to test. and removed needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Aug 4, 2026
@camilamacedo86
camilamacedo86 requested a lite review from Copilot August 4, 2026 18:41

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 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (2)

pkg/plugins/golang/v4/webhook.go:120

  • The --external-api-domain help text diverges from the wording used for the same flag in create api (pkg/plugins/golang/v4/api.go:120-122), which can make --help output feel inconsistent across subcommands. Consider keeping the shared first sentence consistent and appending the create-webhook-specific behavior.
	fs.StringVar(&p.options.ExternalAPIDomain, "external-api-domain", "",
		"Domain for the external API (e.g., cert-manager.io). Selects the recorded resource when "+
			"several share a group, version, and kind, and is used to generate accurate RBAC markers "+
			"and permissions for the external resources")

pkg/plugins/golang/v4/webhook.go:350

  • resolutionError builds the domain list without quoting, which can make the message ambiguous when a recorded domain is empty (e.g. it renders as a leading/trailing comma with no obvious "empty" entry). Quoting the domains makes the error actionable in that edge case.
	domains := make([]string, len(candidates))
	for i, c := range candidates {
		domains[i] = c.Domain
	}

@prash2512
prash2512 force-pushed the fix-webhook-ambiguous-resource branch from 8b27fcd to 3c9c9d6 Compare August 4, 2026 22:55
@prash2512

Copy link
Copy Markdown
Contributor Author

/test pull-kubebuilder-test

@prash2512

Copy link
Copy Markdown
Contributor Author

Hi @camilamacedo86
Can I get a follow up review for this ?

@prash2512

Copy link
Copy Markdown
Contributor Author

Hi @camilamacedo86
Can you help with follow up review for this pr?
Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. ok-to-test Indicates a non-member PR verified by an org member that is safe to test. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

create webhook works on the wrong resource when several match the group, version and kind

3 participants