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