From 767d0db885833c457f933b5b39206d9cf32f160d Mon Sep 17 00:00:00 2001
From: Camila Macedo <7708031+camilamacedo86@users.noreply.github.com>
Date: Sun, 2 Nov 2025 13:56:48 +0000
Subject: [PATCH] Add support for custom webhook paths
Users can now set custom paths for webhooks using --defaulting-path and
--validation-path flags. Requires controller-runtime v0.20+.
Assisted-by: Cursor
---
 .../internal/webhook/v1/cronjob_webhook.go    |   2 +
 .../webhook-implementation.md                 |  27 +-
 .../src/multiversion-tutorial/conversion.md   |  10 +
 .../internal/webhook/v1/cronjob_webhook.go    |   2 +
 .../internal/webhook/v2/cronjob_webhook.go    |   3 +-
 docs/book/src/reference/admission-webhook.md  |  31 +++
 .../cronjob-tutorial/generate_cronjob.go      |   7 +-
 .../webhook_implementation.go                 |   4 +-
 pkg/cli/alpha/internal/generate.go            |   7 +
 pkg/model/resource/webhooks.go                |  21 +-
 pkg/plugins/golang/options.go                 |  12 +
 .../internal/templates/webhooks/webhook.go    |  19 +-
 pkg/plugins/golang/v4/webhook.go              |  29 ++
 test/e2e/v4/generate_test.go                  |  64 +++++
 test/e2e/v4/plugin_cluster_test.go            |   4 +
 test/testdata/generate.sh                     |   7 +-
 .../webhook/apps/v1/deployment_webhook.go     |   3 +-
 .../internal/webhook/core/v1/pod_webhook.go   |   3 +-
 .../webhook/crew/v1/captain_webhook.go        |   3 +-
 .../example.com/v1alpha1/memcached_webhook.go |   3 +-
 .../webhook/ship/v2alpha1/cruiser_webhook.go  |   3 +-
 .../webhook/v1alpha1/memcached_webhook.go     |   3 +-
 testdata/project-v4/PROJECT                   |  17 ++
 testdata/project-v4/api/v1/sailor_types.go    |  92 +++++++
 .../api/v1/zz_generated.deepcopy.go           | 101 +++++++
 testdata/project-v4/cmd/main.go               |  14 +
 .../bases/crew.testproject.org_sailors.yaml   | 126 +++++++++
 .../project-v4/config/crd/kustomization.yaml  |   1 +
 .../project-v4/config/rbac/kustomization.yaml |   3 +
 testdata/project-v4/config/rbac/role.yaml     |   3 +
 .../config/rbac/sailor_admin_role.yaml        |  27 ++
 .../config/rbac/sailor_editor_role.yaml       |  33 +++
 .../config/rbac/sailor_viewer_role.yaml       |  29 ++
 .../config/samples/crew_v1_sailor.yaml        |   9 +
 .../config/samples/kustomization.yaml         |   1 +
 .../project-v4/config/webhook/manifests.yaml  |  60 ++++
 testdata/project-v4/dist/install.yaml         | 260 ++++++++++++++++++
 .../internal/controller/sailor_controller.go  |  63 +++++
 .../controller/sailor_controller_test.go      |  84 ++++++
 .../internal/webhook/v1/admiral_webhook.go    |  57 ++++
 .../webhook/v1/admiral_webhook_test.go        |  26 ++
 .../internal/webhook/v1/captain_webhook.go    |   3 +-
 .../internal/webhook/v1/deployment_webhook.go |   3 +-
 .../internal/webhook/v1/sailor_webhook.go     | 127 +++++++++
 .../webhook/v1/sailor_webhook_test.go         |  87 ++++++
 .../internal/webhook/v1/webhook_suite_test.go |   3 +
 46 files changed, 1466 insertions(+), 30 deletions(-)
 create mode 100644 testdata/project-v4/api/v1/sailor_types.go
 create mode 100644 testdata/project-v4/config/crd/bases/crew.testproject.org_sailors.yaml
 create mode 100644 testdata/project-v4/config/rbac/sailor_admin_role.yaml
 create mode 100644 testdata/project-v4/config/rbac/sailor_editor_role.yaml
 create mode 100644 testdata/project-v4/config/rbac/sailor_viewer_role.yaml
 create mode 100644 testdata/project-v4/config/samples/crew_v1_sailor.yaml
 create mode 100644 testdata/project-v4/internal/controller/sailor_controller.go
 create mode 100644 testdata/project-v4/internal/controller/sailor_controller_test.go
 create mode 100644 testdata/project-v4/internal/webhook/v1/sailor_webhook.go
 create mode 100644 testdata/project-v4/internal/webhook/v1/sailor_webhook_test.go
