Skip to content

Commit 5c09e2b

Browse files
Fix custom defaulter from deleting unknown fields
1 parent e6c3d13 commit 5c09e2b

File tree

2 files changed

+101
-6
lines changed

2 files changed

+101
-6
lines changed

pkg/webhook/admission/defaulter_custom.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,24 +71,32 @@ func (h *defaulterForType) Handle(ctx context.Context, req Request) Response {
7171
ctx = NewContextWithRequest(ctx, req)
7272

7373
// Get the object in the request
74-
obj := h.object.DeepCopyObject()
75-
if err := h.decoder.Decode(req, obj); err != nil {
74+
original := h.object.DeepCopyObject()
75+
if err := h.decoder.Decode(req, original); err != nil {
7676
return Errored(http.StatusBadRequest, err)
7777
}
7878

7979
// Default the object
80-
if err := h.defaulter.Default(ctx, obj); err != nil {
80+
updated := original.DeepCopyObject()
81+
if err := h.defaulter.Default(ctx, updated); err != nil {
8182
var apiStatus apierrors.APIStatus
8283
if errors.As(err, &apiStatus) {
8384
return validationResponseFromStatus(false, apiStatus.Status())
8485
}
8586
return Denied(err.Error())
8687
}
8788

88-
// Create the patch
89-
marshalled, err := json.Marshal(obj)
89+
// Create the patch.
90+
// We need to decode and marshall the original because the type registered in the
91+
// decoder might not match the latest version of the API.
92+
// Creating a diff from the raw object might cause new fields to be dropped.
93+
marshalledOriginal, err := json.Marshal(original)
9094
if err != nil {
9195
return Errored(http.StatusInternalServerError, err)
9296
}
93-
return PatchResponseFromRaw(req.Object.Raw, marshalled)
97+
marshalledUpdated, err := json.Marshal(updated)
98+
if err != nil {
99+
return Errored(http.StatusInternalServerError, err)
100+
}
101+
return PatchResponseFromRaw(marshalledOriginal, marshalledUpdated)
94102
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package admission
2+
3+
import (
4+
"context"
5+
"net/http"
6+
7+
. "github.com/onsi/ginkgo/v2"
8+
. "github.com/onsi/gomega"
9+
"gomodules.xyz/jsonpatch/v2"
10+
11+
admissionv1 "k8s.io/api/admission/v1"
12+
"k8s.io/apimachinery/pkg/runtime"
13+
"k8s.io/apimachinery/pkg/runtime/schema"
14+
)
15+
16+
var _ = Describe("CustomDefaulter Handler", func() {
17+
18+
It("should should not lose unknown fields", func() {
19+
obj := &TestObject{}
20+
handler := WithCustomDefaulter(admissionScheme, obj, &TestCustomDefaulter{})
21+
22+
resp := handler.Handle(context.TODO(), Request{
23+
AdmissionRequest: admissionv1.AdmissionRequest{
24+
Operation: admissionv1.Create,
25+
Object: runtime.RawExtension{
26+
Raw: []byte(`{"newField":"foo"}`),
27+
},
28+
},
29+
})
30+
Expect(resp.Allowed).Should(BeTrue())
31+
Expect(resp.Patches).To(Equal([]jsonpatch.JsonPatchOperation{{
32+
Operation: "add",
33+
Path: "/replica",
34+
Value: 2.0,
35+
}}))
36+
Expect(resp.Result.Code).Should(Equal(int32(http.StatusOK)))
37+
})
38+
39+
It("should return ok if received delete verb in defaulter handler", func() {
40+
obj := &TestObject{}
41+
handler := WithCustomDefaulter(admissionScheme, obj, &TestCustomDefaulter{})
42+
43+
resp := handler.Handle(context.TODO(), Request{
44+
AdmissionRequest: admissionv1.AdmissionRequest{
45+
Operation: admissionv1.Delete,
46+
OldObject: runtime.RawExtension{
47+
Raw: []byte("{}"),
48+
},
49+
},
50+
})
51+
Expect(resp.Allowed).Should(BeTrue())
52+
Expect(resp.Result.Code).Should(Equal(int32(http.StatusOK)))
53+
})
54+
55+
})
56+
57+
type TestCustomDefaulter struct{}
58+
59+
func (d *TestCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error {
60+
tObj := obj.(*TestObject)
61+
if tObj.Replica < 2 {
62+
tObj.Replica = 2
63+
}
64+
return nil
65+
}
66+
67+
type TestObject struct {
68+
Replica int `json:"replica,omitempty"`
69+
}
70+
71+
func (o *TestObject) GetObjectKind() schema.ObjectKind { return o }
72+
func (o *TestObject) DeepCopyObject() runtime.Object {
73+
return &TestObject{
74+
Replica: o.Replica,
75+
}
76+
}
77+
78+
func (o *TestObject) GroupVersionKind() schema.GroupVersionKind {
79+
return testDefaulterGVK
80+
}
81+
82+
func (o *TestObject) SetGroupVersionKind(gvk schema.GroupVersionKind) {}
83+
84+
type TestObjectList struct{}
85+
86+
func (*TestObjectList) GetObjectKind() schema.ObjectKind { return nil }
87+
func (*TestObjectList) DeepCopyObject() runtime.Object { return nil }

0 commit comments

Comments
 (0)