Skip to content

Commit ebe24da

Browse files
committed
Add DefaulterPreserveUnknownFields
An option stops the defaulter from pruning the fields that are not recognized in the local scheme.
1 parent c30f09c commit ebe24da

File tree

2 files changed

+69
-14
lines changed

2 files changed

+69
-14
lines changed

pkg/webhook/admission/defaulter_custom.go

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,33 @@ type CustomDefaulter interface {
3636
Default(ctx context.Context, obj runtime.Object) error
3737
}
3838

39+
type defaulterOptions struct {
40+
preserveUnknownFields bool
41+
}
42+
43+
type defaulterOption func(*defaulterOptions)
44+
45+
// DefaulterPreserveUnknownFields stops the defaulter from pruning the fields that are not recognized in the local scheme.
46+
func DefaulterPreserveUnknownFields(o *defaulterOptions) {
47+
o.preserveUnknownFields = true
48+
}
49+
3950
// WithCustomDefaulter creates a new Webhook for a CustomDefaulter interface.
40-
func WithCustomDefaulter(scheme *runtime.Scheme, obj runtime.Object, defaulter CustomDefaulter) *Webhook {
51+
func WithCustomDefaulter(scheme *runtime.Scheme, obj runtime.Object, defaulter CustomDefaulter, opts ...defaulterOption) *Webhook {
52+
options := &defaulterOptions{}
53+
for _, o := range opts {
54+
o(options)
55+
}
4156
return &Webhook{
42-
Handler: &defaulterForType{object: obj, defaulter: defaulter, decoder: NewDecoder(scheme)},
57+
Handler: &defaulterForType{object: obj, defaulter: defaulter, decoder: NewDecoder(scheme), preserveUnknownFields: options.preserveUnknownFields},
4358
}
4459
}
4560

4661
type defaulterForType struct {
47-
defaulter CustomDefaulter
48-
object runtime.Object
49-
decoder Decoder
62+
defaulter CustomDefaulter
63+
object runtime.Object
64+
decoder Decoder
65+
preserveUnknownFields bool
5066
}
5167

5268
// Handle handles admission requests.
@@ -79,8 +95,11 @@ func (h *defaulterForType) Handle(ctx context.Context, req Request) Response {
7995
return Errored(http.StatusBadRequest, err)
8096
}
8197

82-
// Keep a copy of the object
83-
originalObj := obj.DeepCopyObject()
98+
// Keep a copy of the object if needed
99+
var originalObj runtime.Object
100+
if h.preserveUnknownFields {
101+
originalObj = obj.DeepCopyObject()
102+
}
84103

85104
// Default the object
86105
if err := h.defaulter.Default(ctx, obj); err != nil {
@@ -96,9 +115,12 @@ func (h *defaulterForType) Handle(ctx context.Context, req Request) Response {
96115
if err != nil {
97116
return Errored(http.StatusInternalServerError, err)
98117
}
99-
handlerResponse := PatchResponseFromRaw(req.Object.Raw, marshalled)
100118

101-
return h.dropSchemeRemovals(handlerResponse, originalObj, req.Object.Raw)
119+
handlerResponse := PatchResponseFromRaw(req.Object.Raw, marshalled)
120+
if h.preserveUnknownFields {
121+
handlerResponse = h.dropSchemeRemovals(handlerResponse, originalObj, req.Object.Raw)
122+
}
123+
return handlerResponse
102124
}
103125

104126
func (h *defaulterForType) dropSchemeRemovals(r Response, original runtime.Object, raw []byte) Response {

pkg/webhook/admission/defaulter_custom_test.go

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import (
2929

3030
var _ = Describe("Defaulter Handler", func() {
3131

32-
It("should not lose unknown fields", func() {
32+
It("should not preserve unknown fields by default", func() {
3333
obj := &TestDefaulter{}
3434
handler := WithCustomDefaulter(admissionScheme, obj, &TestCustomDefaulter{})
3535

@@ -42,17 +42,50 @@ var _ = Describe("Defaulter Handler", func() {
4242
},
4343
})
4444
Expect(resp.Allowed).Should(BeTrue())
45-
Expect(resp.Patches).To(Equal([]jsonpatch.JsonPatchOperation{
46-
{
45+
Expect(resp.Patches).To(HaveLen(3))
46+
Expect(resp.Patches).To(ContainElements(
47+
jsonpatch.JsonPatchOperation{
4748
Operation: "add",
4849
Path: "/replica",
4950
Value: 2.0,
5051
},
51-
{
52+
jsonpatch.JsonPatchOperation{
53+
Operation: "remove",
54+
Path: "/newField",
55+
},
56+
jsonpatch.JsonPatchOperation{
57+
Operation: "remove",
58+
Path: "/totalReplicas",
59+
},
60+
))
61+
Expect(resp.Result.Code).Should(Equal(int32(http.StatusOK)))
62+
})
63+
64+
It("should preserve unknown fields when DefaulterPreserveUnknownFields is passed", func() {
65+
obj := &TestDefaulter{}
66+
handler := WithCustomDefaulter(admissionScheme, obj, &TestCustomDefaulter{}, DefaulterPreserveUnknownFields)
67+
68+
resp := handler.Handle(context.TODO(), Request{
69+
AdmissionRequest: admissionv1.AdmissionRequest{
70+
Operation: admissionv1.Create,
71+
Object: runtime.RawExtension{
72+
Raw: []byte(`{"newField":"foo", "totalReplicas":5}`),
73+
},
74+
},
75+
})
76+
Expect(resp.Allowed).Should(BeTrue())
77+
Expect(resp.Patches).To(HaveLen(2))
78+
Expect(resp.Patches).To(ContainElements(
79+
jsonpatch.JsonPatchOperation{
80+
Operation: "add",
81+
Path: "/replica",
82+
Value: 2.0,
83+
},
84+
jsonpatch.JsonPatchOperation{
5285
Operation: "remove",
5386
Path: "/totalReplicas",
5487
},
55-
}))
88+
))
5689
Expect(resp.Result.Code).Should(Equal(int32(http.StatusOK)))
5790
})
5891

0 commit comments

Comments
 (0)