Skip to content

Commit 08caf7e

Browse files
Drop remove operations from webhook patches (#3132)
* Drop remove operations from webhook patches that could be caused by unkown fields in the go types Change-Id: Iabc66880aef0227f60ec3deec9109cfb255fe2da * Resync webhook builder Change-Id: I4de1ebbe75a9220466c50381d0e708a1b6d23c8f * Rename Defaulter to LosslessDefaulter Change-Id: I232b1f2cd1a8a0c6b2f2fa7506da4d17d3e3ae55
1 parent 425ece1 commit 08caf7e

File tree

12 files changed

+476
-45
lines changed

12 files changed

+476
-45
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ require (
1717
github.com/spf13/cobra v1.8.1
1818
github.com/spf13/pflag v1.0.5
1919
go.uber.org/zap v1.27.0
20+
gomodules.xyz/jsonpatch/v2 v2.4.0
2021
k8s.io/api v0.31.1
2122
k8s.io/apimachinery v0.31.1
2223
k8s.io/apiserver v0.31.1
@@ -128,7 +129,6 @@ require (
128129
golang.org/x/text v0.17.0 // indirect
129130
golang.org/x/time v0.5.0 // indirect
130131
golang.org/x/tools v0.24.0 // indirect
131-
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
132132
google.golang.org/genproto v0.0.0-20240528184218-531527333157 // indirect
133133
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect
134134
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect

pkg/controller/jobframework/base_webhook.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import (
2424
"k8s.io/klog/v2"
2525
ctrl "sigs.k8s.io/controller-runtime"
2626
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
27+
28+
"sigs.k8s.io/kueue/pkg/controller/jobframework/webhook"
2729
)
2830

2931
// BaseWebhook applies basic defaulting and validation for jobs.
@@ -39,9 +41,9 @@ func DefaultWebhookFactory(job GenericJob, fromObject func(runtime.Object) Gener
3941
ManageJobsWithoutQueueName: options.ManageJobsWithoutQueueName,
4042
FromObject: fromObject,
4143
}
42-
return ctrl.NewWebhookManagedBy(mgr).
44+
return webhook.WebhookManagedBy(mgr).
4345
For(job.Object()).
44-
WithDefaulter(wh).
46+
WithMutationHandler(webhook.WithLosslessDefaulter(mgr.GetScheme(), job.Object(), wh)).
4547
WithValidator(wh).
4648
Complete()
4749
}

pkg/controller/jobframework/noop_webhook.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ import (
2424
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
2525
)
2626

27-
type webhook struct {
27+
type noopWebhook struct {
2828
}
2929

3030
func setupNoopWebhook(mgr ctrl.Manager, apiType runtime.Object) error {
31-
wh := &webhook{}
31+
wh := &noopWebhook{}
3232
return ctrl.NewWebhookManagedBy(mgr).
3333
For(apiType).
3434
WithDefaulter(wh).
@@ -37,21 +37,21 @@ func setupNoopWebhook(mgr ctrl.Manager, apiType runtime.Object) error {
3737
}
3838

3939
// Default implements webhook.CustomDefaulter so a webhook will be registered for the type
40-
func (w *webhook) Default(context.Context, runtime.Object) error {
40+
func (w *noopWebhook) Default(context.Context, runtime.Object) error {
4141
return nil
4242
}
4343

4444
// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type
45-
func (w *webhook) ValidateCreate(context.Context, runtime.Object) (admission.Warnings, error) {
45+
func (w *noopWebhook) ValidateCreate(context.Context, runtime.Object) (admission.Warnings, error) {
4646
return nil, nil
4747
}
4848

4949
// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type
50-
func (w *webhook) ValidateUpdate(context.Context, runtime.Object, runtime.Object) (admission.Warnings, error) {
50+
func (w *noopWebhook) ValidateUpdate(context.Context, runtime.Object, runtime.Object) (admission.Warnings, error) {
5151
return nil, nil
5252
}
5353

5454
// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type
55-
func (w *webhook) ValidateDelete(context.Context, runtime.Object) (admission.Warnings, error) {
55+
func (w *noopWebhook) ValidateDelete(context.Context, runtime.Object) (admission.Warnings, error) {
5656
return nil, nil
5757
}
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
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 webhook
18+
19+
import (
20+
"errors"
21+
"net/http"
22+
"net/url"
23+
"strings"
24+
25+
"github.com/go-logr/logr"
26+
"k8s.io/apimachinery/pkg/runtime"
27+
"k8s.io/apimachinery/pkg/runtime/schema"
28+
"k8s.io/client-go/rest"
29+
"k8s.io/klog/v2"
30+
31+
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
32+
"sigs.k8s.io/controller-runtime/pkg/manager"
33+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
34+
"sigs.k8s.io/controller-runtime/pkg/webhook/conversion"
35+
)
36+
37+
// This code is copied from https://github.com/kubernetes-sigs/controller-runtime/blob/896f6ded750155f9ecfdf4d8e10a26fc3fb78384/pkg/builder/webhook.go
38+
// with one modification to get full control of the construction of patches:
39+
// replacing CustomDefaulter with an admission.Handler.
40+
// TODO(#3137): remove this file
41+
42+
// WebhookBuilder builds a Webhook.
43+
type WebhookBuilder struct {
44+
apiType runtime.Object
45+
mutationHandler admission.Handler
46+
customValidator admission.CustomValidator
47+
gvk schema.GroupVersionKind
48+
mgr manager.Manager
49+
config *rest.Config
50+
recoverPanic *bool
51+
logConstructor func(base logr.Logger, req *admission.Request) logr.Logger
52+
err error
53+
}
54+
55+
// WebhookManagedBy returns a new webhook builder.
56+
func WebhookManagedBy(m manager.Manager) *WebhookBuilder {
57+
return &WebhookBuilder{mgr: m}
58+
}
59+
60+
// TODO(droot): update the GoDoc for conversion.
61+
62+
// For takes a runtime.Object which should be a CR.
63+
// If the given object implements the admission.Defaulter interface, a MutatingWebhook will be wired for this type.
64+
// If the given object implements the admission.Validator interface, a ValidatingWebhook will be wired for this type.
65+
func (blder *WebhookBuilder) For(apiType runtime.Object) *WebhookBuilder {
66+
if blder.apiType != nil {
67+
blder.err = errors.New("For(...) should only be called once, could not assign multiple objects for webhook registration")
68+
}
69+
blder.apiType = apiType
70+
return blder
71+
}
72+
73+
// WithDefaulter takes an admission.CustomDefaulter interface, a MutatingWebhook will be wired for this type.
74+
func (blder *WebhookBuilder) WithMutationHandler(h admission.Handler) *WebhookBuilder {
75+
blder.mutationHandler = h
76+
return blder
77+
}
78+
79+
// WithValidator takes a admission.CustomValidator interface, a ValidatingWebhook will be wired for this type.
80+
func (blder *WebhookBuilder) WithValidator(validator admission.CustomValidator) *WebhookBuilder {
81+
blder.customValidator = validator
82+
return blder
83+
}
84+
85+
// WithLogConstructor overrides the webhook's LogConstructor.
86+
func (blder *WebhookBuilder) WithLogConstructor(logConstructor func(base logr.Logger, req *admission.Request) logr.Logger) *WebhookBuilder {
87+
blder.logConstructor = logConstructor
88+
return blder
89+
}
90+
91+
// RecoverPanic indicates whether panics caused by the webhook should be recovered.
92+
// Defaults to true.
93+
func (blder *WebhookBuilder) RecoverPanic(recoverPanic bool) *WebhookBuilder {
94+
blder.recoverPanic = &recoverPanic
95+
return blder
96+
}
97+
98+
// Complete builds the webhook.
99+
func (blder *WebhookBuilder) Complete() error {
100+
// Set the Config
101+
blder.loadRestConfig()
102+
103+
// Configure the default LogConstructor
104+
blder.setLogConstructor()
105+
106+
// Set the Webhook if needed
107+
return blder.registerWebhooks()
108+
}
109+
110+
func (blder *WebhookBuilder) loadRestConfig() {
111+
if blder.config == nil {
112+
blder.config = blder.mgr.GetConfig()
113+
}
114+
}
115+
116+
func (blder *WebhookBuilder) setLogConstructor() {
117+
if blder.logConstructor == nil {
118+
blder.logConstructor = func(base logr.Logger, req *admission.Request) logr.Logger {
119+
log := base.WithValues(
120+
"webhookGroup", blder.gvk.Group,
121+
"webhookKind", blder.gvk.Kind,
122+
)
123+
if req != nil {
124+
return log.WithValues(
125+
blder.gvk.Kind, klog.KRef(req.Namespace, req.Name),
126+
"namespace", req.Namespace, "name", req.Name,
127+
"resource", req.Resource, "user", req.UserInfo.Username,
128+
"requestID", req.UID,
129+
)
130+
}
131+
return log
132+
}
133+
}
134+
}
135+
136+
func (blder *WebhookBuilder) registerWebhooks() error {
137+
typ, err := blder.getType()
138+
if err != nil {
139+
return err
140+
}
141+
142+
blder.gvk, err = apiutil.GVKForObject(typ, blder.mgr.GetScheme())
143+
if err != nil {
144+
return err
145+
}
146+
147+
// Register webhook(s) for type
148+
blder.registerDefaultingWebhook()
149+
blder.registerValidatingWebhook()
150+
151+
err = blder.registerConversionWebhook()
152+
if err != nil {
153+
return err
154+
}
155+
return blder.err
156+
}
157+
158+
// registerDefaultingWebhook registers a defaulting webhook if necessary.
159+
func (blder *WebhookBuilder) registerDefaultingWebhook() {
160+
mwh := blder.getDefaultingWebhook()
161+
if mwh != nil {
162+
mwh.LogConstructor = blder.logConstructor
163+
path := generateMutatePath(blder.gvk)
164+
165+
// Checking if the path is already registered.
166+
// If so, just skip it.
167+
if !blder.isAlreadyHandled(path) {
168+
log := blder.mgr.GetLogger()
169+
log.Info("Registering a mutating webhook",
170+
"GVK", blder.gvk,
171+
"path", path)
172+
blder.mgr.GetWebhookServer().Register(path, mwh)
173+
}
174+
}
175+
}
176+
177+
func (blder *WebhookBuilder) getDefaultingWebhook() *admission.Webhook {
178+
if handler := blder.mutationHandler; handler != nil {
179+
w := &admission.Webhook{
180+
Handler: handler,
181+
}
182+
if blder.recoverPanic != nil {
183+
w = w.WithRecoverPanic(*blder.recoverPanic)
184+
}
185+
return w
186+
}
187+
return nil
188+
}
189+
190+
// registerValidatingWebhook registers a validating webhook if necessary.
191+
func (blder *WebhookBuilder) registerValidatingWebhook() {
192+
vwh := blder.getValidatingWebhook()
193+
if vwh != nil {
194+
vwh.LogConstructor = blder.logConstructor
195+
path := generateValidatePath(blder.gvk)
196+
197+
// Checking if the path is already registered.
198+
// If so, just skip it.
199+
if !blder.isAlreadyHandled(path) {
200+
log := blder.mgr.GetLogger()
201+
log.Info("Registering a validating webhook",
202+
"GVK", blder.gvk,
203+
"path", path)
204+
blder.mgr.GetWebhookServer().Register(path, vwh)
205+
}
206+
}
207+
}
208+
209+
func (blder *WebhookBuilder) getValidatingWebhook() *admission.Webhook {
210+
if validator := blder.customValidator; validator != nil {
211+
w := admission.WithCustomValidator(blder.mgr.GetScheme(), blder.apiType, validator)
212+
if blder.recoverPanic != nil {
213+
w = w.WithRecoverPanic(*blder.recoverPanic)
214+
}
215+
return w
216+
}
217+
return nil
218+
}
219+
220+
func (blder *WebhookBuilder) registerConversionWebhook() error {
221+
log := blder.mgr.GetLogger()
222+
ok, err := conversion.IsConvertible(blder.mgr.GetScheme(), blder.apiType)
223+
if err != nil {
224+
log.Error(err, "conversion check failed", "GVK", blder.gvk)
225+
return err
226+
}
227+
if ok {
228+
if !blder.isAlreadyHandled("/convert") {
229+
blder.mgr.GetWebhookServer().Register("/convert", conversion.NewWebhookHandler(blder.mgr.GetScheme()))
230+
}
231+
log.Info("Conversion webhook enabled", "GVK", blder.gvk)
232+
}
233+
234+
return nil
235+
}
236+
237+
func (blder *WebhookBuilder) getType() (runtime.Object, error) {
238+
if blder.apiType != nil {
239+
return blder.apiType, nil
240+
}
241+
return nil, errors.New("For() must be called with a valid object")
242+
}
243+
244+
func (blder *WebhookBuilder) isAlreadyHandled(path string) bool {
245+
if blder.mgr.GetWebhookServer().WebhookMux() == nil {
246+
return false
247+
}
248+
h, p := blder.mgr.GetWebhookServer().WebhookMux().Handler(&http.Request{URL: &url.URL{Path: path}})
249+
if p == path && h != nil {
250+
return true
251+
}
252+
return false
253+
}
254+
255+
func generateMutatePath(gvk schema.GroupVersionKind) string {
256+
return "/mutate-" + strings.ReplaceAll(gvk.Group, ".", "-") + "-" +
257+
gvk.Version + "-" + strings.ToLower(gvk.Kind)
258+
}
259+
260+
func generateValidatePath(gvk schema.GroupVersionKind) string {
261+
return "/validate-" + strings.ReplaceAll(gvk.Group, ".", "-") + "-" +
262+
gvk.Version + "-" + strings.ToLower(gvk.Kind)
263+
}

0 commit comments

Comments
 (0)