...

Source file src/sigs.k8s.io/controller-runtime/pkg/builder/webhook.go

Documentation: sigs.k8s.io/controller-runtime/pkg/builder

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package builder
    18  
    19  import (
    20  	"errors"
    21  	"net/http"
    22  	"net/url"
    23  	"strings"
    24  
    25  	"github.com/go-logr/logr"
    26  	"k8s.io/apimachinery/pkg/runtime"
    27  	"k8s.io/apimachinery/pkg/runtime/schema"
    28  	"k8s.io/client-go/rest"
    29  	"k8s.io/klog/v2"
    30  
    31  	"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
    32  	"sigs.k8s.io/controller-runtime/pkg/manager"
    33  	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
    34  	"sigs.k8s.io/controller-runtime/pkg/webhook/conversion"
    35  )
    36  
    37  // WebhookBuilder builds a Webhook.
    38  type WebhookBuilder struct {
    39  	apiType         runtime.Object
    40  	customDefaulter admission.CustomDefaulter
    41  	customValidator admission.CustomValidator
    42  	gvk             schema.GroupVersionKind
    43  	mgr             manager.Manager
    44  	config          *rest.Config
    45  	recoverPanic    bool
    46  	logConstructor  func(base logr.Logger, req *admission.Request) logr.Logger
    47  	err             error
    48  }
    49  
    50  // WebhookManagedBy returns a new webhook builder.
    51  func WebhookManagedBy(m manager.Manager) *WebhookBuilder {
    52  	return &WebhookBuilder{mgr: m}
    53  }
    54  
    55  // TODO(droot): update the GoDoc for conversion.
    56  
    57  // For takes a runtime.Object which should be a CR.
    58  // If the given object implements the admission.Defaulter interface, a MutatingWebhook will be wired for this type.
    59  // If the given object implements the admission.Validator interface, a ValidatingWebhook will be wired for this type.
    60  func (blder *WebhookBuilder) For(apiType runtime.Object) *WebhookBuilder {
    61  	if blder.apiType != nil {
    62  		blder.err = errors.New("For(...) should only be called once, could not assign multiple objects for webhook registration")
    63  	}
    64  	blder.apiType = apiType
    65  	return blder
    66  }
    67  
    68  // WithDefaulter takes an admission.CustomDefaulter interface, a MutatingWebhook will be wired for this type.
    69  func (blder *WebhookBuilder) WithDefaulter(defaulter admission.CustomDefaulter) *WebhookBuilder {
    70  	blder.customDefaulter = defaulter
    71  	return blder
    72  }
    73  
    74  // WithValidator takes a admission.CustomValidator interface, a ValidatingWebhook will be wired for this type.
    75  func (blder *WebhookBuilder) WithValidator(validator admission.CustomValidator) *WebhookBuilder {
    76  	blder.customValidator = validator
    77  	return blder
    78  }
    79  
    80  // WithLogConstructor overrides the webhook's LogConstructor.
    81  func (blder *WebhookBuilder) WithLogConstructor(logConstructor func(base logr.Logger, req *admission.Request) logr.Logger) *WebhookBuilder {
    82  	blder.logConstructor = logConstructor
    83  	return blder
    84  }
    85  
    86  // RecoverPanic indicates whether panics caused by the webhook should be recovered.
    87  func (blder *WebhookBuilder) RecoverPanic() *WebhookBuilder {
    88  	blder.recoverPanic = true
    89  	return blder
    90  }
    91  
    92  // Complete builds the webhook.
    93  func (blder *WebhookBuilder) Complete() error {
    94  	// Set the Config
    95  	blder.loadRestConfig()
    96  
    97  	// Configure the default LogConstructor
    98  	blder.setLogConstructor()
    99  
   100  	// Set the Webhook if needed
   101  	return blder.registerWebhooks()
   102  }
   103  
   104  func (blder *WebhookBuilder) loadRestConfig() {
   105  	if blder.config == nil {
   106  		blder.config = blder.mgr.GetConfig()
   107  	}
   108  }
   109  
   110  func (blder *WebhookBuilder) setLogConstructor() {
   111  	if blder.logConstructor == nil {
   112  		blder.logConstructor = func(base logr.Logger, req *admission.Request) logr.Logger {
   113  			log := base.WithValues(
   114  				"webhookGroup", blder.gvk.Group,
   115  				"webhookKind", blder.gvk.Kind,
   116  			)
   117  			if req != nil {
   118  				return log.WithValues(
   119  					blder.gvk.Kind, klog.KRef(req.Namespace, req.Name),
   120  					"namespace", req.Namespace, "name", req.Name,
   121  					"resource", req.Resource, "user", req.UserInfo.Username,
   122  					"requestID", req.UID,
   123  				)
   124  			}
   125  			return log
   126  		}
   127  	}
   128  }
   129  
   130  func (blder *WebhookBuilder) registerWebhooks() error {
   131  	typ, err := blder.getType()
   132  	if err != nil {
   133  		return err
   134  	}
   135  
   136  	blder.gvk, err = apiutil.GVKForObject(typ, blder.mgr.GetScheme())
   137  	if err != nil {
   138  		return err
   139  	}
   140  
   141  	// Register webhook(s) for type
   142  	blder.registerDefaultingWebhook()
   143  	blder.registerValidatingWebhook()
   144  
   145  	err = blder.registerConversionWebhook()
   146  	if err != nil {
   147  		return err
   148  	}
   149  	return blder.err
   150  }
   151  
   152  // registerDefaultingWebhook registers a defaulting webhook if necessary.
   153  func (blder *WebhookBuilder) registerDefaultingWebhook() {
   154  	mwh := blder.getDefaultingWebhook()
   155  	if mwh != nil {
   156  		mwh.LogConstructor = blder.logConstructor
   157  		path := generateMutatePath(blder.gvk)
   158  
   159  		// Checking if the path is already registered.
   160  		// If so, just skip it.
   161  		if !blder.isAlreadyHandled(path) {
   162  			log.Info("Registering a mutating webhook",
   163  				"GVK", blder.gvk,
   164  				"path", path)
   165  			blder.mgr.GetWebhookServer().Register(path, mwh)
   166  		}
   167  	}
   168  }
   169  
   170  func (blder *WebhookBuilder) getDefaultingWebhook() *admission.Webhook {
   171  	if defaulter := blder.customDefaulter; defaulter != nil {
   172  		return admission.WithCustomDefaulter(blder.mgr.GetScheme(), blder.apiType, defaulter).WithRecoverPanic(blder.recoverPanic)
   173  	}
   174  	if defaulter, ok := blder.apiType.(admission.Defaulter); ok {
   175  		return admission.DefaultingWebhookFor(blder.mgr.GetScheme(), defaulter).WithRecoverPanic(blder.recoverPanic)
   176  	}
   177  	log.Info(
   178  		"skip registering a mutating webhook, object does not implement admission.Defaulter or WithDefaulter wasn't called",
   179  		"GVK", blder.gvk)
   180  	return nil
   181  }
   182  
   183  // registerValidatingWebhook registers a validating webhook if necessary.
   184  func (blder *WebhookBuilder) registerValidatingWebhook() {
   185  	vwh := blder.getValidatingWebhook()
   186  	if vwh != nil {
   187  		vwh.LogConstructor = blder.logConstructor
   188  		path := generateValidatePath(blder.gvk)
   189  
   190  		// Checking if the path is already registered.
   191  		// If so, just skip it.
   192  		if !blder.isAlreadyHandled(path) {
   193  			log.Info("Registering a validating webhook",
   194  				"GVK", blder.gvk,
   195  				"path", path)
   196  			blder.mgr.GetWebhookServer().Register(path, vwh)
   197  		}
   198  	}
   199  }
   200  
   201  func (blder *WebhookBuilder) getValidatingWebhook() *admission.Webhook {
   202  	if validator := blder.customValidator; validator != nil {
   203  		return admission.WithCustomValidator(blder.mgr.GetScheme(), blder.apiType, validator).WithRecoverPanic(blder.recoverPanic)
   204  	}
   205  	if validator, ok := blder.apiType.(admission.Validator); ok {
   206  		return admission.ValidatingWebhookFor(blder.mgr.GetScheme(), validator).WithRecoverPanic(blder.recoverPanic)
   207  	}
   208  	log.Info(
   209  		"skip registering a validating webhook, object does not implement admission.Validator or WithValidator wasn't called",
   210  		"GVK", blder.gvk)
   211  	return nil
   212  }
   213  
   214  func (blder *WebhookBuilder) registerConversionWebhook() error {
   215  	ok, err := conversion.IsConvertible(blder.mgr.GetScheme(), blder.apiType)
   216  	if err != nil {
   217  		log.Error(err, "conversion check failed", "GVK", blder.gvk)
   218  		return err
   219  	}
   220  	if ok {
   221  		if !blder.isAlreadyHandled("/convert") {
   222  			blder.mgr.GetWebhookServer().Register("/convert", conversion.NewWebhookHandler(blder.mgr.GetScheme()))
   223  		}
   224  		log.Info("Conversion webhook enabled", "GVK", blder.gvk)
   225  	}
   226  
   227  	return nil
   228  }
   229  
   230  func (blder *WebhookBuilder) getType() (runtime.Object, error) {
   231  	if blder.apiType != nil {
   232  		return blder.apiType, nil
   233  	}
   234  	return nil, errors.New("For() must be called with a valid object")
   235  }
   236  
   237  func (blder *WebhookBuilder) isAlreadyHandled(path string) bool {
   238  	if blder.mgr.GetWebhookServer().WebhookMux() == nil {
   239  		return false
   240  	}
   241  	h, p := blder.mgr.GetWebhookServer().WebhookMux().Handler(&http.Request{URL: &url.URL{Path: path}})
   242  	if p == path && h != nil {
   243  		return true
   244  	}
   245  	return false
   246  }
   247  
   248  func generateMutatePath(gvk schema.GroupVersionKind) string {
   249  	return "/mutate-" + strings.ReplaceAll(gvk.Group, ".", "-") + "-" +
   250  		gvk.Version + "-" + strings.ToLower(gvk.Kind)
   251  }
   252  
   253  func generateValidatePath(gvk schema.GroupVersionKind) string {
   254  	return "/validate-" + strings.ReplaceAll(gvk.Group, ".", "-") + "-" +
   255  		gvk.Version + "-" + strings.ToLower(gvk.Kind)
   256  }
   257  

View as plain text