Skip to content

Commit 02653c3

Browse files
authored
Merge pull request #4301 from damsien/fix-webhook-core-path
🐛 (go/v4): Fix path configuration for webhook markers generated for core types with non-"core" group values
2 parents 3ccda32 + 6a46e63 commit 02653c3

File tree

16 files changed

+771
-2
lines changed

16 files changed

+771
-2
lines changed

pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ func Setup{{ .Resource.Kind }}WebhookWithManager(mgr ctrl.Manager) error {
158158

159159
//nolint:lll
160160
defaultingWebhookTemplate = `
161-
// +kubebuilder:webhook:{{ if ne .Resource.Webhooks.WebhookVersion "v1" }}webhookVersions={{"{"}}{{ .Resource.Webhooks.WebhookVersion }}{{"}"}},{{ end }}path=/mutate-{{ if .Resource.Core }}-{{ .Resource.Version }}-{{ lower .Resource.Kind }}{{ else }}{{ .QualifiedGroupWithDash }}-{{ .Resource.Version }}-{{ lower .Resource.Kind }}{{ end }},mutating=true,failurePolicy=fail,sideEffects=None,groups={{ if .Resource.Core }}""{{ else }}{{ .Resource.QualifiedGroup }}{{ end }},resources={{ .Resource.Plural }},verbs=create;update,versions={{ .Resource.Version }},name=m{{ lower .Resource.Kind }}-{{ .Resource.Version }}.kb.io,admissionReviewVersions={{ .AdmissionReviewVersions }}
161+
// +kubebuilder:webhook:{{ if ne .Resource.Webhooks.WebhookVersion "v1" }}webhookVersions={{"{"}}{{ .Resource.Webhooks.WebhookVersion }}{{"}"}},{{ end }}path=/mutate-{{ if and .Resource.Core (eq .Resource.QualifiedGroup "core") }}-{{ else }}{{ .QualifiedGroupWithDash }}-{{ end }}{{ .Resource.Version }}-{{ lower .Resource.Kind }},mutating=true,failurePolicy=fail,sideEffects=None,groups={{ if and .Resource.Core (eq .Resource.QualifiedGroup "core") }}""{{ else }}{{ .Resource.QualifiedGroup }}{{ end }},resources={{ .Resource.Plural }},verbs=create;update,versions={{ .Resource.Version }},name=m{{ lower .Resource.Kind }}-{{ .Resource.Version }}.kb.io,admissionReviewVersions={{ .AdmissionReviewVersions }}
162162
163163
{{ if .IsLegacyPath -}}
164164
// +kubebuilder:object:generate=false
@@ -198,7 +198,7 @@ func (d *{{ .Resource.Kind }}CustomDefaulter) Default(ctx context.Context, obj r
198198
// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
199199
// NOTE: The 'path' attribute must follow a specific pattern and should not be modified directly here.
200200
// Modifying the path for an invalid path can cause API server errors; failing to locate the webhook.
201-
// +kubebuilder:webhook:{{ if ne .Resource.Webhooks.WebhookVersion "v1" }}webhookVersions={{"{"}}{{ .Resource.Webhooks.WebhookVersion }}{{"}"}},{{ end }}path=/validate-{{ if .Resource.Core }}-{{ .Resource.Version }}-{{ lower .Resource.Kind }}{{ else }}{{ .QualifiedGroupWithDash }}-{{ .Resource.Version }}-{{ lower .Resource.Kind }}{{ end }},mutating=false,failurePolicy=fail,sideEffects=None,groups={{ if .Resource.Core }}""{{ else }}{{ .Resource.QualifiedGroup }}{{ end }},resources={{ .Resource.Plural }},verbs=create;update,versions={{ .Resource.Version }},name=v{{ lower .Resource.Kind }}-{{ .Resource.Version }}.kb.io,admissionReviewVersions={{ .AdmissionReviewVersions }}
201+
// +kubebuilder:webhook:{{ if ne .Resource.Webhooks.WebhookVersion "v1" }}webhookVersions={{"{"}}{{ .Resource.Webhooks.WebhookVersion }}{{"}"}},{{ end }}path=/validate-{{ if and .Resource.Core (eq .Resource.QualifiedGroup "core") }}-{{ else }}{{ .QualifiedGroupWithDash }}-{{ end }}{{ .Resource.Version }}-{{ lower .Resource.Kind }},mutating=false,failurePolicy=fail,sideEffects=None,groups={{ if and .Resource.Core (eq .Resource.QualifiedGroup "core") }}""{{ else }}{{ .Resource.QualifiedGroup }}{{ end }},resources={{ .Resource.Plural }},verbs=create;update,versions={{ .Resource.Version }},name=v{{ lower .Resource.Kind }}-{{ .Resource.Version }}.kb.io,admissionReviewVersions={{ .AdmissionReviewVersions }}
202202
203203
{{ if .IsLegacyPath -}}
204204
// +kubebuilder:object:generate=false

test/testdata/generate.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ function scaffold_test_project {
5959
$kb create webhook --group "cert-manager" --version v1 --kind Issuer --defaulting --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 --external-api-domain=io
6060
# Webhook for Core type
6161
$kb create webhook --group core --version v1 --kind Pod --defaulting
62+
# Webhook for kubernetes Core type that is part of an api group
63+
$kb create webhook --group apps --version v1 --kind Deployment --defaulting --programmatic-validation
6264
fi
6365

6466
if [[ $project =~ multigroup ]]; then
@@ -88,6 +90,8 @@ function scaffold_test_project {
8890
$kb create webhook --group "cert-manager" --version v1 --kind Issuer --defaulting --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 --external-api-domain=io
8991
# Webhook for Core type
9092
$kb create webhook --group core --version v1 --kind Pod --programmatic-validation --make=false
93+
# Webhook for kubernetes Core type that is part of an api group
94+
$kb create webhook --group apps --version v1 --kind Deployment --defaulting --programmatic-validation --make=false
9195
fi
9296

9397
if [[ $project =~ multigroup ]] || [[ $project =~ with-plugins ]] ; then

testdata/project-v4-multigroup/PROJECT

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ resources:
105105
kind: Deployment
106106
path: k8s.io/api/apps/v1
107107
version: v1
108+
webhooks:
109+
defaulting: true
110+
validation: true
111+
webhookVersion: v1
108112
- api:
109113
crdVersion: v1
110114
namespaced: true

testdata/project-v4-multigroup/cmd/main.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import (
5858
foopolicycontroller "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/foo.policy"
5959
seacreaturescontroller "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/sea-creatures"
6060
shipcontroller "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/ship"
61+
webhookappsv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/apps/v1"
6162
webhookcertmanagerv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/cert-manager/v1"
6263
webhookcorev1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/core/v1"
6364
webhookcrewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/crew/v1"
@@ -294,6 +295,13 @@ func main() {
294295
os.Exit(1)
295296
}
296297
}
298+
// nolint:goconst
299+
if os.Getenv("ENABLE_WEBHOOKS") != "false" {
300+
if err = webhookappsv1.SetupDeploymentWebhookWithManager(mgr); err != nil {
301+
setupLog.Error(err, "unable to create webhook", "webhook", "Deployment")
302+
os.Exit(1)
303+
}
304+
}
297305
if err = (&examplecomcontroller.MemcachedReconciler{
298306
Client: mgr.GetClient(),
299307
Scheme: mgr.GetScheme(),

testdata/project-v4-multigroup/config/webhook/manifests.yaml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,26 @@ kind: MutatingWebhookConfiguration
44
metadata:
55
name: mutating-webhook-configuration
66
webhooks:
7+
- admissionReviewVersions:
8+
- v1
9+
clientConfig:
10+
service:
11+
name: webhook-service
12+
namespace: system
13+
path: /mutate-apps-v1-deployment
14+
failurePolicy: Fail
15+
name: mdeployment-v1.kb.io
16+
rules:
17+
- apiGroups:
18+
- apps
19+
apiVersions:
20+
- v1
21+
operations:
22+
- CREATE
23+
- UPDATE
24+
resources:
25+
- deployments
26+
sideEffects: None
727
- admissionReviewVersions:
828
- v1
929
clientConfig:
@@ -70,6 +90,26 @@ kind: ValidatingWebhookConfiguration
7090
metadata:
7191
name: validating-webhook-configuration
7292
webhooks:
93+
- admissionReviewVersions:
94+
- v1
95+
clientConfig:
96+
service:
97+
name: webhook-service
98+
namespace: system
99+
path: /validate-apps-v1-deployment
100+
failurePolicy: Fail
101+
name: vdeployment-v1.kb.io
102+
rules:
103+
- apiGroups:
104+
- apps
105+
apiVersions:
106+
- v1
107+
operations:
108+
- CREATE
109+
- UPDATE
110+
resources:
111+
- deployments
112+
sideEffects: None
73113
- admissionReviewVersions:
74114
- v1
75115
clientConfig:

testdata/project-v4-multigroup/dist/install.yaml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1919,6 +1919,26 @@ kind: MutatingWebhookConfiguration
19191919
metadata:
19201920
name: project-v4-multigroup-mutating-webhook-configuration
19211921
webhooks:
1922+
- admissionReviewVersions:
1923+
- v1
1924+
clientConfig:
1925+
service:
1926+
name: project-v4-multigroup-webhook-service
1927+
namespace: project-v4-multigroup-system
1928+
path: /mutate-apps-v1-deployment
1929+
failurePolicy: Fail
1930+
name: mdeployment-v1.kb.io
1931+
rules:
1932+
- apiGroups:
1933+
- apps
1934+
apiVersions:
1935+
- v1
1936+
operations:
1937+
- CREATE
1938+
- UPDATE
1939+
resources:
1940+
- deployments
1941+
sideEffects: None
19221942
- admissionReviewVersions:
19231943
- v1
19241944
clientConfig:
@@ -1985,6 +2005,26 @@ kind: ValidatingWebhookConfiguration
19852005
metadata:
19862006
name: project-v4-multigroup-validating-webhook-configuration
19872007
webhooks:
2008+
- admissionReviewVersions:
2009+
- v1
2010+
clientConfig:
2011+
service:
2012+
name: project-v4-multigroup-webhook-service
2013+
namespace: project-v4-multigroup-system
2014+
path: /validate-apps-v1-deployment
2015+
failurePolicy: Fail
2016+
name: vdeployment-v1.kb.io
2017+
rules:
2018+
- apiGroups:
2019+
- apps
2020+
apiVersions:
2021+
- v1
2022+
operations:
2023+
- CREATE
2024+
- UPDATE
2025+
resources:
2026+
- deployments
2027+
sideEffects: None
19882028
- admissionReviewVersions:
19892029
- v1
19902030
clientConfig:
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
Copyright 2024 The Kubernetes authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1
18+
19+
import (
20+
"context"
21+
"fmt"
22+
23+
appsv1 "k8s.io/api/apps/v1"
24+
"k8s.io/apimachinery/pkg/runtime"
25+
ctrl "sigs.k8s.io/controller-runtime"
26+
logf "sigs.k8s.io/controller-runtime/pkg/log"
27+
"sigs.k8s.io/controller-runtime/pkg/webhook"
28+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
29+
)
30+
31+
// nolint:unused
32+
// log is for logging in this package.
33+
var deploymentlog = logf.Log.WithName("deployment-resource")
34+
35+
// SetupDeploymentWebhookWithManager registers the webhook for Deployment in the manager.
36+
func SetupDeploymentWebhookWithManager(mgr ctrl.Manager) error {
37+
return ctrl.NewWebhookManagedBy(mgr).For(&appsv1.Deployment{}).
38+
WithValidator(&DeploymentCustomValidator{}).
39+
WithDefaulter(&DeploymentCustomDefaulter{}).
40+
Complete()
41+
}
42+
43+
// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
44+
45+
// +kubebuilder:webhook:path=/mutate-apps-v1-deployment,mutating=true,failurePolicy=fail,sideEffects=None,groups=apps,resources=deployments,verbs=create;update,versions=v1,name=mdeployment-v1.kb.io,admissionReviewVersions=v1
46+
47+
// DeploymentCustomDefaulter struct is responsible for setting default values on the custom resource of the
48+
// Kind Deployment when those are created or updated.
49+
//
50+
// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,
51+
// as it is used only for temporary operations and does not need to be deeply copied.
52+
type DeploymentCustomDefaulter struct {
53+
// TODO(user): Add more fields as needed for defaulting
54+
}
55+
56+
var _ webhook.CustomDefaulter = &DeploymentCustomDefaulter{}
57+
58+
// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Deployment.
59+
func (d *DeploymentCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error {
60+
deployment, ok := obj.(*appsv1.Deployment)
61+
62+
if !ok {
63+
return fmt.Errorf("expected an Deployment object but got %T", obj)
64+
}
65+
deploymentlog.Info("Defaulting for Deployment", "name", deployment.GetName())
66+
67+
// TODO(user): fill in your defaulting logic.
68+
69+
return nil
70+
}
71+
72+
// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
73+
// NOTE: The 'path' attribute must follow a specific pattern and should not be modified directly here.
74+
// Modifying the path for an invalid path can cause API server errors; failing to locate the webhook.
75+
// +kubebuilder:webhook:path=/validate-apps-v1-deployment,mutating=false,failurePolicy=fail,sideEffects=None,groups=apps,resources=deployments,verbs=create;update,versions=v1,name=vdeployment-v1.kb.io,admissionReviewVersions=v1
76+
77+
// DeploymentCustomValidator struct is responsible for validating the Deployment resource
78+
// when it is created, updated, or deleted.
79+
//
80+
// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,
81+
// as this struct is used only for temporary operations and does not need to be deeply copied.
82+
type DeploymentCustomValidator struct {
83+
//TODO(user): Add more fields as needed for validation
84+
}
85+
86+
var _ webhook.CustomValidator = &DeploymentCustomValidator{}
87+
88+
// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Deployment.
89+
func (v *DeploymentCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
90+
deployment, ok := obj.(*appsv1.Deployment)
91+
if !ok {
92+
return nil, fmt.Errorf("expected a Deployment object but got %T", obj)
93+
}
94+
deploymentlog.Info("Validation for Deployment upon creation", "name", deployment.GetName())
95+
96+
// TODO(user): fill in your validation logic upon object creation.
97+
98+
return nil, nil
99+
}
100+
101+
// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Deployment.
102+
func (v *DeploymentCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
103+
deployment, ok := newObj.(*appsv1.Deployment)
104+
if !ok {
105+
return nil, fmt.Errorf("expected a Deployment object for the newObj but got %T", newObj)
106+
}
107+
deploymentlog.Info("Validation for Deployment upon update", "name", deployment.GetName())
108+
109+
// TODO(user): fill in your validation logic upon object update.
110+
111+
return nil, nil
112+
}
113+
114+
// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type Deployment.
115+
func (v *DeploymentCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
116+
deployment, ok := obj.(*appsv1.Deployment)
117+
if !ok {
118+
return nil, fmt.Errorf("expected a Deployment object but got %T", obj)
119+
}
120+
deploymentlog.Info("Validation for Deployment upon deletion", "name", deployment.GetName())
121+
122+
// TODO(user): fill in your validation logic upon object deletion.
123+
124+
return nil, nil
125+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
Copyright 2024 The Kubernetes authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1
18+
19+
import (
20+
. "github.com/onsi/ginkgo/v2"
21+
. "github.com/onsi/gomega"
22+
23+
appsv1 "k8s.io/api/apps/v1"
24+
// TODO (user): Add any additional imports if needed
25+
)
26+
27+
var _ = Describe("Deployment Webhook", func() {
28+
var (
29+
obj *appsv1.Deployment
30+
oldObj *appsv1.Deployment
31+
validator DeploymentCustomValidator
32+
defaulter DeploymentCustomDefaulter
33+
)
34+
35+
BeforeEach(func() {
36+
obj = &appsv1.Deployment{}
37+
oldObj = &appsv1.Deployment{}
38+
validator = DeploymentCustomValidator{}
39+
Expect(validator).NotTo(BeNil(), "Expected validator to be initialized")
40+
defaulter = DeploymentCustomDefaulter{}
41+
Expect(defaulter).NotTo(BeNil(), "Expected defaulter to be initialized")
42+
Expect(oldObj).NotTo(BeNil(), "Expected oldObj to be initialized")
43+
Expect(obj).NotTo(BeNil(), "Expected obj to be initialized")
44+
// TODO (user): Add any setup logic common to all tests
45+
})
46+
47+
AfterEach(func() {
48+
// TODO (user): Add any teardown logic common to all tests
49+
})
50+
51+
Context("When creating Deployment under Defaulting Webhook", func() {
52+
// TODO (user): Add logic for defaulting webhooks
53+
// Example:
54+
// It("Should apply defaults when a required field is empty", func() {
55+
// By("simulating a scenario where defaults should be applied")
56+
// obj.SomeFieldWithDefault = ""
57+
// By("calling the Default method to apply defaults")
58+
// defaulter.Default(ctx, obj)
59+
// By("checking that the default values are set")
60+
// Expect(obj.SomeFieldWithDefault).To(Equal("default_value"))
61+
// })
62+
})
63+
64+
Context("When creating or updating Deployment under Validating Webhook", func() {
65+
// TODO (user): Add logic for validating webhooks
66+
// Example:
67+
// It("Should deny creation if a required field is missing", func() {
68+
// By("simulating an invalid creation scenario")
69+
// obj.SomeRequiredField = ""
70+
// Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred())
71+
// })
72+
//
73+
// It("Should admit creation if all required fields are present", func() {
74+
// By("simulating an invalid creation scenario")
75+
// obj.SomeRequiredField = "valid_value"
76+
// Expect(validator.ValidateCreate(ctx, obj)).To(BeNil())
77+
// })
78+
//
79+
// It("Should validate updates correctly", func() {
80+
// By("simulating a valid update scenario")
81+
// oldObj.SomeRequiredField = "updated_value"
82+
// obj.SomeRequiredField = "updated_value"
83+
// Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil())
84+
// })
85+
})
86+
87+
})

0 commit comments

Comments
 (0)