k8s.io/apiserver@v0.31.1/pkg/admission/configuration/mutating_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 mutatingWebhookAccessorCreator func(uid string, configurationName string, h *v1.MutatingWebhook) webhook.WebhookAccessor
    38  
    39  // mutatingWebhookConfigurationManager collects the mutating webhook objects so that they can be called.
    40  type mutatingWebhookConfigurationManager struct {
    41  	lister              admissionregistrationlisters.MutatingWebhookConfigurationLister
    42  	hasSynced           func() bool
    43  	lazy                synctrack.Lazy[[]webhook.WebhookAccessor]
    44  	configurationsCache sync.Map
    45  	// createMutatingWebhookAccessor 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  	createMutatingWebhookAccessor mutatingWebhookAccessorCreator
    49  }
    50  
    51  var _ generic.Source = &mutatingWebhookConfigurationManager{}
    52  
    53  func NewMutatingWebhookConfigurationManager(f informers.SharedInformerFactory) generic.Source {
    54  	informer := f.Admissionregistration().V1().MutatingWebhookConfigurations()
    55  	manager := &mutatingWebhookConfigurationManager{
    56  		lister:                        informer.Lister(),
    57  		createMutatingWebhookAccessor: webhook.NewMutatingWebhookAccessor,
    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.MutatingWebhookConfiguration)
    65  			manager.configurationsCache.Delete(obj.GetName())
    66  			manager.lazy.Notify()
    67  		},
    68  		DeleteFunc: func(obj interface{}) {
    69  			vwc, ok := obj.(*v1.MutatingWebhookConfiguration)
    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.MutatingWebhookConfiguration)
    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 MutatingWebhookConfiguration.
    92  func (m *mutatingWebhookConfigurationManager) Webhooks() []webhook.WebhookAccessor {
    93  	out, err := m.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 mutating webhook configurations
   101  // has been loaded.
   102  func (m *mutatingWebhookConfigurationManager) HasSynced() bool { return m.hasSynced() }
   103  
   104  func (m *mutatingWebhookConfigurationManager) getConfiguration() ([]webhook.WebhookAccessor, error) {
   105  	configurations, err := m.lister.List(labels.Everything())
   106  	if err != nil {
   107  		return []webhook.WebhookAccessor{}, err
   108  	}
   109  	return m.getMutatingWebhookConfigurations(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 (m *mutatingWebhookConfigurationManager) getMutatingWebhookConfigurations(configurations []*v1.MutatingWebhookConfiguration) []webhook.WebhookAccessor {
   118  	// The internal order of webhooks for each configuration is provided by the user
   119  	// but configurations themselves can be in any order. As we are going to run these
   120  	// webhooks in serial, they are sorted here to have a deterministic order.
   121  	sort.SliceStable(configurations, MutatingWebhookConfigurationSorter(configurations).ByName)
   122  	size := 0
   123  	for _, cfg := range configurations {
   124  		size += len(cfg.Webhooks)
   125  	}
   126  	accessors := make([]webhook.WebhookAccessor, 0, size)
   127  
   128  	for _, c := range configurations {
   129  		cachedConfigurationAccessors, ok := m.configurationsCache.Load(c.Name)
   130  		if ok {
   131  			// Pick an already cached webhookAccessor
   132  			accessors = append(accessors, cachedConfigurationAccessors.([]webhook.WebhookAccessor)...)
   133  			continue
   134  		}
   135  
   136  		// webhook names are not validated for uniqueness, so we check for duplicates and
   137  		// add a int suffix to distinguish between them
   138  		names := map[string]int{}
   139  		configurationAccessors := make([]webhook.WebhookAccessor, 0, len(c.Webhooks))
   140  		for i := range c.Webhooks {
   141  			n := c.Webhooks[i].Name
   142  			uid := fmt.Sprintf("%s/%s/%d", c.Name, n, names[n])
   143  			names[n]++
   144  			configurationAccessor := m.createMutatingWebhookAccessor(uid, c.Name, &c.Webhooks[i])
   145  			configurationAccessors = append(configurationAccessors, configurationAccessor)
   146  		}
   147  		accessors = append(accessors, configurationAccessors...)
   148  		m.configurationsCache.Store(c.Name, configurationAccessors)
   149  	}
   150  	return accessors
   151  }
   152  
   153  type MutatingWebhookConfigurationSorter []*v1.MutatingWebhookConfiguration
   154  
   155  func (a MutatingWebhookConfigurationSorter) ByName(i, j int) bool {
   156  	return a[i].Name < a[j].Name
   157  }