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