shardingsphere-operator/pkg/webhook/webhook.go (163 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package webhook import ( "errors" "net/http" "net/url" "strings" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" "sigs.k8s.io/controller-runtime/pkg/webhook/conversion" ) var log = logf.Log.WithName("builder") // Do additional hacks for apiService const apiPath = "/apis/admission.shardingsphere.apache.org/v1alpha1" // WebhookBuilder builds a Webhook. type WebhookBuilder struct { apiType runtime.Object withDefaulter admission.CustomDefaulter withValidator admission.CustomValidator gvk schema.GroupVersionKind mgr manager.Manager config *rest.Config } // NewWebhookManagedBy allows inform its manager.Manager. func NewWebhookManagedBy(m manager.Manager) *WebhookBuilder { return &WebhookBuilder{mgr: m} } // For takes a runtime.Object which should be a CR. // If the given object implements the admission.Defaulter interface, a MutatingWebhook will be wired for this type. // If the given object implements the admission.Validator interface, a ValidatingWebhook will be wired for this type. func (blder *WebhookBuilder) For(apiType runtime.Object) *WebhookBuilder { blder.apiType = apiType return blder } // WithDefaulter takes a admission.WithDefaulter interface, a MutatingWebhook will be wired for this type. func (blder *WebhookBuilder) WithDefaulter(defaulter admission.CustomDefaulter) *WebhookBuilder { blder.withDefaulter = defaulter return blder } // WithValidator takes a admission.WithValidator interface, a ValidatingWebhook will be wired for this type. func (blder *WebhookBuilder) WithValidator(validator admission.CustomValidator) *WebhookBuilder { blder.withValidator = validator return blder } // Complete builds the webhook. func (blder *WebhookBuilder) Complete() error { // Set the Config blder.loadRestConfig() // Set the Webhook if needed return blder.registerWebhooks() } func (blder *WebhookBuilder) loadRestConfig() { if blder.config == nil { blder.config = blder.mgr.GetConfig() } } func (blder *WebhookBuilder) registerWebhooks() error { typ, err := blder.getType() if err != nil { return err } // Create webhook(s) for each type blder.gvk, err = apiutil.GVKForObject(typ, blder.mgr.GetScheme()) if err != nil { return err } blder.registerApiservice() blder.registerDefaultingWebhook() blder.registerValidatingWebhook() err = blder.registerConversionWebhook() if err != nil { return err } return nil } // registerDefaultingWebhook registers a defaulting webhook if th. func (blder *WebhookBuilder) registerDefaultingWebhook() { mwh := blder.getDefaultingWebhook() if mwh != nil { path := generateMutatePath(blder.gvk) // Checking if the path is already registered. // If so, just skip it. if !blder.isAlreadyHandled(path) { log.Info("Registering a mutating webhook", "GVK", blder.gvk, "path", path) blder.mgr.GetWebhookServer().Register(path, mwh) } } } func (blder *WebhookBuilder) getDefaultingWebhook() *admission.Webhook { if defaulter := blder.withDefaulter; defaulter != nil { return admission.WithCustomDefaulter(blder.apiType, defaulter) } if defaulter, ok := blder.apiType.(admission.Defaulter); ok { return admission.DefaultingWebhookFor(defaulter) } log.Info( "skip registering a mutating webhook, object does not implement admission.Defaulter or WithDefaulter wasn't called", "GVK", blder.gvk) return nil } func (blder *WebhookBuilder) registerValidatingWebhook() { vwh := blder.getValidatingWebhook() if vwh != nil { path := generateValidatePath(blder.gvk) // Checking if the path is already registered. // If so, just skip it. if !blder.isAlreadyHandled(path) { log.Info("Registering a validating webhook", "GVK", blder.gvk, "path", path) blder.mgr.GetWebhookServer().Register(path, vwh) } } } func (blder *WebhookBuilder) getValidatingWebhook() *admission.Webhook { if validator := blder.withValidator; validator != nil { return admission.WithCustomValidator(blder.apiType, validator) } if validator, ok := blder.apiType.(admission.Validator); ok { return admission.ValidatingWebhookFor(validator) } log.Info( "skip registering a validating webhook, object does not implement admission.Validator or WithValidator wasn't called", "GVK", blder.gvk) return nil } func (blder *WebhookBuilder) registerConversionWebhook() error { ok, err := conversion.IsConvertible(blder.mgr.GetScheme(), blder.apiType) if err != nil { log.Error(err, "conversion check failed", "GVK", blder.gvk) return err } if ok { if !blder.isAlreadyHandled("/convert") { blder.mgr.GetWebhookServer().Register("/convert", &conversion.Webhook{}) } log.Info("Conversion webhook enabled", "GVK", blder.gvk) } return nil } func (blder *WebhookBuilder) getType() (runtime.Object, error) { if blder.apiType != nil { return blder.apiType, nil } return nil, errors.New("For() must be called with a valid object") } func (blder *WebhookBuilder) isAlreadyHandled(path string) bool { if blder.mgr.GetWebhookServer().WebhookMux == nil { return false } h, p := blder.mgr.GetWebhookServer().WebhookMux.Handler(&http.Request{URL: &url.URL{Path: path}}) if p == path && h != nil { return true } return false } func (blder *WebhookBuilder) registerApiservice() { blder.mgr.GetWebhookServer().Register(apiPath, getAPIService()) } func getAPIService() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte("{}")) w.WriteHeader(http.StatusOK) } } func generateMutatePath(gvk schema.GroupVersionKind) string { return apiPath + "/mutate-" + strings.ReplaceAll(gvk.Group, ".", "-") + "-" + gvk.Version + "-" + strings.ToLower(gvk.Kind) } func generateValidatePath(gvk schema.GroupVersionKind) string { return apiPath + "/validate-" + strings.ReplaceAll(gvk.Group, ".", "-") + "-" + gvk.Version + "-" + strings.ToLower(gvk.Kind) }