knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/client/injection/kube/reconciler/admissionregistration/v1/mutatingwebhookconfiguration/reconciler.go (about)

     1  /*
     2  Copyright 2022 The Knative 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  // Code generated by injection-gen. DO NOT EDIT.
    18  
    19  package mutatingwebhookconfiguration
    20  
    21  import (
    22  	context "context"
    23  	json "encoding/json"
    24  	fmt "fmt"
    25  
    26  	zap "go.uber.org/zap"
    27  	v1 "k8s.io/api/admissionregistration/v1"
    28  	corev1 "k8s.io/api/core/v1"
    29  	errors "k8s.io/apimachinery/pkg/api/errors"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	labels "k8s.io/apimachinery/pkg/labels"
    32  	types "k8s.io/apimachinery/pkg/types"
    33  	sets "k8s.io/apimachinery/pkg/util/sets"
    34  	kubernetes "k8s.io/client-go/kubernetes"
    35  	scheme "k8s.io/client-go/kubernetes/scheme"
    36  	admissionregistrationv1 "k8s.io/client-go/listers/admissionregistration/v1"
    37  	record "k8s.io/client-go/tools/record"
    38  	controller "knative.dev/pkg/controller"
    39  	logging "knative.dev/pkg/logging"
    40  	reconciler "knative.dev/pkg/reconciler"
    41  )
    42  
    43  // Interface defines the strongly typed interfaces to be implemented by a
    44  // controller reconciling v1.MutatingWebhookConfiguration.
    45  type Interface interface {
    46  	// ReconcileKind implements custom logic to reconcile v1.MutatingWebhookConfiguration. Any changes
    47  	// to the objects .Status or .Finalizers will be propagated to the stored
    48  	// object. It is recommended that implementors do not call any update calls
    49  	// for the Kind inside of ReconcileKind, it is the responsibility of the calling
    50  	// controller to propagate those properties. The resource passed to ReconcileKind
    51  	// will always have an empty deletion timestamp.
    52  	ReconcileKind(ctx context.Context, o *v1.MutatingWebhookConfiguration) reconciler.Event
    53  }
    54  
    55  // Finalizer defines the strongly typed interfaces to be implemented by a
    56  // controller finalizing v1.MutatingWebhookConfiguration.
    57  type Finalizer interface {
    58  	// FinalizeKind implements custom logic to finalize v1.MutatingWebhookConfiguration. Any changes
    59  	// to the objects .Status or .Finalizers will be ignored. Returning a nil or
    60  	// Normal type reconciler.Event will allow the finalizer to be deleted on
    61  	// the resource. The resource passed to FinalizeKind will always have a set
    62  	// deletion timestamp.
    63  	FinalizeKind(ctx context.Context, o *v1.MutatingWebhookConfiguration) reconciler.Event
    64  }
    65  
    66  // ReadOnlyInterface defines the strongly typed interfaces to be implemented by a
    67  // controller reconciling v1.MutatingWebhookConfiguration if they want to process resources for which
    68  // they are not the leader.
    69  type ReadOnlyInterface interface {
    70  	// ObserveKind implements logic to observe v1.MutatingWebhookConfiguration.
    71  	// This method should not write to the API.
    72  	ObserveKind(ctx context.Context, o *v1.MutatingWebhookConfiguration) reconciler.Event
    73  }
    74  
    75  type doReconcile func(ctx context.Context, o *v1.MutatingWebhookConfiguration) reconciler.Event
    76  
    77  // reconcilerImpl implements controller.Reconciler for v1.MutatingWebhookConfiguration resources.
    78  type reconcilerImpl struct {
    79  	// LeaderAwareFuncs is inlined to help us implement reconciler.LeaderAware.
    80  	reconciler.LeaderAwareFuncs
    81  
    82  	// Client is used to write back status updates.
    83  	Client kubernetes.Interface
    84  
    85  	// Listers index properties about resources.
    86  	Lister admissionregistrationv1.MutatingWebhookConfigurationLister
    87  
    88  	// Recorder is an event recorder for recording Event resources to the
    89  	// Kubernetes API.
    90  	Recorder record.EventRecorder
    91  
    92  	// configStore allows for decorating a context with config maps.
    93  	// +optional
    94  	configStore reconciler.ConfigStore
    95  
    96  	// reconciler is the implementation of the business logic of the resource.
    97  	reconciler Interface
    98  
    99  	// finalizerName is the name of the finalizer to reconcile.
   100  	finalizerName string
   101  
   102  	// useServerSideApplyForFinalizers configures whether to use server-side apply for finalizer management
   103  	useServerSideApplyForFinalizers bool
   104  
   105  	// finalizerFieldManager is the field manager name for server-side apply of finalizers
   106  	finalizerFieldManager string
   107  
   108  	// forceApplyFinalizers configures whether to force server-side apply for finalizers
   109  	forceApplyFinalizers bool
   110  }
   111  
   112  // Check that our Reconciler implements controller.Reconciler.
   113  var _ controller.Reconciler = (*reconcilerImpl)(nil)
   114  
   115  // Check that our generated Reconciler is always LeaderAware.
   116  var _ reconciler.LeaderAware = (*reconcilerImpl)(nil)
   117  
   118  func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client kubernetes.Interface, lister admissionregistrationv1.MutatingWebhookConfigurationLister, recorder record.EventRecorder, r Interface, options ...controller.Options) controller.Reconciler {
   119  	// Check the options function input. It should be 0 or 1.
   120  	if len(options) > 1 {
   121  		logger.Fatal("Up to one options struct is supported, found: ", len(options))
   122  	}
   123  
   124  	// Fail fast when users inadvertently implement the other LeaderAware interface.
   125  	// For the typed reconcilers, Promote shouldn't take any arguments.
   126  	if _, ok := r.(reconciler.LeaderAware); ok {
   127  		logger.Fatalf("%T implements the incorrect LeaderAware interface. Promote() should not take an argument as genreconciler handles the enqueuing automatically.", r)
   128  	}
   129  
   130  	rec := &reconcilerImpl{
   131  		LeaderAwareFuncs: reconciler.LeaderAwareFuncs{
   132  			PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error {
   133  				all, err := lister.List(labels.Everything())
   134  				if err != nil {
   135  					return err
   136  				}
   137  				for _, elt := range all {
   138  					// TODO: Consider letting users specify a filter in options.
   139  					enq(bkt, types.NamespacedName{
   140  						Namespace: elt.GetNamespace(),
   141  						Name:      elt.GetName(),
   142  					})
   143  				}
   144  				return nil
   145  			},
   146  		},
   147  		Client:        client,
   148  		Lister:        lister,
   149  		Recorder:      recorder,
   150  		reconciler:    r,
   151  		finalizerName: defaultFinalizerName,
   152  	}
   153  
   154  	for _, opts := range options {
   155  		if opts.ConfigStore != nil {
   156  			rec.configStore = opts.ConfigStore
   157  		}
   158  		if opts.FinalizerName != "" {
   159  			rec.finalizerName = opts.FinalizerName
   160  		}
   161  		if opts.DemoteFunc != nil {
   162  			rec.DemoteFunc = opts.DemoteFunc
   163  		}
   164  		if opts.UseServerSideApplyForFinalizers {
   165  			if opts.FinalizerFieldManager == "" {
   166  				logger.Fatal("FinalizerFieldManager must be provided when UseServerSideApplyForFinalizers is enabled")
   167  			}
   168  			rec.useServerSideApplyForFinalizers = true
   169  			rec.finalizerFieldManager = opts.FinalizerFieldManager
   170  			rec.forceApplyFinalizers = opts.ForceApplyFinalizers
   171  		}
   172  	}
   173  
   174  	return rec
   175  }
   176  
   177  // Reconcile implements controller.Reconciler
   178  func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error {
   179  	logger := logging.FromContext(ctx)
   180  
   181  	// Initialize the reconciler state. This will convert the namespace/name
   182  	// string into a distinct namespace and name, determine if this instance of
   183  	// the reconciler is the leader, and any additional interfaces implemented
   184  	// by the reconciler. Returns an error is the resource key is invalid.
   185  	s, err := newState(key, r)
   186  	if err != nil {
   187  		logger.Error("Invalid resource key: ", key)
   188  		return nil
   189  	}
   190  
   191  	// If we are not the leader, and we don't implement either ReadOnly
   192  	// observer interfaces, then take a fast-path out.
   193  	if s.isNotLeaderNorObserver() {
   194  		return controller.NewSkipKey(key)
   195  	}
   196  
   197  	// If configStore is set, attach the frozen configuration to the context.
   198  	if r.configStore != nil {
   199  		ctx = r.configStore.ToContext(ctx)
   200  	}
   201  
   202  	// Add the recorder to context.
   203  	ctx = controller.WithEventRecorder(ctx, r.Recorder)
   204  
   205  	// Get the resource with this namespace/name.
   206  
   207  	getter := r.Lister
   208  
   209  	original, err := getter.Get(s.name)
   210  
   211  	if errors.IsNotFound(err) {
   212  		// The resource may no longer exist, in which case we stop processing and call
   213  		// the ObserveDeletion handler if appropriate.
   214  		logger.Debugf("Resource %q no longer exists", key)
   215  		if del, ok := r.reconciler.(reconciler.OnDeletionInterface); ok {
   216  			return del.ObserveDeletion(ctx, types.NamespacedName{
   217  				Namespace: s.namespace,
   218  				Name:      s.name,
   219  			})
   220  		}
   221  		return nil
   222  	} else if err != nil {
   223  		return err
   224  	}
   225  
   226  	// Don't modify the informers copy.
   227  	resource := original.DeepCopy()
   228  
   229  	var reconcileEvent reconciler.Event
   230  
   231  	name, do := s.reconcileMethodFor(resource)
   232  	// Append the target method to the logger.
   233  	logger = logger.With(zap.String("targetMethod", name))
   234  	switch name {
   235  	case reconciler.DoReconcileKind:
   236  		// Set and update the finalizer on resource if r.reconciler
   237  		// implements Finalizer.
   238  		if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil {
   239  			return fmt.Errorf("failed to set finalizers: %w", err)
   240  		}
   241  
   242  		// Reconcile this copy of the resource and then write back any status
   243  		// updates regardless of whether the reconciliation errored out.
   244  		reconcileEvent = do(ctx, resource)
   245  
   246  	case reconciler.DoFinalizeKind:
   247  		// For finalizing reconcilers, if this resource being marked for deletion
   248  		// and reconciled cleanly (nil or normal event), remove the finalizer.
   249  		reconcileEvent = do(ctx, resource)
   250  
   251  		if resource, err = r.clearFinalizer(ctx, resource, reconcileEvent); err != nil {
   252  			return fmt.Errorf("failed to clear finalizers: %w", err)
   253  		}
   254  
   255  	case reconciler.DoObserveKind:
   256  		// Observe any changes to this resource, since we are not the leader.
   257  		reconcileEvent = do(ctx, resource)
   258  
   259  	}
   260  
   261  	// Report the reconciler event, if any.
   262  	if reconcileEvent != nil {
   263  		var event *reconciler.ReconcilerEvent
   264  		if reconciler.EventAs(reconcileEvent, &event) {
   265  			logger.Infow("Returned an event", zap.Any("event", reconcileEvent))
   266  			r.Recorder.Event(resource, event.EventType, event.Reason, event.Error())
   267  
   268  			// the event was wrapped inside an error, consider the reconciliation as failed
   269  			if _, isEvent := reconcileEvent.(*reconciler.ReconcilerEvent); !isEvent {
   270  				return reconcileEvent
   271  			}
   272  			return nil
   273  		}
   274  
   275  		if controller.IsSkipKey(reconcileEvent) {
   276  			// This is a wrapped error, don't emit an event.
   277  		} else if ok, _ := controller.IsRequeueKey(reconcileEvent); ok {
   278  			// This is a wrapped error, don't emit an event.
   279  		} else {
   280  			logger.Errorw("Returned an error", zap.Error(reconcileEvent))
   281  			r.Recorder.Event(resource, corev1.EventTypeWarning, "InternalError", reconcileEvent.Error())
   282  		}
   283  		return reconcileEvent
   284  	}
   285  
   286  	return nil
   287  }
   288  
   289  // updateFinalizersFiltered will update the Finalizers of the resource.
   290  // TODO: this method could be generic and sync all finalizers. For now it only
   291  // updates defaultFinalizerName or its override.
   292  func (r *reconcilerImpl) updateFinalizersFiltered(ctx context.Context, resource *v1.MutatingWebhookConfiguration, desiredFinalizers sets.Set[string]) (*v1.MutatingWebhookConfiguration, error) {
   293  	if r.useServerSideApplyForFinalizers {
   294  		return r.updateFinalizersFilteredServerSideApply(ctx, resource, desiredFinalizers)
   295  	}
   296  	return r.updateFinalizersFilteredMergePatch(ctx, resource, desiredFinalizers)
   297  }
   298  
   299  // updateFinalizersFilteredServerSideApply uses server-side apply to manage only this controller's finalizer.
   300  func (r *reconcilerImpl) updateFinalizersFilteredServerSideApply(ctx context.Context, resource *v1.MutatingWebhookConfiguration, desiredFinalizers sets.Set[string]) (*v1.MutatingWebhookConfiguration, error) {
   301  	// Check if we need to do anything
   302  	existingFinalizers := sets.New[string](resource.Finalizers...)
   303  
   304  	var finalizers []string
   305  	if desiredFinalizers.Has(r.finalizerName) {
   306  		if existingFinalizers.Has(r.finalizerName) {
   307  			// Nothing to do.
   308  			return resource, nil
   309  		}
   310  		// Apply configuration with only our finalizer to add it.
   311  		finalizers = []string{r.finalizerName}
   312  	} else {
   313  		if !existingFinalizers.Has(r.finalizerName) {
   314  			// Nothing to do.
   315  			return resource, nil
   316  		}
   317  		// For removal, we apply an empty configuration for our finalizer field manager.
   318  		// This effectively removes our finalizer while preserving others.
   319  		finalizers = []string{} // Empty array removes our managed finalizers
   320  	}
   321  
   322  	// Determine GVK
   323  	gvks, _, err := scheme.Scheme.ObjectKinds(resource)
   324  	if err != nil || len(gvks) == 0 {
   325  		return resource, fmt.Errorf("failed to determine GVK for resource: %w", err)
   326  	}
   327  	gvk := gvks[0]
   328  
   329  	// Create apply configuration
   330  	applyConfig := map[string]interface{}{
   331  		"apiVersion": gvk.GroupVersion().String(),
   332  		"kind":       gvk.Kind,
   333  		"metadata": map[string]interface{}{
   334  			"name":       resource.Name,
   335  			"uid":        resource.UID,
   336  			"finalizers": finalizers,
   337  		},
   338  	}
   339  
   340  	patch, err := json.Marshal(applyConfig)
   341  	if err != nil {
   342  		return resource, err
   343  	}
   344  
   345  	patcher := r.Client.AdmissionregistrationV1().MutatingWebhookConfigurations()
   346  
   347  	patchOpts := metav1.PatchOptions{
   348  		FieldManager: r.finalizerFieldManager,
   349  		Force:        &r.forceApplyFinalizers,
   350  	}
   351  
   352  	updated, err := patcher.Patch(ctx, resource.Name, types.ApplyPatchType, patch, patchOpts)
   353  	if err != nil {
   354  		r.Recorder.Eventf(resource, corev1.EventTypeWarning, "FinalizerUpdateFailed",
   355  			"Failed to update finalizers for %q via server-side apply: %v", resource.Name, err)
   356  	} else {
   357  		r.Recorder.Eventf(updated, corev1.EventTypeNormal, "FinalizerUpdate",
   358  			"Updated finalizers for %q via server-side apply", resource.GetName())
   359  	}
   360  	return updated, err
   361  }
   362  
   363  // updateFinalizersFilteredMergePatch uses merge patch to manage finalizers (legacy behavior).
   364  func (r *reconcilerImpl) updateFinalizersFilteredMergePatch(ctx context.Context, resource *v1.MutatingWebhookConfiguration, desiredFinalizers sets.Set[string]) (*v1.MutatingWebhookConfiguration, error) {
   365  	// Don't modify the informers copy.
   366  	existing := resource.DeepCopy()
   367  
   368  	var finalizers []string
   369  
   370  	// If there's nothing to update, just return.
   371  	existingFinalizers := sets.New[string](existing.Finalizers...)
   372  
   373  	if desiredFinalizers.Has(r.finalizerName) {
   374  		if existingFinalizers.Has(r.finalizerName) {
   375  			// Nothing to do.
   376  			return resource, nil
   377  		}
   378  		// Add the finalizer.
   379  		finalizers = append(existing.Finalizers, r.finalizerName)
   380  	} else {
   381  		if !existingFinalizers.Has(r.finalizerName) {
   382  			// Nothing to do.
   383  			return resource, nil
   384  		}
   385  		// Remove the finalizer.
   386  		existingFinalizers.Delete(r.finalizerName)
   387  		finalizers = sets.List(existingFinalizers)
   388  	}
   389  
   390  	mergePatch := map[string]interface{}{
   391  		"metadata": map[string]interface{}{
   392  			"finalizers":      finalizers,
   393  			"resourceVersion": existing.ResourceVersion,
   394  		},
   395  	}
   396  
   397  	patch, err := json.Marshal(mergePatch)
   398  	if err != nil {
   399  		return resource, err
   400  	}
   401  
   402  	patcher := r.Client.AdmissionregistrationV1().MutatingWebhookConfigurations()
   403  
   404  	resourceName := resource.Name
   405  	updated, err := patcher.Patch(ctx, resourceName, types.MergePatchType, patch, metav1.PatchOptions{})
   406  	if err != nil {
   407  		r.Recorder.Eventf(existing, corev1.EventTypeWarning, "FinalizerUpdateFailed",
   408  			"Failed to update finalizers for %q: %v", resourceName, err)
   409  	} else {
   410  		r.Recorder.Eventf(updated, corev1.EventTypeNormal, "FinalizerUpdate",
   411  			"Updated %q finalizers", resource.GetName())
   412  	}
   413  	return updated, err
   414  }
   415  
   416  func (r *reconcilerImpl) setFinalizerIfFinalizer(ctx context.Context, resource *v1.MutatingWebhookConfiguration) (*v1.MutatingWebhookConfiguration, error) {
   417  	if _, ok := r.reconciler.(Finalizer); !ok {
   418  		return resource, nil
   419  	}
   420  
   421  	finalizers := sets.New[string](resource.Finalizers...)
   422  
   423  	// If this resource is not being deleted, mark the finalizer.
   424  	if resource.GetDeletionTimestamp().IsZero() {
   425  		finalizers.Insert(r.finalizerName)
   426  	}
   427  
   428  	// Synchronize the finalizers filtered by r.finalizerName.
   429  	return r.updateFinalizersFiltered(ctx, resource, finalizers)
   430  }
   431  
   432  func (r *reconcilerImpl) clearFinalizer(ctx context.Context, resource *v1.MutatingWebhookConfiguration, reconcileEvent reconciler.Event) (*v1.MutatingWebhookConfiguration, error) {
   433  	if _, ok := r.reconciler.(Finalizer); !ok {
   434  		return resource, nil
   435  	}
   436  	if resource.GetDeletionTimestamp().IsZero() {
   437  		return resource, nil
   438  	}
   439  
   440  	finalizers := sets.New[string](resource.Finalizers...)
   441  
   442  	if reconcileEvent != nil {
   443  		var event *reconciler.ReconcilerEvent
   444  		if reconciler.EventAs(reconcileEvent, &event) {
   445  			if event.EventType == corev1.EventTypeNormal {
   446  				finalizers.Delete(r.finalizerName)
   447  			}
   448  		}
   449  	} else {
   450  		finalizers.Delete(r.finalizerName)
   451  	}
   452  
   453  	// Synchronize the finalizers filtered by r.finalizerName.
   454  	updated, err := r.updateFinalizersFiltered(ctx, resource, finalizers)
   455  	if err != nil {
   456  		// Check if the resource still exists by querying the API server to avoid logging errors
   457  		// when reconciling stale object from cache while the object is actually deleted.
   458  		logger := logging.FromContext(ctx)
   459  
   460  		getter := r.Client.AdmissionregistrationV1().MutatingWebhookConfigurations()
   461  
   462  		_, getErr := getter.Get(ctx, resource.Name, metav1.GetOptions{})
   463  		if errors.IsNotFound(getErr) {
   464  			// Resource no longer exists, which could happen during deletion
   465  			logger.Debugw("Resource no longer exists while clearing finalizers",
   466  				"resource", resource.GetName(),
   467  				"namespace", resource.GetNamespace(),
   468  				"originalError", err)
   469  			// Return the original resource since the finalizer clearing is effectively complete
   470  			return resource, nil
   471  		}
   472  
   473  		// For other errors, return the original error
   474  		return updated, err
   475  	}
   476  
   477  	return updated, nil
   478  }