diff --git a/docs/book/src/cronjob-tutorial/testdata/project/internal/webhook/v1/cronjob_webhook.go b/docs/book/src/cronjob-tutorial/testdata/project/internal/webhook/v1/cronjob_webhook.go
index 1f58ce66db3..f923176b2e3 100644
--- a/docs/book/src/cronjob-tutorial/testdata/project/internal/webhook/v1/cronjob_webhook.go
+++ b/docs/book/src/cronjob-tutorial/testdata/project/internal/webhook/v1/cronjob_webhook.go
@@ -155,6 +155,8 @@ validate anything on deletion.
 /*
 This marker is responsible for generating a validation webhook manifest.
 */
+
+// NOTE: If you want to customise the 'path', use the flags '--defaulting-path' or '--validation-path'.
 // +kubebuilder:webhook:path=/validate-batch-tutorial-kubebuilder-io-v1-cronjob,mutating=false,failurePolicy=fail,sideEffects=None,groups=batch.tutorial.kubebuilder.io,resources=cronjobs,verbs=create;update,versions=v1,name=vcronjob-v1.kb.io,admissionReviewVersions=v1
 
 // CronJobCustomValidator struct is responsible for validating the CronJob resource
diff --git a/docs/book/src/cronjob-tutorial/webhook-implementation.md b/docs/book/src/cronjob-tutorial/webhook-implementation.md
index 423f27f73b1..ea4b4375a42 100644
--- a/docs/book/src/cronjob-tutorial/webhook-implementation.md
+++ b/docs/book/src/cronjob-tutorial/webhook-implementation.md
@@ -11,7 +11,7 @@ Kubebuilder takes care of the rest for you, such as
 1. Creating handlers for your webhooks.
 1. Registering each handler with a path in your server.
 
