Skip to content

Commit

Permalink
Merge pull request #8 from snorwin/object-mutator-interface
Browse files Browse the repository at this point in the history
Re-apply changes from 09626e5
  • Loading branch information
snorwin authored Aug 18, 2021
2 parents 1259fa6 + 727fbfc commit fe47a9b
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 48 deletions.
14 changes: 3 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,7 @@ if err = (&pod.Webhook{}).SetupWebhookWithManager(mgr); err != nil {
```

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

Expand All @@ -78,19 +76,13 @@ import (
)

type Webhook struct {
mutator *webhook.MutateObjectFunc
webhook.MutatingObjectWebhook
}

func (w *Webhook) SetupWebhookWithManager(mgr manager.Manager) error {
w.mutator = &webhook.MutateObjectFunc{
Func: func(ctx context.Context, request admission.Request, object runtime.Object) error {
return w.Mutate(ctx, request, object)
},
}

return webhook.NewGenericWebhookManagedBy(mgr).
For(&corev1.Pod{}).
Complete(w.mutator)
Complete(&w)
}

func (w *Webhook) Mutate(ctx context.Context, request admission.Request, object runtime.Object) error {
Expand Down
1 change: 1 addition & 0 deletions pkg/webhook/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ func (h *handler) InjectDecoder(decoder *admission.Decoder) error {

// InjectClient implements the inject.Client interface.
func (h *handler) InjectClient(client client.Client) error {
// pass client to the underlying handler
if injector, ok := h.Handler.(inject.Client); ok {
return injector.InjectClient(client)
}
Expand Down
28 changes: 28 additions & 0 deletions pkg/webhook/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,34 @@ var _ = Describe("Handler", func() {
result = h.Handle(context.TODO(), admission.Request{})
Ω(result.Allowed).Should(BeTrue())
})
It("should mutate object", func() {
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "bar",
},
}
raw, err := json.Marshal(pod)
Ω(err).ShouldNot(HaveOccurred())

h := handler{
Handler: wrapAsMutator(&MutatingObjectWebhook{}),
Object: &corev1.Pod{},
}
err = h.InjectDecoder(decoder)
Ω(err).ShouldNot(HaveOccurred())
result := h.Handle(context.TODO(), admission.Request{
AdmissionRequest: admissionv1.AdmissionRequest{
Object: runtime.RawExtension{
Raw: raw,
},
Operation: admissionv1.Create,
},
})
Ω(result.Allowed).Should(BeTrue())
result = h.Handle(context.TODO(), admission.Request{})
Ω(result.Allowed).Should(BeTrue())
})
It("should validate", func() {
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Expand Down
98 changes: 98 additions & 0 deletions pkg/webhook/mutating_object_webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package webhook

import (
"context"
"encoding/json"
"net/http"

"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/runtime/inject"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

// ObjectMutator specifies the interface for an object mutating webhook.
type ObjectMutator interface {
// MutateObject mutates the runtime.Object of a admission.Request
MutateObject(context.Context, admission.Request, runtime.Object) error
}

// wrapAsMutator wrap the given ObjectMutator as generic Mutator
func wrapAsMutator(mutator ObjectMutator) Mutator {
return &objectMutatorAdapter{mutator}
}

type objectMutatorAdapter struct {
ObjectMutator
}

// Mutate implements the Mutator interface.
func (a *objectMutatorAdapter) Mutate(ctx context.Context, req admission.Request) admission.Response {
return MutateObjectByFunc(ctx, req, a.ObjectMutator.MutateObject)
}

// InjectDecoder implements the admission.DecoderInjector interface.
func (a *objectMutatorAdapter) InjectDecoder(decoder *admission.Decoder) error {
// pass decoder to the underlying handler
if injector, ok := a.ObjectMutator.(admission.DecoderInjector); ok {
return injector.InjectDecoder(decoder)
}

return nil
}

// InjectClient implements the inject.Client interface.
func (a *objectMutatorAdapter) InjectClient(client client.Client) error {
// pass client to the underlying handler
if injector, ok := a.ObjectMutator.(inject.Client); ok {
return injector.InjectClient(client)
}

return nil
}

// ensure MutatingObjectWebhook implements ObjectMutator
var _ ObjectMutator = &MutatingObjectWebhook{}

// MutatingObjectWebhook is a simplified mutating admission webhook.
type MutatingObjectWebhook struct {
InjectedClient
InjectedDecoder
}

// MutateObject implements the ObjectMutator interface.
func (w *MutatingObjectWebhook) MutateObject(_ context.Context, _ admission.Request, _ runtime.Object) error {
return nil
}

// MutateObjectFunc is a functional interface for an object mutating admission webhook.
type MutateObjectFunc struct {
MutatingWebhook

Func func(context.Context, admission.Request, runtime.Object) error
}

// Mutate implements the Mutator interface by calling the Func using the request's runtime.Object.
func (m *MutateObjectFunc) Mutate(ctx context.Context, req admission.Request) admission.Response {
if m.Func != nil {
return MutateObjectByFunc(ctx, req, m.Func)
}

return m.MutatingWebhook.Mutate(ctx, req)
}

// MutateObjectByFunc yields and admission.Response for a runtime.Object mutated bu the specified function.
func MutateObjectByFunc(ctx context.Context, req admission.Request, f func(context.Context, admission.Request, runtime.Object) error) admission.Response {
obj := req.Object.Object
err := f(ctx, req, obj)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}

marshalled, err := json.Marshal(obj)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}

return admission.PatchResponseFromRaw(req.Object.Raw, marshalled)
}
35 changes: 0 additions & 35 deletions pkg/webhook/mutating_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ package webhook

import (
"context"
"encoding/json"
"net/http"

"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

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

return m.MutatingWebhook.Mutate(ctx, req)
}

// MutateObjectFunc is a functional interface for an object mutating admission webhook.
type MutateObjectFunc struct {
MutatingWebhook

Func func(context.Context, admission.Request, runtime.Object) error
}

// Mutate implements the Mutator interface by calling the Func using the request's runtime.Object.
func (m *MutateObjectFunc) Mutate(ctx context.Context, req admission.Request) admission.Response {
if m.Func != nil {
return MutateObjectByFunc(ctx, req, m.Func)
}

return m.MutatingWebhook.Mutate(ctx, req)
}

func MutateObjectByFunc(ctx context.Context, req admission.Request, f func(context.Context, admission.Request, runtime.Object) error) admission.Response {
obj := req.Object.Object
err := f(ctx, req, obj)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}

marshalled, err := json.Marshal(obj)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}

return admission.PatchResponseFromRaw(req.Object.Raw, marshalled)
}
23 changes: 21 additions & 2 deletions pkg/webhook/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func (blder *Builder) Complete(i interface{}) error {
return fmt.Errorf("validating prefix %q must start with '/'", blder.prefixValidate)
}

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

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

func (blder *Builder) asValidator(i interface{}) Validator {
if validator, ok := i.(Validator); ok {
return validator
}

return nil
}

func (blder *Builder) asMutator(i interface{}) Mutator {
if mutator, ok := i.(Mutator); ok {
return mutator
}
if mutator, ok := i.(ObjectMutator); ok {
return wrapAsMutator(mutator)
}

return nil
}

func (blder *Builder) createAdmissionWebhook(handler Handler) (*admission.Webhook, error) {
w := &admission.Webhook{
Handler: handler,
Expand Down
6 changes: 6 additions & 0 deletions pkg/webhook/webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ var _ = Describe("Webhook", func() {
Complete(&webhook.MutatingWebhook{})
Ω(err).ShouldNot(HaveOccurred())
})
It("should build object mutating webhook", func() {
err := webhook.NewGenericWebhookManagedBy(mgr).
For(&corev1.Pod{}).
Complete(&webhook.MutatingObjectWebhook{})
Ω(err).ShouldNot(HaveOccurred())
})
It("should build validating webhook", func() {
err := webhook.NewGenericWebhookManagedBy(mgr).
For(&corev1.Pod{}).
Expand Down

0 comments on commit fe47a9b

Please sign in to comment.