Skip to content

Commit fe47a9b

Browse files
authored
Merge pull request #8 from snorwin/object-mutator-interface
Re-apply changes from 09626e5
2 parents 1259fa6 + 727fbfc commit fe47a9b

File tree

7 files changed

+157
-48
lines changed

7 files changed

+157
-48
lines changed

README.md

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,7 @@ if err = (&pod.Webhook{}).SetupWebhookWithManager(mgr); err != nil {
5858
```
5959

6060
## Examples
61-
### Mutating admission webhook using `MutateObjectFunc`
62-
The `MutateObjectFunc` creates the JSON patches for the admission response automatically in order to simplify the mutation of `runtime.Object`.
63-
The example shows how the functional interface `MutateObjectFunc` can be used to mutate a `Pod`.
61+
### Object mutating admission webhook for `Pod`
6462
```go
6563
package pod
6664

@@ -78,19 +76,13 @@ import (
7876
)
7977

8078
type Webhook struct {
81-
mutator *webhook.MutateObjectFunc
79+
webhook.MutatingObjectWebhook
8280
}
8381

8482
func (w *Webhook) SetupWebhookWithManager(mgr manager.Manager) error {
85-
w.mutator = &webhook.MutateObjectFunc{
86-
Func: func(ctx context.Context, request admission.Request, object runtime.Object) error {
87-
return w.Mutate(ctx, request, object)
88-
},
89-
}
90-
9183
return webhook.NewGenericWebhookManagedBy(mgr).
9284
For(&corev1.Pod{}).
93-
Complete(w.mutator)
85+
Complete(&w)
9486
}
9587

9688
func (w *Webhook) Mutate(ctx context.Context, request admission.Request, object runtime.Object) error {

pkg/webhook/handler.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ func (h *handler) InjectDecoder(decoder *admission.Decoder) error {
9595

9696
// InjectClient implements the inject.Client interface.
9797
func (h *handler) InjectClient(client client.Client) error {
98+
// pass client to the underlying handler
9899
if injector, ok := h.Handler.(inject.Client); ok {
99100
return injector.InjectClient(client)
100101
}

pkg/webhook/handler_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,34 @@ var _ = Describe("Handler", func() {
6565
result = h.Handle(context.TODO(), admission.Request{})
6666
Ω(result.Allowed).Should(BeTrue())
6767
})
68+
It("should mutate object", func() {
69+
pod := &corev1.Pod{
70+
ObjectMeta: metav1.ObjectMeta{
71+
Name: "foo",
72+
Namespace: "bar",
73+
},
74+
}
75+
raw, err := json.Marshal(pod)
76+
Ω(err).ShouldNot(HaveOccurred())
77+
78+
h := handler{
79+
Handler: wrapAsMutator(&MutatingObjectWebhook{}),
80+
Object: &corev1.Pod{},
81+
}
82+
err = h.InjectDecoder(decoder)
83+
Ω(err).ShouldNot(HaveOccurred())
84+
result := h.Handle(context.TODO(), admission.Request{
85+
AdmissionRequest: admissionv1.AdmissionRequest{
86+
Object: runtime.RawExtension{
87+
Raw: raw,
88+
},
89+
Operation: admissionv1.Create,
90+
},
91+
})
92+
Ω(result.Allowed).Should(BeTrue())
93+
result = h.Handle(context.TODO(), admission.Request{})
94+
Ω(result.Allowed).Should(BeTrue())
95+
})
6896
It("should validate", func() {
6997
pod := &corev1.Pod{
7098
ObjectMeta: metav1.ObjectMeta{
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package webhook
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
8+
"k8s.io/apimachinery/pkg/runtime"
9+
"sigs.k8s.io/controller-runtime/pkg/client"
10+
"sigs.k8s.io/controller-runtime/pkg/runtime/inject"
11+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
12+
)
13+
14+
// ObjectMutator specifies the interface for an object mutating webhook.
15+
type ObjectMutator interface {
16+
// MutateObject mutates the runtime.Object of a admission.Request
17+
MutateObject(context.Context, admission.Request, runtime.Object) error
18+
}
19+
20+
// wrapAsMutator wrap the given ObjectMutator as generic Mutator
21+
func wrapAsMutator(mutator ObjectMutator) Mutator {
22+
return &objectMutatorAdapter{mutator}
23+
}
24+
25+
type objectMutatorAdapter struct {
26+
ObjectMutator
27+
}
28+
29+
// Mutate implements the Mutator interface.
30+
func (a *objectMutatorAdapter) Mutate(ctx context.Context, req admission.Request) admission.Response {
31+
return MutateObjectByFunc(ctx, req, a.ObjectMutator.MutateObject)
32+
}
33+
34+
// InjectDecoder implements the admission.DecoderInjector interface.
35+
func (a *objectMutatorAdapter) InjectDecoder(decoder *admission.Decoder) error {
36+
// pass decoder to the underlying handler
37+
if injector, ok := a.ObjectMutator.(admission.DecoderInjector); ok {
38+
return injector.InjectDecoder(decoder)
39+
}
40+
41+
return nil
42+
}
43+
44+
// InjectClient implements the inject.Client interface.
45+
func (a *objectMutatorAdapter) InjectClient(client client.Client) error {
46+
// pass client to the underlying handler
47+
if injector, ok := a.ObjectMutator.(inject.Client); ok {
48+
return injector.InjectClient(client)
49+
}
50+
51+
return nil
52+
}
53+
54+
// ensure MutatingObjectWebhook implements ObjectMutator
55+
var _ ObjectMutator = &MutatingObjectWebhook{}
56+
57+
// MutatingObjectWebhook is a simplified mutating admission webhook.
58+
type MutatingObjectWebhook struct {
59+
InjectedClient
60+
InjectedDecoder
61+
}
62+
63+
// MutateObject implements the ObjectMutator interface.
64+
func (w *MutatingObjectWebhook) MutateObject(_ context.Context, _ admission.Request, _ runtime.Object) error {
65+
return nil
66+
}
67+
68+
// MutateObjectFunc is a functional interface for an object mutating admission webhook.
69+
type MutateObjectFunc struct {
70+
MutatingWebhook
71+
72+
Func func(context.Context, admission.Request, runtime.Object) error
73+
}
74+
75+
// Mutate implements the Mutator interface by calling the Func using the request's runtime.Object.
76+
func (m *MutateObjectFunc) Mutate(ctx context.Context, req admission.Request) admission.Response {
77+
if m.Func != nil {
78+
return MutateObjectByFunc(ctx, req, m.Func)
79+
}
80+
81+
return m.MutatingWebhook.Mutate(ctx, req)
82+
}
83+
84+
// MutateObjectByFunc yields and admission.Response for a runtime.Object mutated bu the specified function.
85+
func MutateObjectByFunc(ctx context.Context, req admission.Request, f func(context.Context, admission.Request, runtime.Object) error) admission.Response {
86+
obj := req.Object.Object
87+
err := f(ctx, req, obj)
88+
if err != nil {
89+
return admission.Errored(http.StatusInternalServerError, err)
90+
}
91+
92+
marshalled, err := json.Marshal(obj)
93+
if err != nil {
94+
return admission.Errored(http.StatusInternalServerError, err)
95+
}
96+
97+
return admission.PatchResponseFromRaw(req.Object.Raw, marshalled)
98+
}

pkg/webhook/mutating_webhook.go

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@ package webhook
22

33
import (
44
"context"
5-
"encoding/json"
6-
"net/http"
7-
8-
"k8s.io/apimachinery/pkg/runtime"
95
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
106
)
117

@@ -44,34 +40,3 @@ func (m *MutateFunc) Mutate(ctx context.Context, req admission.Request) admissio
4440

4541
return m.MutatingWebhook.Mutate(ctx, req)
4642
}
47-
48-
// MutateObjectFunc is a functional interface for an object mutating admission webhook.
49-
type MutateObjectFunc struct {
50-
MutatingWebhook
51-
52-
Func func(context.Context, admission.Request, runtime.Object) error
53-
}
54-
55-
// Mutate implements the Mutator interface by calling the Func using the request's runtime.Object.
56-
func (m *MutateObjectFunc) Mutate(ctx context.Context, req admission.Request) admission.Response {
57-
if m.Func != nil {
58-
return MutateObjectByFunc(ctx, req, m.Func)
59-
}
60-
61-
return m.MutatingWebhook.Mutate(ctx, req)
62-
}
63-
64-
func MutateObjectByFunc(ctx context.Context, req admission.Request, f func(context.Context, admission.Request, runtime.Object) error) admission.Response {
65-
obj := req.Object.Object
66-
err := f(ctx, req, obj)
67-
if err != nil {
68-
return admission.Errored(http.StatusInternalServerError, err)
69-
}
70-
71-
marshalled, err := json.Marshal(obj)
72-
if err != nil {
73-
return admission.Errored(http.StatusInternalServerError, err)
74-
}
75-
76-
return admission.PatchResponseFromRaw(req.Object.Raw, marshalled)
77-
}

pkg/webhook/webhook.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func (blder *Builder) Complete(i interface{}) error {
8080
return fmt.Errorf("validating prefix %q must start with '/'", blder.prefixValidate)
8181
}
8282

83-
if validator, ok := i.(Validator); ok {
83+
if validator := blder.asValidator(i); validator != nil {
8484
w, err := blder.createAdmissionWebhook(&handler{Handler: validator, Object: blder.apiType})
8585
if err != nil {
8686
return err
@@ -91,7 +91,7 @@ func (blder *Builder) Complete(i interface{}) error {
9191
}
9292
}
9393

94-
if mutator, ok := i.(Mutator); ok {
94+
if mutator := blder.asMutator(i); mutator != nil {
9595
w, err := blder.createAdmissionWebhook(&handler{Handler: mutator, Object: blder.apiType})
9696
if err != nil {
9797
return err
@@ -105,6 +105,25 @@ func (blder *Builder) Complete(i interface{}) error {
105105
return nil
106106
}
107107

108+
func (blder *Builder) asValidator(i interface{}) Validator {
109+
if validator, ok := i.(Validator); ok {
110+
return validator
111+
}
112+
113+
return nil
114+
}
115+
116+
func (blder *Builder) asMutator(i interface{}) Mutator {
117+
if mutator, ok := i.(Mutator); ok {
118+
return mutator
119+
}
120+
if mutator, ok := i.(ObjectMutator); ok {
121+
return wrapAsMutator(mutator)
122+
}
123+
124+
return nil
125+
}
126+
108127
func (blder *Builder) createAdmissionWebhook(handler Handler) (*admission.Webhook, error) {
109128
w := &admission.Webhook{
110129
Handler: handler,

pkg/webhook/webhook_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ var _ = Describe("Webhook", func() {
5454
Complete(&webhook.MutatingWebhook{})
5555
Ω(err).ShouldNot(HaveOccurred())
5656
})
57+
It("should build object mutating webhook", func() {
58+
err := webhook.NewGenericWebhookManagedBy(mgr).
59+
For(&corev1.Pod{}).
60+
Complete(&webhook.MutatingObjectWebhook{})
61+
Ω(err).ShouldNot(HaveOccurred())
62+
})
5763
It("should build validating webhook", func() {
5864
err := webhook.NewGenericWebhookManagedBy(mgr).
5965
For(&corev1.Pod{}).

0 commit comments

Comments
 (0)