Skip to content

Commit

Permalink
Implement object interfaces as functional only and provide helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
snorwin committed Aug 17, 2021
1 parent 09626e5 commit 2c1aa26
Show file tree
Hide file tree
Showing 16 changed files with 280 additions and 399 deletions.
35 changes: 0 additions & 35 deletions pkg/webhook/base.go

This file was deleted.

7 changes: 4 additions & 3 deletions pkg/webhook/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,11 @@ func (h *handler) Handle(ctx context.Context, req admission.Request) admission.R
}

// invoke mutator
if req.Object.Object != nil {
if mutator, ok := h.Handler.(Mutator); ok {
// invoke mutator
if mutator, ok := h.Handler.(Mutator); ok {
if req.Object.Object != nil {
return mutator.Mutate(ctx, req)
} else {
return admission.Allowed("")
}
}

Expand Down
32 changes: 32 additions & 0 deletions pkg/webhook/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,38 @@ var _ = Describe("Handler", func() {
result := (&handler{}).Handle(context.TODO(), admission.Request{})
Ω(result.Allowed).Should(BeFalse())
})
It("should mutate", func() {
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "bar",
},
}
raw, err := json.Marshal(pod)
Ω(err).ShouldNot(HaveOccurred())

h := handler{
Handler: &MutateFunc{
Func: func(ctx context.Context, request admission.Request) admission.Response {
return admission.Allowed("")
},
},
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
28 changes: 28 additions & 0 deletions pkg/webhook/injection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package webhook

import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

// InjectedClient holds an injected client.Client
type InjectedClient struct {
Client client.Client
}

// InjectClient implements the inject.Client interface.
func (i *InjectedClient) InjectClient(client client.Client) error {
i.Client = client
return nil
}

// InjectedDecoder holds an injected admission.Decoder
type InjectedDecoder struct {
Decoder *admission.Decoder
}

// InjectDecoder implements the admission.DecoderInjector interface.
func (i *InjectedDecoder) InjectDecoder(decoder *admission.Decoder) error {
i.Decoder = decoder
return nil
}
71 changes: 0 additions & 71 deletions pkg/webhook/mutating_simple_webhook.go

This file was deleted.

36 changes: 0 additions & 36 deletions pkg/webhook/mutating_simple_webhook_private_test.go

This file was deleted.

30 changes: 0 additions & 30 deletions pkg/webhook/mutating_simple_webhook_test.go

This file was deleted.

39 changes: 37 additions & 2 deletions pkg/webhook/mutating_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package webhook

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

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

// Mutator specifies the interface for a mutating webhook.
// Mutator specifies the interface for a generic mutating webhook.
type Mutator interface {
// Mutate yields a response to an mutating AdmissionRequest.
Mutate(context.Context, admission.Request) admission.Response
Expand All @@ -17,7 +20,8 @@ var _ Mutator = &MutatingWebhook{}

// MutatingWebhook is a generic mutating admission webhook.
type MutatingWebhook struct {
baseHandler
InjectedClient
InjectedDecoder
}

// Mutate implements the Mutator interface.
Expand All @@ -40,3 +44,34 @@ 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)
}
57 changes: 57 additions & 0 deletions pkg/webhook/mutating_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@ package webhook_test

import (
"context"
"encoding/json"
"errors"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

"github.com/snorwin/k8s-generic-webhook/pkg/webhook"
admissionv1 "k8s.io/api/admission/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

Expand All @@ -24,4 +30,55 @@ var _ = Describe("Mutating Webhook", func() {
Ω(result.Allowed).Should(BeFalse())
})
})
Context("MutateObjectFunc", func() {
var (
n *corev1.Namespace
raw []byte
)
BeforeEach(func() {
var err error
n = &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
}
raw, err = json.Marshal(n)
Ω(err).ShouldNot(HaveOccurred())
})
It("should by default allow all", func() {
result := (&webhook.MutateObjectFunc{}).Mutate(context.TODO(), admission.Request{})
Ω(result.Allowed).Should(BeTrue())
})
It("should use defined functions", func() {
result := (&webhook.MutateObjectFunc{
Func: func(ctx context.Context, _ admission.Request, object runtime.Object) error {
Ω(object).Should(Equal(n))
return nil
},
}).Mutate(context.TODO(), admission.Request{
AdmissionRequest: admissionv1.AdmissionRequest{
Object: runtime.RawExtension{
Object: n,
Raw: raw,
},
},
})
Ω(result.Allowed).Should(BeTrue())
})
It("should deny if error", func() {
result := (&webhook.MutateObjectFunc{
Func: func(ctx context.Context, _ admission.Request, _ runtime.Object) error {
return errors.New("")
},
}).Mutate(context.TODO(), admission.Request{
AdmissionRequest: admissionv1.AdmissionRequest{
Object: runtime.RawExtension{
Object: n,
Raw: raw,
},
},
})
Ω(result.Allowed).Should(BeFalse())
})
})
})
Loading

0 comments on commit 2c1aa26

Please sign in to comment.