k8s.io/apiserver@v0.31.1/pkg/admission/configuration/validating_webhook_manager.go (about)

     1  /*
     2  Copyright 2017 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 configuration
    18  
    19  import (
    20  	"fmt"
    21  	"sort"
    22  	"sync"
    23  
    24  	v1 "k8s.io/api/admissionregistration/v1"
    25  	"k8s.io/apimachinery/pkg/labels"
    26  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    27  	"k8s.io/apiserver/pkg/admission/plugin/webhook"
    28  	"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
    29  	"k8s.io/client-go/informers"
    30  	admissionregistrationlisters "k8s.io/client-go/listers/admissionregistration/v1"
    31  	"k8s.io/client-go/tools/cache"
    32  	"k8s.io/client-go/tools/cache/synctrack"
    33  	"k8s.io/klog/v2"
    34  )
    35  
    36  // Type for test injection.
    37  type validatingWebhookAccessorCreator func(uid string, configurationName string, h *v1.ValidatingWebhook) webhook.WebhookAccessor
    38  
    39  // validatingWebhookConfigurationManager collects the validating webhook objects so that they can be called.
    40  type validatingWebhookConfigurationManager struct {
    41  	lister              admissionregistrationlisters.ValidatingWebhookConfigurationLister
    42  	hasSynced           func() bool
    43  	lazy                synctrack.Lazy[[]webhook.WebhookAccessor]
    44  	configurationsCache sync.Map
    45  	// createValidatingWebhookAccessor is used to instantiate webhook accessors.
    46  	// This function is defined as field instead of a struct method to allow injection
    47  	// during tests
    48  	createValidatingWebhookAccessor validatingWebhookAccessorCreator
    49  }
    50  
    51  var _ generic.Source = &validatingWebhookConfigurationManager{}
    52  
    53  func NewValidatingWebhookConfigurationManager(f informers.SharedInformerFactory) generic.Source {
    54  	informer := f.Admissionregistration().V1().ValidatingWebhookConfigurations()
    55  	manager := &validatingWebhookConfigurationManager{
    56  		lister:                          informer.Lister(),
    57  		createValidatingWebhookAccessor: webhook.NewValidatingWebhookAccessor,
    58  	}
    59  	manager.lazy.Evaluate = manager.getConfiguration
    60  
    61  	handle, _ := informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
    62  		AddFunc: func(_ interface{}) { manager.lazy.Notify() },
    63  		UpdateFunc: func(old, new interface{}) {
    64  			obj := new.(*v1.ValidatingWebhookConfiguration)
    65  			manager.configurationsCache.Delete(obj.GetName())
    66  			manager.lazy.Notify()
    67  		},
    68  		DeleteFunc: func(obj interface{}) {
    69  			vwc, ok := obj.(*v1.ValidatingWebhookConfiguration)
    70  			if !ok {
    71  				tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
    72  				if !ok {
    73  					klog.V(2).Infof("Couldn't get object from tombstone %#v", obj)
    74  					return
    75  				}
    76  				vwc, ok = tombstone.Obj.(*v1.ValidatingWebhookConfiguration)
    77  				if !ok {
    78  					klog.V(2).Infof("Tombstone contained object that is not expected %#v", obj)
    79  					return
    80  				}
    81  			}
    82  			manager.configurationsCache.Delete(vwc.Name)
    83  			manager.lazy.Notify()
    84  		},
    85  	})
    86  	manager.hasSynced = handle.HasSynced
    87  
    88  	return manager
    89  }
    90  
    91  // Webhooks returns the merged ValidatingWebhookConfiguration.
    92  func (v *validatingWebhookConfigurationManager) Webhooks() []webhook.WebhookAccessor {
    93  	out, err := v.lazy.Get()
    94  	if err != nil {
    95  		utilruntime.HandleError(fmt.Errorf("error getting webhook configuration: %v", err))
    96  	}
    97  	return out
    98  }
    99  
   100  // HasSynced returns true if the initial set of validating webhook configurations
   101  // has been loaded.
   102  func (v *validatingWebhookConfigurationManager) HasSynced() bool { return v.hasSynced() }
   103  
   104  func (v *validatingWebhookConfigurationManager) getConfiguration() ([]webhook.WebhookAccessor, error) {
   105  	configurations, err := v.lister.List(labels.Everything())
   106  	if err != nil {
   107  		return []webhook.WebhookAccessor{}, err
   108  	}
   109  	return v.getValidatingWebhookConfigurations(configurations), nil
   110  }
   111  
   112  // getMutatingWebhookConfigurations returns the webhook accessors for a given list of
   113  // mutating webhook configurations.
   114  //
   115  // This function will, first, try to load the webhook accessors from the cache and avoid
   116  // recreating them, which can be expessive (requiring CEL expression recompilation).
   117  func (v *validatingWebhookConfigurationManager) getValidatingWebhookConfigurations(configurations []*v1.ValidatingWebhookConfiguration) []webhook.WebhookAccessor {
   118  	sort.SliceStable(configurations, ValidatingWebhookConfigurationSorter(configurations).ByName)
   119  	size := 0
   120  	for _, cfg := range configurations {
   121  		size += len(cfg.Webhooks)
   122  	}
   123  	accessors := make([]webhook.WebhookAccessor, 0, size)
   124  
   125  	for _, c := range configurations {
   126  		cachedConfigurationAccessors, ok := v.configurationsCache.Load(c.Name)
   127  		if ok {
   128  			// Pick an already cached webhookAccessor
   129  			accessors = append(accessors, cachedConfigurationAccessors.([]webhook.WebhookAccessor)...)
   130  			continue
   131  		}
   132  
   133  		// webhook names are not validated for uniqueness, so we check for duplicates and
   134  		// add a int suffix to distinguish between them
   135  		names := map[string]int{}
   136  		configurationAccessors := make([]webhook.WebhookAccessor, 0, len(c.Webhooks))
   137  		for i := range c.Webhooks {
   138  			n := c.Webhooks[i].Name
   139  			uid := fmt.Sprintf("%s/%s/%d", c.Name, n, names[n])
   140  			names[n]++
   141  			configurationAccessor := v.createValidatingWebhookAccessor(uid, c.Name, &c.Webhooks[i])
   142  			configurationAccessors = append(configurationAccessors, configurationAccessor)
   143  		}
   144  		accessors = append(accessors, configurationAccessors...)
   145  		v.configurationsCache.Store(c.Name, configurationAccessors)
   146  	}
   147  
   148  	return accessors
   149  }
   150  
   151  type ValidatingWebhookConfigurationSorter []*v1.ValidatingWebhookConfiguration
   152  
   153  func (a ValidatingWebhookConfigurationSorter) ByName(i, j int) bool {
   154  	return a[i].Name < a[j].Name
   155  }