Skip to content

Commit

Permalink
Simplify design and error on multiple mutation mechanisms
Browse files Browse the repository at this point in the history
  • Loading branch information
alculquicondor committed Sep 25, 2024
1 parent ea977b9 commit a375c55
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 31 deletions.
39 changes: 25 additions & 14 deletions pkg/builder/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import (
// WebhookBuilder builds a Webhook.
type WebhookBuilder struct {
apiType runtime.Object
mutatorFactory admission.HandlerFactory
mutationHandler admission.Handler
customDefaulter admission.CustomDefaulter
customValidator admission.CustomValidator
gvk schema.GroupVersionKind
Expand Down Expand Up @@ -66,9 +66,9 @@ func (blder *WebhookBuilder) For(apiType runtime.Object) *WebhookBuilder {
return blder
}

// WithMutatorFactory takes an admission.HandlerFactory, a MutatingWebhook will be wired for the handler that this factory creates.
func (blder *WebhookBuilder) WithMutatorFactory(factory admission.HandlerFactory) *WebhookBuilder {
blder.mutatorFactory = factory
// WithMutationHandler takes an admission.Handler, a MutatingWebhook will be wired for it.
func (blder *WebhookBuilder) WithMutationHandler(h admission.Handler) *WebhookBuilder {
blder.mutationHandler = h
return blder
}

Expand Down Expand Up @@ -147,7 +147,9 @@ func (blder *WebhookBuilder) registerWebhooks() error {
}

// Register webhook(s) for type
blder.registerDefaultingWebhook()
if err := blder.registerDefaultingWebhook(); err != nil {
return err
}
blder.registerValidatingWebhook()

err = blder.registerConversionWebhook()
Expand All @@ -158,8 +160,11 @@ func (blder *WebhookBuilder) registerWebhooks() error {
}

// registerDefaultingWebhook registers a defaulting webhook if necessary.
func (blder *WebhookBuilder) registerDefaultingWebhook() {
mwh := blder.getDefaultingWebhook()
func (blder *WebhookBuilder) registerDefaultingWebhook() error {
mwh, err := blder.getDefaultingWebhook()
if err != nil {
return err
}
if mwh != nil {
mwh.LogConstructor = blder.logConstructor
path := generateMutatePath(blder.gvk)
Expand All @@ -173,21 +178,27 @@ func (blder *WebhookBuilder) registerDefaultingWebhook() {
blder.mgr.GetWebhookServer().Register(path, mwh)
}
}
return nil
}

func (blder *WebhookBuilder) getDefaultingWebhook() *admission.Webhook {
func (blder *WebhookBuilder) getDefaultingWebhook() (*admission.Webhook, error) {
var w *admission.Webhook
if factory := blder.mutatorFactory; factory != nil {
w = admission.WithHandlerFactory(blder.mgr.GetScheme(), blder.apiType, factory)
} else if defaulter := blder.customDefaulter; defaulter != nil {
if handler := blder.mutationHandler; handler != nil {
w = &admission.Webhook{Handler: handler}
}
if defaulter := blder.customDefaulter; defaulter != nil {
if w != nil {
return nil, errors.New("a WebhookBuilder can only define a MutationHandler or a Defaulter, but not both")
}
w = admission.WithCustomDefaulter(blder.mgr.GetScheme(), blder.apiType, defaulter)
} else {
return nil
}
if w == nil {
return nil, nil
}
if blder.recoverPanic != nil {
w = w.WithRecoverPanic(*blder.recoverPanic)
}
return w
return w, nil
}

// registerValidatingWebhook registers a validating webhook if necessary.
Expand Down
31 changes: 25 additions & 6 deletions pkg/builder/webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes"
"gomodules.xyz/jsonpatch/v2"
admissionv1 "k8s.io/api/admission/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/utils/ptr"

logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
Expand Down Expand Up @@ -229,8 +232,8 @@ func runTests(admissionReviewVersion string) {
ExpectWithOffset(1, err).NotTo(HaveOccurred())

err = WebhookManagedBy(m).
WithMutatorFactory(mutatorFactoryForTestDefaulter(m.GetScheme())).
For(&TestDefaulter{}).
WithMutationHandler(&TestMutationHandler{}).
WithLogConstructor(func(base logr.Logger, req *admission.Request) logr.Logger {
return admission.DefaultLogConstructor(testingLogger, req)
}).
Expand Down Expand Up @@ -280,7 +283,7 @@ func runTests(admissionReviewVersion string) {
ExpectWithOffset(1, w.Body).To(ContainSubstring(`"allowed":true`))
ExpectWithOffset(1, w.Body).To(ContainSubstring(`"patch":`))
ExpectWithOffset(1, w.Body).To(ContainSubstring(`"code":200`))
EventuallyWithOffset(1, logBuffer).Should(gbytes.Say(`"msg":"Defaulting object","object":{"name":"foo","namespace":"default"},"namespace":"default","name":"foo","resource":{"group":"foo.test.org","version":"v1","resource":"testdefaulter"},"user":"","requestID":"07e52e8d-4513-11e9-a716-42010a800270"`))
EventuallyWithOffset(1, logBuffer).Should(gbytes.Say(`"msg":"Mutating object","object":{"name":"foo","namespace":"default"},"namespace":"default","name":"foo","resource":{"group":"foo.test.org","version":"v1","resource":"testdefaulter"},"user":"","requestID":"07e52e8d-4513-11e9-a716-42010a800270"`))

By("sending a request to a validating webhook path that doesn't exist")
path = generateValidatePath(testDefaulterGVK)
Expand Down Expand Up @@ -646,7 +649,8 @@ func (*TestDefaultValidatorList) DeepCopyObject() runtime.Object { return nil
type TestCustomDefaulter struct{}

func (*TestCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error {
logf.FromContext(ctx).Info("Defaulting object")
log := logf.FromContext(ctx)
log.Info("Defaulting object")
req, err := admission.RequestFromContext(ctx)
if err != nil {
return fmt.Errorf("expected admission.Request in ctx: %w", err)
Expand All @@ -668,12 +672,27 @@ func (*TestCustomDefaulter) Default(ctx context.Context, obj runtime.Object) err

var _ admission.CustomDefaulter = &TestCustomDefaulter{}

func mutatorFactoryForTestDefaulter(scheme *runtime.Scheme) admission.HandlerFactory {
return func(obj runtime.Object, _ admission.Decoder) admission.Handler {
return admission.WithCustomDefaulter(scheme, obj, &TestCustomDefaulter{}).Handler
// TestMutationHandler
type TestMutationHandler struct{}

func (*TestMutationHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
log := logf.FromContext(ctx)
log.Info("Mutating object")
return admission.Response{
AdmissionResponse: admissionv1.AdmissionResponse{
Allowed: true,
PatchType: ptr.To(admissionv1.PatchTypeJSONPatch),
},
Patches: []jsonpatch.Operation{{
Operation: "replace",
Path: "/replica",
Value: "2",
}},
}
}

var _ admission.Handler = &TestMutationHandler{}

// TestCustomValidator.

type TestCustomValidator struct{}
Expand Down
11 changes: 0 additions & 11 deletions pkg/webhook/admission/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import (
"gomodules.xyz/jsonpatch/v2"
admissionv1 "k8s.io/api/admission/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/json"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/klog/v2"
Expand Down Expand Up @@ -96,9 +95,6 @@ func (r *Response) Complete(req Request) error {
return nil
}

// HandlerFactory can create a Handler for the given type.
type HandlerFactory func(obj runtime.Object, decoder Decoder) Handler

// Handler can handle an AdmissionRequest.
type Handler interface {
// Handle yields a response to an AdmissionRequest.
Expand All @@ -118,13 +114,6 @@ func (f HandlerFunc) Handle(ctx context.Context, req Request) Response {
return f(ctx, req)
}

// WithHandlerFactory creates a new Webhook for a handler factory.
func WithHandlerFactory(scheme *runtime.Scheme, obj runtime.Object, factory HandlerFactory) *Webhook {
return &Webhook{
Handler: factory(obj, NewDecoder(scheme)),
}
}

// Webhook represents each individual webhook.
//
// It must be registered with a webhook.Server or
Expand Down

0 comments on commit a375c55

Please sign in to comment.