-First, let's scaffold the webhooks for our CRD (CronJob). We’ll need to run the following command with the `--defaulting` and `--programmatic-validation` flags (since our test project will use defaulting and validating webhooks):
+First, let's scaffold the webhooks for our CRD (CronJob). We'll need to run the following command with the `--defaulting` and `--programmatic-validation` flags (since our test project will use defaulting and validating webhooks):
 
 ```bash
 kubebuilder create webhook --group batch --version v1 --kind CronJob --defaulting --programmatic-validation
@@ -19,4 +19,29 @@ kubebuilder create webhook --group batch --version v1 --kind CronJob --defaultin
 
 This will scaffold the webhook functions and register your webhook with the manager in your `main.go` for you.
 
+## Custom Webhook Paths
+
+You can specify custom HTTP paths for your webhooks using the `--defaulting-path` and `--validation-path` flags:
+
+```bash
+# Custom path for defaulting webhook
+kubebuilder create webhook --group batch --version v1 --kind CronJob --defaulting --defaulting-path=/my-custom-mutate-path
+
+# Custom path for validation webhook
+kubebuilder create webhook --group batch --version v1 --kind CronJob --programmatic-validation --validation-path=/my-custom-validate-path
+
+# Both webhooks with different custom paths
+kubebuilder create webhook --group batch --version v1 --kind CronJob --defaulting --programmatic-validation \
+  --defaulting-path=/custom-mutate --validation-path=/custom-validate
+```
+
+This changes the path in the webhook marker annotation but does not change where the webhook files are scaffolded. The webhook files will still be created in `internal/webhook/v1/`.
+
+
+
 {{#literatego ./testdata/project/internal/webhook/v1/cronjob_webhook.go}}
diff --git a/docs/book/src/multiversion-tutorial/conversion.md b/docs/book/src/multiversion-tutorial/conversion.md
index 39b37a907b2..4166992e419 100644
--- a/docs/book/src/multiversion-tutorial/conversion.md
+++ b/docs/book/src/multiversion-tutorial/conversion.md
@@ -13,6 +13,16 @@ The above command will generate the `cronjob_conversion.go` next to our
 `cronjob_types.go` file, to avoid
 cluttering up our main types file with extra functions.
 
+
+
 ## Hub...
 
 First, we'll implement the hub.  We'll choose the v1 version as the hub:
diff --git a/docs/book/src/multiversion-tutorial/testdata/project/internal/webhook/v1/cronjob_webhook.go b/docs/book/src/multiversion-tutorial/testdata/project/internal/webhook/v1/cronjob_webhook.go
index 2a076ab4f23..7f5224e5581 100644
--- a/docs/book/src/multiversion-tutorial/testdata/project/internal/webhook/v1/cronjob_webhook.go
+++ b/docs/book/src/multiversion-tutorial/testdata/project/internal/webhook/v1/cronjob_webhook.go
@@ -160,6 +160,8 @@ validate anything on deletion.
 /*
 This marker is responsible for generating a validation webhook manifest.
 */
+
+// NOTE: If you want to customise the 'path', use the flags '--defaulting-path' or '--validation-path'.
 // +kubebuilder:webhook:path=/validate-batch-tutorial-kubebuilder-io-v1-cronjob,mutating=false,failurePolicy=fail,sideEffects=None,groups=batch.tutorial.kubebuilder.io,resources=cronjobs,verbs=create;update,versions=v1,name=vcronjob-v1.kb.io,admissionReviewVersions=v1
 
 // CronJobCustomValidator struct is responsible for validating the CronJob resource
diff --git a/docs/book/src/multiversion-tutorial/testdata/project/internal/webhook/v2/cronjob_webhook.go b/docs/book/src/multiversion-tutorial/testdata/project/internal/webhook/v2/cronjob_webhook.go
index ab1d46b2f79..9d6e3d48d71 100644
--- a/docs/book/src/multiversion-tutorial/testdata/project/internal/webhook/v2/cronjob_webhook.go
+++ b/docs/book/src/multiversion-tutorial/testdata/project/internal/webhook/v2/cronjob_webhook.go
@@ -88,8 +88,7 @@ func (d *CronJobCustomDefaulter) Default(_ context.Context, obj runtime.Object)
 }
 
 // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
-// NOTE: The 'path' attribute must follow a specific pattern and should not be modified directly here.
-// Modifying the path for an invalid path can cause API server errors; failing to locate the webhook.
+// NOTE: If you want to customise the 'path', use the flags '--defaulting-path' or '--validation-path'.
 // +kubebuilder:webhook:path=/validate-batch-tutorial-kubebuilder-io-v2-cronjob,mutating=false,failurePolicy=fail,sideEffects=None,groups=batch.tutorial.kubebuilder.io,resources=cronjobs,verbs=create;update,versions=v2,name=vcronjob-v2.kb.io,admissionReviewVersions=v1
 
 // CronJobCustomValidator struct is responsible for validating the CronJob resource
diff --git a/docs/book/src/reference/admission-webhook.md b/docs/book/src/reference/admission-webhook.md
index f37e4d47bc3..222d49f7437 100644
--- a/docs/book/src/reference/admission-webhook.md
+++ b/docs/book/src/reference/admission-webhook.md
@@ -30,6 +30,37 @@ object after your validation has accepted it.
 
 
 
+## Custom Webhook Paths
+
+By default, Kubebuilder generates webhook paths based on the resource's group, version, and kind. For example:
+- Mutating webhook for `batch/v1/CronJob`: `/mutate-batch-v1-cronjob`
+- Validating webhook for `batch/v1/CronJob`: `/validate-batch-v1-cronjob`
+
+You can specify custom paths for webhooks using dedicated flags:
+
+```bash
+# Custom path for defaulting webhook
+kubebuilder create webhook --group batch --version v1 --kind CronJob \
+  --defaulting --defaulting-path=/my-custom-mutate-path
+
+# Custom path for validation webhook
+kubebuilder create webhook --group batch --version v1 --kind CronJob \
+  --programmatic-validation --validation-path=/my-custom-validate-path
+
+# Both webhooks with different custom paths
+kubebuilder create webhook --group batch --version v1 --kind CronJob \
+  --defaulting --programmatic-validation \
+  --defaulting-path=/custom-mutate --validation-path=/custom-validate
+```
+
+
+
+
 ## Handling Resource Status in Admission Webhooks