Skip to content

Commit b23c574

Browse files
committed
Construct and inject decoder into webhook handlers
This ensures that decoders (which are no longer constructed in the manager) are properly injected into webhook handlers. The webhook itself receives a scheme, which it uses to construct a decoder.
1 parent 74fd7f5 commit b23c574

File tree

7 files changed

+167
-11
lines changed

7 files changed

+167
-11
lines changed

example/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ func main() {
8585
mgr.Add(hookServer)
8686

8787
entryLog.Info("registering webhooks to the webhook server")
88-
hookServer.Register("/mutate-pods", webhook.Admission{&podAnnotator{}})
89-
hookServer.Register("/validate-pods", webhook.Admission{&podValidator{}})
88+
hookServer.Register("/mutate-pods", webhook.Admission{Handler: &podAnnotator{}})
89+
hookServer.Register("/validate-pods", webhook.Admission{Handler: &podValidator{}})
9090

9191
entryLog.Info("starting manager")
9292
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {

example/mutatingwebhook.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import (
2929
// podAnnotator annotates Pods
3030
type podAnnotator struct {
3131
client client.Client
32-
decoder admission.Decoder
32+
decoder *admission.Decoder
3333
}
3434

3535
// podAnnotator adds an annotation to every incoming pods.
@@ -67,7 +67,7 @@ func (a *podAnnotator) InjectClient(c client.Client) error {
6767
// A decoder will be automatically injected.
6868

6969
// InjectDecoder injects the decoder.
70-
func (a *podAnnotator) InjectDecoder(d admission.Decoder) error {
70+
func (a *podAnnotator) InjectDecoder(d *admission.Decoder) error {
7171
a.decoder = d
7272
return nil
7373
}

example/validatingwebhook.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import (
2929
// podValidator validates Pods
3030
type podValidator struct {
3131
client client.Client
32-
decoder admission.Decoder
32+
decoder *admission.Decoder
3333
}
3434

3535
// podValidator admits a pod iff a specific annotation exists.
@@ -66,7 +66,7 @@ func (v *podValidator) InjectClient(c client.Client) error {
6666
// A decoder will be automatically injected.
6767

6868
// InjectDecoder injects the decoder.
69-
func (v *podValidator) InjectDecoder(d admission.Decoder) error {
69+
func (v *podValidator) InjectDecoder(d *admission.Decoder) error {
7070
v.decoder = d
7171
return nil
7272
}

pkg/webhook/admission/http_test.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,20 @@ type nopCloser struct {
100100
func (nopCloser) Close() error { return nil }
101101

102102
type fakeHandler struct {
103-
invoked bool
104-
fn func(context.Context, Request) Response
103+
invoked bool
104+
fn func(context.Context, Request) Response
105+
decoder *Decoder
106+
injectedString string
107+
}
108+
109+
func (h *fakeHandler) InjectDecoder(d *Decoder) error {
110+
h.decoder = d
111+
return nil
112+
}
113+
114+
func (h *fakeHandler) InjectString(s string) error {
115+
h.injectedString = s
116+
return nil
105117
}
106118

107119
func (h *fakeHandler) Handle(ctx context.Context, req Request) Response {

pkg/webhook/admission/inject.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ package admission
1818

1919
// DecoderInjector is used by the ControllerManager to inject decoder into webhook handlers.
2020
type DecoderInjector interface {
21-
InjectDecoder(Decoder) error
21+
InjectDecoder(*Decoder) error
2222
}
2323

2424
// InjectDecoderInto will set decoder on i and return the result if it implements Decoder. Returns
2525
// false if i does not implement Decoder.
26-
func InjectDecoderInto(decoder Decoder, i interface{}) (bool, error) {
26+
func InjectDecoderInto(decoder *Decoder, i interface{}) (bool, error) {
2727
if s, ok := i.(DecoderInjector); ok {
2828
return true, s.InjectDecoder(decoder)
2929
}

pkg/webhook/admission/webhook.go

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/appscode/jsonpatch"
2525
admissionv1beta1 "k8s.io/api/admission/v1beta1"
2626
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/apimachinery/pkg/runtime"
2728
"k8s.io/apimachinery/pkg/util/json"
2829

2930
"sigs.k8s.io/controller-runtime/pkg/runtime/inject"
@@ -108,6 +109,9 @@ type Webhook struct {
108109
// Handler actually processes an admission request returning whether it was allowed or denied,
109110
// and potentially patches to apply to the handler.
110111
Handler Handler
112+
113+
// scheme is used to construct the Decoder
114+
decoder *Decoder
111115
}
112116

113117
// Handle processes AdmissionRequest.
@@ -124,11 +128,60 @@ func (w *Webhook) Handle(ctx context.Context, req Request) Response {
124128
return resp
125129
}
126130

131+
// InjectScheme injects a scheme into the webhook, in order to construct a Decoder.
132+
func (w *Webhook) InjectScheme(s *runtime.Scheme) error {
133+
// TODO(directxman12): we should have a better way to pass this down
134+
135+
var err error
136+
w.decoder, err = NewDecoder(s)
137+
if err != nil {
138+
return err
139+
}
140+
141+
// inject the decoder here too, just in case the order of calling this is not
142+
// scheme first, then inject func
143+
if w.Handler != nil {
144+
if _, err := InjectDecoderInto(w.GetDecoder(), w.Handler); err != nil {
145+
return err
146+
}
147+
}
148+
149+
return nil
150+
}
151+
152+
// GetDecoder returns a decoder to decode the objects embedded in admission requests.
153+
// It may be nil if we haven't received a scheme to use to determine object types yet.
154+
func (w *Webhook) GetDecoder() *Decoder {
155+
return w.decoder
156+
}
157+
127158
// InjectFunc injects the field setter into the webhook.
128159
func (w *Webhook) InjectFunc(f inject.Func) error {
129160
// inject directly into the handlers. It would be more correct
130161
// to do this in a sync.Once in Handle (since we don't have some
131162
// other start/finalize-type method), but it's more efficient to
132163
// do it here, presumably.
133-
return f(w.Handler)
164+
165+
// also inject a decoder, and wrap this so that we get a setFields
166+
// that injects a decoder (hopefully things don't ignore the duplicate
167+
// InjectorInto call).
168+
169+
var setFields inject.Func
170+
setFields = func(target interface{}) error {
171+
if err := f(target); err != nil {
172+
return err
173+
}
174+
175+
if _, err := inject.InjectorInto(setFields, target); err != nil {
176+
return err
177+
}
178+
179+
if _, err := InjectDecoderInto(w.GetDecoder(), target); err != nil {
180+
return err
181+
}
182+
183+
return nil
184+
}
185+
186+
return setFields(w.Handler)
134187
}

pkg/webhook/admission/webhook_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ import (
2626
"github.com/appscode/jsonpatch"
2727
admissionv1beta1 "k8s.io/api/admission/v1beta1"
2828
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"k8s.io/apimachinery/pkg/runtime"
2930
machinerytypes "k8s.io/apimachinery/pkg/types"
31+
32+
"sigs.k8s.io/controller-runtime/pkg/runtime/inject"
3033
)
3134

3235
var _ = Describe("Admission Webhooks", func() {
@@ -117,4 +120,92 @@ var _ = Describe("Admission Webhooks", func() {
117120
Expect(resp.PatchType).To(Equal(&patchType))
118121
Expect(resp.Patch).To(Equal([]byte(`[{"op":"add","path":"/a","value":2},{"op":"replace","path":"/b","value":4}]`)))
119122
})
123+
124+
Describe("dependency injection", func() {
125+
It("should set dependencies passed in on the handler", func() {
126+
By("setting up a webhook and injecting it with a injection func that injects a string")
127+
setFields := func(target interface{}) error {
128+
inj, ok := target.(stringInjector)
129+
if !ok {
130+
return nil
131+
}
132+
133+
return inj.InjectString("something")
134+
}
135+
handler := &fakeHandler{}
136+
webhook := &Webhook{
137+
Handler: handler,
138+
}
139+
Expect(setFields(webhook)).To(Succeed())
140+
Expect(inject.InjectorInto(setFields, webhook)).To(BeTrue())
141+
142+
By("checking that the string was injected")
143+
Expect(handler.injectedString).To(Equal("something"))
144+
})
145+
146+
It("should inject a decoder into the handler", func() {
147+
By("setting up a webhook and injecting it with a injection func that injects a scheme")
148+
setFields := func(target interface{}) error {
149+
if _, err := inject.SchemeInto(runtime.NewScheme(), target); err != nil {
150+
return err
151+
}
152+
return nil
153+
}
154+
handler := &fakeHandler{}
155+
webhook := &Webhook{
156+
Handler: handler,
157+
}
158+
Expect(setFields(webhook)).To(Succeed())
159+
Expect(inject.InjectorInto(setFields, webhook)).To(BeTrue())
160+
161+
By("checking that the decoder was injected")
162+
Expect(handler.decoder).NotTo(BeNil())
163+
})
164+
165+
It("should pass a setFields that also injects a decoder into sub-dependencies", func() {
166+
By("setting up a webhook and injecting it with a injection func that injects a scheme")
167+
setFields := func(target interface{}) error {
168+
if _, err := inject.SchemeInto(runtime.NewScheme(), target); err != nil {
169+
return err
170+
}
171+
return nil
172+
}
173+
handler := &handlerWithSubDependencies{
174+
Handler: HandlerFunc(func(ctx context.Context, req Request) Response {
175+
return Response{}
176+
}),
177+
dep: &subDep{},
178+
}
179+
webhook := &Webhook{
180+
Handler: handler,
181+
}
182+
Expect(setFields(webhook)).To(Succeed())
183+
Expect(inject.InjectorInto(setFields, webhook)).To(BeTrue())
184+
185+
By("checking that setFields sets the decoder as well")
186+
Expect(handler.dep.decoder).NotTo(BeNil())
187+
})
188+
})
120189
})
190+
191+
type stringInjector interface {
192+
InjectString(s string) error
193+
}
194+
195+
type handlerWithSubDependencies struct {
196+
Handler
197+
dep *subDep
198+
}
199+
200+
func (h *handlerWithSubDependencies) InjectFunc(f inject.Func) error {
201+
return f(h.dep)
202+
}
203+
204+
type subDep struct {
205+
decoder *Decoder
206+
}
207+
208+
func (d *subDep) InjectDecoder(dec *Decoder) error {
209+
d.decoder = dec
210+
return nil
211+
}

0 commit comments

Comments
 (0)