knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/codegen/cmd/injection-gen/generators/reconciler/reconciler_reconciler.go (about)

     1  /*
     2  Copyright 2020 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  package reconciler
    18  
    19  import (
    20  	"io"
    21  
    22  	clientgentypes "k8s.io/code-generator/cmd/client-gen/types"
    23  
    24  	"k8s.io/gengo/v2/generator"
    25  	"k8s.io/gengo/v2/namer"
    26  	"k8s.io/gengo/v2/types"
    27  	"k8s.io/klog/v2"
    28  )
    29  
    30  // reconcilerReconcilerGenerator produces a reconciler struct for the given type.
    31  type reconcilerReconcilerGenerator struct {
    32  	generator.GoGenerator
    33  	outputPackage  string
    34  	imports        namer.ImportTracker
    35  	typeToGenerate *types.Type
    36  	clientsetPkg   string
    37  	listerName     string
    38  	listerPkg      string
    39  
    40  	reconcilerClasses  []string
    41  	hasReconcilerClass bool
    42  	nonNamespaced      bool
    43  	isKRShaped         bool
    44  	hasStatus          bool
    45  
    46  	groupGoName  string
    47  	groupVersion clientgentypes.GroupVersion
    48  }
    49  
    50  var _ generator.Generator = (*reconcilerReconcilerGenerator)(nil)
    51  
    52  func (g *reconcilerReconcilerGenerator) Filter(c *generator.Context, t *types.Type) bool {
    53  	// Only process the type for this generator.
    54  	return t == g.typeToGenerate
    55  }
    56  
    57  func (g *reconcilerReconcilerGenerator) Namers(c *generator.Context) namer.NameSystems {
    58  	return namer.NameSystems{
    59  		"raw": namer.NewRawNamer(g.outputPackage, g.imports),
    60  	}
    61  }
    62  
    63  func (g *reconcilerReconcilerGenerator) Imports(c *generator.Context) (imports []string) {
    64  	imports = append(imports, g.imports.ImportLines()...)
    65  	return imports
    66  }
    67  
    68  func (g *reconcilerReconcilerGenerator) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
    69  	sw := generator.NewSnippetWriter(w, c, "{{", "}}")
    70  
    71  	klog.V(5).Info("processing type ", t)
    72  
    73  	m := map[string]interface{}{
    74  		"type":          t,
    75  		"group":         namer.IC(g.groupGoName),
    76  		"version":       namer.IC(g.groupVersion.Version.String()),
    77  		"classes":       g.reconcilerClasses,
    78  		"hasClass":      g.hasReconcilerClass,
    79  		"isKRShaped":    g.isKRShaped,
    80  		"hasStatus":     g.hasStatus,
    81  		"nonNamespaced": g.nonNamespaced,
    82  		"controllerImpl": c.Universe.Type(types.Name{
    83  			Package: "knative.dev/pkg/controller",
    84  			Name:    "Impl",
    85  		}),
    86  		"controllerReconciler": c.Universe.Type(types.Name{
    87  			Package: "knative.dev/pkg/controller",
    88  			Name:    "Reconciler",
    89  		}),
    90  		"controllerWithEventRecorder": c.Universe.Type(types.Name{
    91  			Package: "knative.dev/pkg/controller",
    92  			Name:    "WithEventRecorder",
    93  		}),
    94  		"controllerNewSkipKey": c.Universe.Type(types.Name{
    95  			Package: "knative.dev/pkg/controller",
    96  			Name:    "NewSkipKey",
    97  		}),
    98  		"corev1EventSource": c.Universe.Function(types.Name{
    99  			Package: "k8s.io/api/core/v1",
   100  			Name:    "EventSource",
   101  		}),
   102  		"corev1EventTypeNormal": c.Universe.Type(types.Name{
   103  			Package: "k8s.io/api/core/v1",
   104  			Name:    "EventTypeNormal",
   105  		}),
   106  		"corev1EventTypeWarning": c.Universe.Type(types.Name{
   107  			Package: "k8s.io/api/core/v1",
   108  			Name:    "EventTypeWarning",
   109  		}),
   110  		"reconcilerEvent":                c.Universe.Type(types.Name{Package: "knative.dev/pkg/reconciler", Name: "Event"}),
   111  		"reconcilerReconcilerEvent":      c.Universe.Type(types.Name{Package: "knative.dev/pkg/reconciler", Name: "ReconcilerEvent"}),
   112  		"reconcilerRetryUpdateConflicts": c.Universe.Function(types.Name{Package: "knative.dev/pkg/reconciler", Name: "RetryUpdateConflicts"}),
   113  		"reconcilerConfigStore":          c.Universe.Type(types.Name{Name: "ConfigStore", Package: "knative.dev/pkg/reconciler"}),
   114  		"reconcilerOnDeletionInterface":  c.Universe.Type(types.Name{Package: "knative.dev/pkg/reconciler", Name: "OnDeletionInterface"}),
   115  		// Deps
   116  		"clientsetInterface": c.Universe.Type(types.Name{Name: "Interface", Package: g.clientsetPkg}),
   117  		"resourceLister":     c.Universe.Type(types.Name{Name: g.listerName, Package: g.listerPkg}),
   118  		// K8s types
   119  		"recordEventRecorder": c.Universe.Type(types.Name{Name: "EventRecorder", Package: "k8s.io/client-go/tools/record"}),
   120  		// methods
   121  		"loggingFromContext": c.Universe.Function(types.Name{
   122  			Package: "knative.dev/pkg/logging",
   123  			Name:    "FromContext",
   124  		}),
   125  		"cacheSplitMetaNamespaceKey": c.Universe.Function(types.Name{
   126  			Package: "k8s.io/client-go/tools/cache",
   127  			Name:    "SplitMetaNamespaceKey",
   128  		}),
   129  		"retryRetryOnConflict": c.Universe.Function(types.Name{
   130  			Package: "k8s.io/client-go/util/retry",
   131  			Name:    "RetryOnConflict",
   132  		}),
   133  		"apierrsIsNotFound": c.Universe.Function(types.Name{
   134  			Package: "k8s.io/apimachinery/pkg/api/errors",
   135  			Name:    "IsNotFound",
   136  		}),
   137  		"apierrsIsConflict": c.Universe.Function(types.Name{
   138  			Package: "k8s.io/apimachinery/pkg/api/errors",
   139  			Name:    "IsConflict",
   140  		}),
   141  		"metav1GetOptions": c.Universe.Function(types.Name{
   142  			Package: "k8s.io/apimachinery/pkg/apis/meta/v1",
   143  			Name:    "GetOptions",
   144  		}),
   145  		"metav1PatchOptions": c.Universe.Function(types.Name{
   146  			Package: "k8s.io/apimachinery/pkg/apis/meta/v1",
   147  			Name:    "PatchOptions",
   148  		}),
   149  		"metav1UpdateOptions": c.Universe.Function(types.Name{
   150  			Package: "k8s.io/apimachinery/pkg/apis/meta/v1",
   151  			Name:    "UpdateOptions",
   152  		}),
   153  		"zapSugaredLogger": c.Universe.Type(types.Name{
   154  			Package: "go.uber.org/zap",
   155  			Name:    "SugaredLogger",
   156  		}),
   157  		"zapDebugLevel": c.Universe.Type(types.Name{
   158  			Package: "go.uber.org/zap/zapcore",
   159  			Name:    "DebugLevel",
   160  		}),
   161  		"setsNewString": c.Universe.Function(types.Name{
   162  			Package: "k8s.io/apimachinery/pkg/util/sets",
   163  			Name:    "New[string]",
   164  		}),
   165  		"setsString": c.Universe.Type(types.Name{
   166  			Package: "k8s.io/apimachinery/pkg/util/sets",
   167  			Name:    "Set[string]",
   168  		}),
   169  		"controllerOptions": c.Universe.Type(types.Name{
   170  			Package: "knative.dev/pkg/controller",
   171  			Name:    "Options",
   172  		}),
   173  		"contextContext": c.Universe.Type(types.Name{
   174  			Package: "context",
   175  			Name:    "Context",
   176  		}),
   177  		"kmpSafeDiff": c.Universe.Function(types.Name{
   178  			Package: "knative.dev/pkg/kmp",
   179  			Name:    "SafeDiff",
   180  		}),
   181  		"fmtErrorf":           c.Universe.Package("fmt").Function("Errorf"),
   182  		"equalitySemantic":    c.Universe.Package("k8s.io/apimachinery/pkg/api/equality").Variable("Semantic"),
   183  		"jsonMarshal":         c.Universe.Package("encoding/json").Function("Marshal"),
   184  		"typesMergePatchType": c.Universe.Package("k8s.io/apimachinery/pkg/types").Constant("MergePatchType"),
   185  		"typesApplyPatchType": c.Universe.Package("k8s.io/apimachinery/pkg/types").Constant("ApplyPatchType"),
   186  		"schemeScheme": c.Universe.Function(types.Name{
   187  			Package: "k8s.io/client-go/kubernetes/scheme",
   188  			Name:    "Scheme",
   189  		}),
   190  		"syncRWMutex": c.Universe.Type(types.Name{
   191  			Package: "sync",
   192  			Name:    "RWMutex",
   193  		}),
   194  		"reconcilerLeaderAware": c.Universe.Type(types.Name{
   195  			Package: "knative.dev/pkg/reconciler",
   196  			Name:    "LeaderAware",
   197  		}),
   198  		"reconcilerLeaderAwareFuncs": c.Universe.Type(types.Name{
   199  			Package: "knative.dev/pkg/reconciler",
   200  			Name:    "LeaderAwareFuncs",
   201  		}),
   202  		"reconcilerBucket": c.Universe.Type(types.Name{
   203  			Package: "knative.dev/pkg/reconciler",
   204  			Name:    "Bucket",
   205  		}),
   206  		"typesNamespacedName": c.Universe.Type(types.Name{
   207  			Package: "k8s.io/apimachinery/pkg/types",
   208  			Name:    "NamespacedName",
   209  		}),
   210  		"labelsEverything": c.Universe.Function(types.Name{
   211  			Package: "k8s.io/apimachinery/pkg/labels",
   212  			Name:    "Everything",
   213  		}),
   214  		"doReconcileKind": c.Universe.Type(types.Name{
   215  			Package: "knative.dev/pkg/reconciler",
   216  			Name:    "DoReconcileKind",
   217  		}),
   218  		"doObserveKind": c.Universe.Type(types.Name{
   219  			Package: "knative.dev/pkg/reconciler",
   220  			Name:    "DoObserveKind",
   221  		}),
   222  		"doFinalizeKind": c.Universe.Type(types.Name{
   223  			Package: "knative.dev/pkg/reconciler",
   224  			Name:    "DoFinalizeKind",
   225  		}),
   226  		"controllerIsSkipKey": c.Universe.Function(types.Name{
   227  			Package: "knative.dev/pkg/controller",
   228  			Name:    "IsSkipKey",
   229  		}),
   230  		"controllerIsRequeueKey": c.Universe.Function(types.Name{
   231  			Package: "knative.dev/pkg/controller",
   232  			Name:    "IsRequeueKey",
   233  		}),
   234  	}
   235  
   236  	sw.Do(reconcilerInterfaceFactory, m)
   237  	sw.Do(reconcilerNewReconciler, m)
   238  	sw.Do(reconcilerImplFactory, m)
   239  	if len(g.reconcilerClasses) > 1 {
   240  		sw.Do(reconcilerLookupClass, m)
   241  	}
   242  	if g.hasStatus {
   243  		sw.Do(reconcilerStatusFactory, m)
   244  	}
   245  	sw.Do(reconcilerFinalizerFactory, m)
   246  
   247  	return sw.Error()
   248  }
   249  
   250  var reconcilerLookupClass = `
   251  func lookupClass(annotations map[string]string) (string, bool) {
   252  	for _, key := range ClassAnnotationKeys {
   253  		 if val, ok := annotations[key]; ok {
   254  		   return val, true
   255  		 }
   256  	}
   257  	return "", false
   258  }
   259  `
   260  
   261  var reconcilerInterfaceFactory = `
   262  // Interface defines the strongly typed interfaces to be implemented by a
   263  // controller reconciling {{.type|raw}}.
   264  type Interface interface {
   265  	// ReconcileKind implements custom logic to reconcile {{.type|raw}}. Any changes
   266  	// to the objects .Status or .Finalizers will be propagated to the stored
   267  	// object. It is recommended that implementors do not call any update calls
   268  	// for the Kind inside of ReconcileKind, it is the responsibility of the calling
   269  	// controller to propagate those properties. The resource passed to ReconcileKind
   270   	// will always have an empty deletion timestamp.
   271  	ReconcileKind(ctx {{.contextContext|raw}}, o *{{.type|raw}}) {{.reconcilerEvent|raw}}
   272  }
   273  
   274  // Finalizer defines the strongly typed interfaces to be implemented by a
   275  // controller finalizing {{.type|raw}}.
   276  type Finalizer interface {
   277  	// FinalizeKind implements custom logic to finalize {{.type|raw}}. Any changes
   278  	// to the objects .Status or .Finalizers will be ignored. Returning a nil or
   279  	// Normal type {{.reconcilerEvent|raw}} will allow the finalizer to be deleted on
   280  	// the resource. The resource passed to FinalizeKind will always have a set
   281  	// deletion timestamp.
   282  	FinalizeKind(ctx {{.contextContext|raw}}, o *{{.type|raw}}) {{.reconcilerEvent|raw}}
   283  }
   284  
   285  // ReadOnlyInterface defines the strongly typed interfaces to be implemented by a
   286  // controller reconciling {{.type|raw}} if they want to process resources for which
   287  // they are not the leader.
   288  type ReadOnlyInterface interface {
   289  	// ObserveKind implements logic to observe {{.type|raw}}.
   290  	// This method should not write to the API.
   291  	ObserveKind(ctx {{.contextContext|raw}}, o *{{.type|raw}}) {{.reconcilerEvent|raw}}
   292  }
   293  
   294  type doReconcile func(ctx {{.contextContext|raw}}, o *{{.type|raw}}) {{.reconcilerEvent|raw}}
   295  
   296  // reconcilerImpl implements controller.Reconciler for {{.type|raw}} resources.
   297  type reconcilerImpl struct {
   298  	// LeaderAwareFuncs is inlined to help us implement {{.reconcilerLeaderAware|raw}}.
   299  	{{.reconcilerLeaderAwareFuncs|raw}}
   300  
   301  	// Client is used to write back status updates.
   302  	Client {{.clientsetInterface|raw}}
   303  
   304  	// Listers index properties about resources.
   305  	Lister {{.resourceLister|raw}}
   306  
   307  	// Recorder is an event recorder for recording Event resources to the
   308  	// Kubernetes API.
   309  	Recorder {{.recordEventRecorder|raw}}
   310  
   311  	// configStore allows for decorating a context with config maps.
   312  	// +optional
   313  	configStore {{.reconcilerConfigStore|raw}}
   314  
   315  	// reconciler is the implementation of the business logic of the resource.
   316  	reconciler Interface
   317  
   318  	// finalizerName is the name of the finalizer to reconcile.
   319  	finalizerName string
   320  
   321  	// useServerSideApplyForFinalizers configures whether to use server-side apply for finalizer management
   322  	useServerSideApplyForFinalizers bool
   323  
   324  	// finalizerFieldManager is the field manager name for server-side apply of finalizers
   325  	finalizerFieldManager string
   326  
   327  	// forceApplyFinalizers configures whether to force server-side apply for finalizers
   328  	forceApplyFinalizers bool
   329  
   330  	{{if .hasStatus}}
   331  	// skipStatusUpdates configures whether or not this reconciler automatically updates
   332  	// the status of the reconciled resource.
   333  	skipStatusUpdates bool
   334  	{{end}}
   335  
   336  	{{if len .classes | eq 1 }}
   337  	// classValue is the resource annotation[{{ index .classes 0 }}] instance value this reconciler instance filters on.
   338  	classValue string
   339  	{{else if gt (len .classes) 1 }}
   340  	// classValue is the resource annotation instance value this reconciler instance filters on.
   341  	// The annotations key are:
   342  	{{- range $class := .classes}}
   343  	//   {{$class}}
   344  	{{- end}}
   345  	classValue string
   346  	{{end}}
   347  }
   348  
   349  // Check that our Reconciler implements controller.Reconciler.
   350  var _ controller.Reconciler = (*reconcilerImpl)(nil)
   351  // Check that our generated Reconciler is always LeaderAware.
   352  var _ {{.reconcilerLeaderAware|raw}}  = (*reconcilerImpl)(nil)
   353  
   354  `
   355  
   356  var reconcilerNewReconciler = `
   357  func NewReconciler(ctx {{.contextContext|raw}}, logger *{{.zapSugaredLogger|raw}}, client {{.clientsetInterface|raw}}, lister {{.resourceLister|raw}}, recorder {{.recordEventRecorder|raw}}, r Interface{{if .hasClass}}, classValue string{{end}}, options ...{{.controllerOptions|raw}} ) {{.controllerReconciler|raw}} {
   358  	// Check the options function input. It should be 0 or 1.
   359  	if len(options) > 1 {
   360  		logger.Fatal("Up to one options struct is supported, found: ", len(options))
   361  	}
   362  
   363  	// Fail fast when users inadvertently implement the other LeaderAware interface.
   364  	// For the typed reconcilers, Promote shouldn't take any arguments.
   365  	if _, ok := r.({{.reconcilerLeaderAware|raw}}); ok {
   366  		logger.Fatalf("%T implements the incorrect LeaderAware interface. Promote() should not take an argument as genreconciler handles the enqueuing automatically.", r)
   367  	}
   368  
   369  	rec := &reconcilerImpl{
   370  		LeaderAwareFuncs: {{.reconcilerLeaderAwareFuncs|raw}}{
   371  			PromoteFunc: func(bkt {{.reconcilerBucket|raw}}, enq func({{.reconcilerBucket|raw}}, {{.typesNamespacedName|raw}})) error {
   372  				all, err := lister.List({{.labelsEverything|raw}}())
   373  				if err != nil {
   374  					return err
   375  				}
   376  				for _, elt := range all {
   377  					// TODO: Consider letting users specify a filter in options.
   378  					enq(bkt, {{.typesNamespacedName|raw}}{
   379  						Namespace: elt.GetNamespace(),
   380  						Name: elt.GetName(),
   381  					})
   382  				}
   383  				return nil
   384  			},
   385  		},
   386  		Client: client,
   387  		Lister: lister,
   388  		Recorder: recorder,
   389  		reconciler:    r,
   390  		finalizerName: defaultFinalizerName,
   391  		{{if .hasClass}}classValue: classValue,{{end}}
   392  	}
   393  
   394  	for _, opts := range options {
   395  		if opts.ConfigStore != nil {
   396  			rec.configStore = opts.ConfigStore
   397  		}
   398  		if opts.FinalizerName != "" {
   399  			rec.finalizerName = opts.FinalizerName
   400  		}
   401  		{{- if .hasStatus}}
   402  		if opts.SkipStatusUpdates {
   403  			rec.skipStatusUpdates = true
   404  		}
   405  		{{- end}}
   406  		if opts.DemoteFunc != nil {
   407  			rec.DemoteFunc = opts.DemoteFunc
   408  		}
   409  		if opts.UseServerSideApplyForFinalizers {
   410  			if opts.FinalizerFieldManager == "" {
   411  				logger.Fatal("FinalizerFieldManager must be provided when UseServerSideApplyForFinalizers is enabled")
   412  			}
   413  			rec.useServerSideApplyForFinalizers = true
   414  			rec.finalizerFieldManager = opts.FinalizerFieldManager
   415  			rec.forceApplyFinalizers = opts.ForceApplyFinalizers
   416  		}
   417  	}
   418  
   419  	return rec
   420  }
   421  `
   422  
   423  var reconcilerImplFactory = `
   424  // Reconcile implements controller.Reconciler
   425  func (r *reconcilerImpl) Reconcile(ctx {{.contextContext|raw}}, key string) error {
   426  	logger := {{.loggingFromContext|raw}}(ctx)
   427  
   428  	// Initialize the reconciler state. This will convert the namespace/name
   429  	// string into a distinct namespace and name, determine if this instance of
   430  	// the reconciler is the leader, and any additional interfaces implemented
   431  	// by the reconciler. Returns an error is the resource key is invalid.
   432  	s, err := newState(key, r)
   433  	if err != nil {
   434  		logger.Error("Invalid resource key: ", key)
   435  		return nil
   436  	}
   437  
   438  	// If we are not the leader, and we don't implement either ReadOnly
   439  	// observer interfaces, then take a fast-path out.
   440  	if s.isNotLeaderNorObserver() {
   441  		return {{.controllerNewSkipKey|raw}}(key)
   442  	}
   443  
   444  	// If configStore is set, attach the frozen configuration to the context.
   445  	if r.configStore != nil {
   446  		ctx = r.configStore.ToContext(ctx)
   447  	}
   448  
   449  	// Add the recorder to context.
   450  	ctx = {{.controllerWithEventRecorder|raw}}(ctx, r.Recorder)
   451  
   452  	// Get the resource with this namespace/name.
   453  	{{if .nonNamespaced}}
   454  	getter := r.Lister
   455  	{{else}}
   456  	getter := r.Lister.{{.type|apiGroup}}(s.namespace)
   457  	{{end}}
   458  	original, err := getter.Get(s.name)
   459  
   460  	if {{.apierrsIsNotFound|raw}}(err) {
   461  		// The resource may no longer exist, in which case we stop processing and call
   462  		// the ObserveDeletion handler if appropriate.
   463  		logger.Debugf("Resource %q no longer exists", key)
   464  		if del, ok := r.reconciler.({{.reconcilerOnDeletionInterface|raw}}); ok {
   465  			return del.ObserveDeletion(ctx, {{.typesNamespacedName|raw}}{
   466  				Namespace: s.namespace,
   467  				Name: s.name,
   468  			})
   469  		}
   470  		return nil
   471  	} else if err != nil {
   472  		return err
   473  	}
   474  
   475  {{if len .classes | eq 1 }}
   476  	if classValue, found := original.GetAnnotations()[ClassAnnotationKey]; !found || classValue != r.classValue {
   477  		logger.Debugw("Skip reconciling resource, class annotation value does not match reconciler instance value.",
   478  			zap.String("classKey", ClassAnnotationKey),
   479  			zap.String("issue", classValue+"!="+r.classValue))
   480  		return nil
   481  	}
   482  {{else if gt (len .classes) 1 }}
   483  	if classValue, found := lookupClass(original.GetAnnotations()); !found || classValue != r.classValue {
   484  		logger.Debugw("Skip reconciling resource, class annotation value does not match reconciler instance value.",
   485  			zap.Strings("classKeys", ClassAnnotationKeys),
   486  			zap.String("issue", classValue+"!="+r.classValue))
   487  		return nil
   488  	}
   489  {{end}}
   490  
   491  	// Don't modify the informers copy.
   492  	resource := original.DeepCopy()
   493  
   494  	var reconcileEvent {{.reconcilerEvent|raw}}
   495  
   496  	name, do := s.reconcileMethodFor(resource)
   497  	// Append the target method to the logger.
   498  	logger = logger.With(zap.String("targetMethod", name))
   499  	switch name {
   500  	case {{.doReconcileKind|raw}}:
   501  		// Set and update the finalizer on resource if r.reconciler
   502  		// implements Finalizer.
   503  		if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil {
   504  			return {{.fmtErrorf|raw}}("failed to set finalizers: %w", err)
   505  		}
   506  		{{if .isKRShaped}}
   507  		if !r.skipStatusUpdates {
   508  			reconciler.PreProcessReconcile(ctx, resource)
   509  		}
   510  		{{end}}
   511  
   512  		// Reconcile this copy of the resource and then write back any status
   513  		// updates regardless of whether the reconciliation errored out.
   514  		reconcileEvent = do(ctx, resource)
   515  
   516  		{{if .isKRShaped}}
   517  		if !r.skipStatusUpdates {
   518  			reconciler.PostProcessReconcile(ctx, resource, original)
   519  		}
   520  		{{end}}
   521  
   522  	case {{.doFinalizeKind|raw}}:
   523  		// For finalizing reconcilers, if this resource being marked for deletion
   524  		// and reconciled cleanly (nil or normal event), remove the finalizer.
   525  		reconcileEvent = do(ctx, resource)
   526  
   527  		if resource, err = r.clearFinalizer(ctx, resource, reconcileEvent); err != nil {
   528  			return {{.fmtErrorf|raw}}("failed to clear finalizers: %w", err)
   529  		}
   530  
   531  	case {{.doObserveKind|raw}}:
   532  		// Observe any changes to this resource, since we are not the leader.
   533  		reconcileEvent = do(ctx, resource)
   534  
   535  	}
   536  
   537  	{{if .hasStatus}}
   538  	// Synchronize the status.
   539  	switch {
   540  	case r.skipStatusUpdates:
   541  		// This reconciler implementation is configured to skip resource updates.
   542  		// This may mean this reconciler does not observe spec, but reconciles external changes.
   543  	case {{.equalitySemantic|raw}}.DeepEqual(original.Status, resource.Status):
   544  		// If we didn't change anything then don't call updateStatus.
   545  		// This is important because the copy we loaded from the injectionInformer's
   546  		// cache may be stale and we don't want to overwrite a prior update
   547  		// to status with this stale state.
   548  	case !s.isLeader:
   549  		// High-availability reconcilers may have many replicas watching the resource, but only
   550  		// the elected leader is expected to write modifications.
   551  		logger.Warn("Saw status changes when we aren't the leader!")
   552  	default:
   553  		if err = r.updateStatus(ctx, logger, original, resource); err != nil {
   554  			logger.Warnw("Failed to update resource status", zap.Error(err))
   555  			r.Recorder.Eventf(resource, {{.corev1EventTypeWarning|raw}}, "UpdateFailed",
   556  				"Failed to update status for %q: %v", resource.Name, err)
   557  			return err
   558  		}
   559  	}
   560  	{{end}}
   561  
   562  	// Report the reconciler event, if any.
   563  	if reconcileEvent != nil {
   564  		var event *{{.reconcilerReconcilerEvent|raw}}
   565  		if reconciler.EventAs(reconcileEvent, &event) {
   566  			logger.Infow("Returned an event", zap.Any("event", reconcileEvent))
   567  			r.Recorder.Event(resource, event.EventType, event.Reason, event.Error())
   568  
   569  			// the event was wrapped inside an error, consider the reconciliation as failed
   570  			if _, isEvent := reconcileEvent.(*reconciler.ReconcilerEvent); !isEvent {
   571  				return reconcileEvent
   572  			}
   573  			return nil
   574  		}
   575  
   576  		if {{ .controllerIsSkipKey|raw }}(reconcileEvent) {
   577  			// This is a wrapped error, don't emit an event.
   578  		} else if ok, _ := {{ .controllerIsRequeueKey|raw }}(reconcileEvent); ok {
   579  			// This is a wrapped error, don't emit an event.
   580  		} else if {{ .apierrsIsConflict|raw }}(reconcileEvent) {
   581  			// Conflict errors are expected, don't emit an event.
   582  		} else {
   583  			logger.Errorw("Returned an error", zap.Error(reconcileEvent))
   584  			r.Recorder.Event(resource, {{.corev1EventTypeWarning|raw}}, "InternalError", reconcileEvent.Error())
   585  		}
   586  		return reconcileEvent
   587  	}
   588  
   589  	return nil
   590  }
   591  `
   592  
   593  var reconcilerStatusFactory = `
   594  func (r *reconcilerImpl) updateStatus(ctx {{.contextContext|raw}}, logger *{{.zapSugaredLogger|raw}}, existing *{{.type|raw}}, desired *{{.type|raw}}) error {
   595  	existing = existing.DeepCopy()
   596  	return {{.reconcilerRetryUpdateConflicts|raw}}(func(attempts int) (err error) {
   597  		// The first iteration tries to use the injectionInformer's state, subsequent attempts fetch the latest state via API.
   598  		if attempts > 0 {
   599  			{{if .nonNamespaced}}
   600  			getter := r.Client.{{.group}}{{.version}}().{{.type|apiGroup}}()
   601  			{{else}}
   602  			getter := r.Client.{{.group}}{{.version}}().{{.type|apiGroup}}(desired.Namespace)
   603  			{{end}}
   604  			existing, err = getter.Get(ctx, desired.Name, {{.metav1GetOptions|raw}}{})
   605  			if err != nil {
   606  				return err
   607  			}
   608  		}
   609  
   610  		// If there's nothing to update, just return.
   611  		if {{.equalitySemantic|raw}}.DeepEqual(existing.Status, desired.Status) {
   612  			return nil
   613  		}
   614  
   615  		if logger.Desugar().Core().Enabled({{.zapDebugLevel|raw}}) {
   616  			if diff, err := {{.kmpSafeDiff|raw}}(existing.Status, desired.Status); err == nil && diff != "" {
   617  				logger.Debug("Updating status with: ", diff)
   618  			}
   619  		}
   620  
   621  		existing.Status = desired.Status
   622  
   623  		{{if .nonNamespaced}}
   624  		updater := r.Client.{{.group}}{{.version}}().{{.type|apiGroup}}()
   625  		{{else}}
   626  		updater := r.Client.{{.group}}{{.version}}().{{.type|apiGroup}}(existing.Namespace)
   627  		{{end}}
   628  		_, err = updater.UpdateStatus(ctx, existing, {{.metav1UpdateOptions|raw}}{})
   629  		return err
   630  	})
   631  }
   632  `
   633  
   634  var reconcilerFinalizerFactory = `
   635  // updateFinalizersFiltered will update the Finalizers of the resource.
   636  // TODO: this method could be generic and sync all finalizers. For now it only
   637  // updates defaultFinalizerName or its override.
   638  func (r *reconcilerImpl) updateFinalizersFiltered(ctx {{.contextContext|raw}}, resource *{{.type|raw}}, desiredFinalizers {{.setsString|raw}}) (*{{.type|raw}}, error) {
   639  	if r.useServerSideApplyForFinalizers {
   640  		return r.updateFinalizersFilteredServerSideApply(ctx, resource, desiredFinalizers)
   641  	}
   642  	return r.updateFinalizersFilteredMergePatch(ctx, resource, desiredFinalizers)
   643  }
   644  
   645  // updateFinalizersFilteredServerSideApply uses server-side apply to manage only this controller's finalizer.
   646  func (r *reconcilerImpl) updateFinalizersFilteredServerSideApply(ctx {{.contextContext|raw}}, resource *{{.type|raw}}, desiredFinalizers {{.setsString|raw}}) (*{{.type|raw}}, error) {
   647  	// Check if we need to do anything
   648  	existingFinalizers := {{.setsNewString|raw}}(resource.Finalizers...)
   649  
   650  	var finalizers []string
   651  	if desiredFinalizers.Has(r.finalizerName) {
   652  		if existingFinalizers.Has(r.finalizerName) {
   653  			// Nothing to do.
   654  			return resource, nil
   655  		}
   656  		// Apply configuration with only our finalizer to add it.
   657  		finalizers = []string{r.finalizerName}
   658  	} else {
   659  		if !existingFinalizers.Has(r.finalizerName) {
   660  			// Nothing to do.
   661  			return resource, nil
   662  		}
   663  		// For removal, we apply an empty configuration for our finalizer field manager.
   664  		// This effectively removes our finalizer while preserving others.
   665  		finalizers = []string{} // Empty array removes our managed finalizers
   666  	}
   667  
   668  	// Determine GVK
   669  	gvks, _, err := {{.schemeScheme|raw}}.ObjectKinds(resource)
   670  	if err != nil || len(gvks) == 0 {
   671  		return resource, {{.fmtErrorf|raw}}("failed to determine GVK for resource: %w", err)
   672  	}
   673  	gvk := gvks[0]
   674  
   675  	// Create apply configuration
   676  	applyConfig := map[string]interface{}{
   677  		"apiVersion": gvk.GroupVersion().String(),
   678  		"kind":       gvk.Kind,
   679  		"metadata": map[string]interface{}{
   680  			"name":       resource.Name,
   681  			"uid":        resource.UID,
   682  			"finalizers": finalizers,
   683  		},
   684  	}
   685  	{{if not .nonNamespaced}}
   686  	applyConfig["metadata"].(map[string]interface{})["namespace"] = resource.Namespace
   687  	{{end}}
   688  
   689  	patch, err := {{.jsonMarshal|raw}}(applyConfig)
   690  	if err != nil {
   691  		return resource, err
   692  	}
   693  
   694  	{{if .nonNamespaced}}
   695  	patcher := r.Client.{{.group}}{{.version}}().{{.type|apiGroup}}()
   696  	{{else}}
   697  	patcher := r.Client.{{.group}}{{.version}}().{{.type|apiGroup}}(resource.Namespace)
   698  	{{end}}
   699  
   700  	patchOpts := {{.metav1PatchOptions|raw}}{
   701  		FieldManager:    r.finalizerFieldManager,
   702  		Force:           &r.forceApplyFinalizers,
   703  	}
   704  
   705  	updated, err := patcher.Patch(ctx, resource.Name, {{.typesApplyPatchType|raw}}, patch, patchOpts)
   706  	if err != nil {
   707  		if !{{ .apierrsIsConflict|raw }}(err) {
   708  			r.Recorder.Eventf(resource, {{.corev1EventTypeWarning|raw}}, "FinalizerUpdateFailed",
   709  				"Failed to update finalizers for %q via server-side apply: %v", resource.Name, err)
   710  		}
   711  	} else {
   712  		r.Recorder.Eventf(updated, {{.corev1EventTypeNormal|raw}}, "FinalizerUpdate",
   713  			"Updated finalizers for %q via server-side apply", resource.GetName())
   714  	}
   715  	return updated, err
   716  }
   717  
   718  // updateFinalizersFilteredMergePatch uses merge patch to manage finalizers (legacy behavior).
   719  func (r *reconcilerImpl) updateFinalizersFilteredMergePatch(ctx {{.contextContext|raw}}, resource *{{.type|raw}}, desiredFinalizers {{.setsString|raw}}) (*{{.type|raw}}, error) {
   720  	// Don't modify the informers copy.
   721  	existing := resource.DeepCopy()
   722  
   723  	var finalizers []string
   724  
   725  	// If there's nothing to update, just return.
   726  	existingFinalizers := {{.setsNewString|raw}}(existing.Finalizers...)
   727  
   728  	if desiredFinalizers.Has(r.finalizerName) {
   729  		if existingFinalizers.Has(r.finalizerName) {
   730  			// Nothing to do.
   731  			return resource, nil
   732  		}
   733  		// Add the finalizer.
   734  		finalizers = append(existing.Finalizers, r.finalizerName)
   735  	} else {
   736  		if !existingFinalizers.Has(r.finalizerName) {
   737  			// Nothing to do.
   738  			return resource, nil
   739  		}
   740  		// Remove the finalizer.
   741  		existingFinalizers.Delete(r.finalizerName)
   742  		finalizers = sets.List(existingFinalizers)
   743  	}
   744  
   745  	mergePatch := map[string]interface{}{
   746  		"metadata": map[string]interface{}{
   747  			"finalizers":      finalizers,
   748  			"resourceVersion": existing.ResourceVersion,
   749  		},
   750  	}
   751  
   752  	patch, err := {{.jsonMarshal|raw}}(mergePatch)
   753  	if err != nil {
   754  		return resource, err
   755  	}
   756  
   757  	{{if .nonNamespaced}}
   758  	patcher := r.Client.{{.group}}{{.version}}().{{.type|apiGroup}}()
   759  	{{else}}
   760  	patcher := r.Client.{{.group}}{{.version}}().{{.type|apiGroup}}(resource.Namespace)
   761  	{{end}}
   762  	resourceName := resource.Name
   763  	updated, err := patcher.Patch(ctx, resourceName, {{.typesMergePatchType|raw}}, patch, {{.metav1PatchOptions|raw}}{})
   764  	if err != nil {
   765  		if !{{ .apierrsIsConflict|raw }}(err) {
   766  			r.Recorder.Eventf(existing, {{.corev1EventTypeWarning|raw}}, "FinalizerUpdateFailed",
   767  				"Failed to update finalizers for %q: %v", resourceName, err)
   768  		}
   769  	} else {
   770  		r.Recorder.Eventf(updated, {{.corev1EventTypeNormal|raw}}, "FinalizerUpdate",
   771  			"Updated %q finalizers", resource.GetName())
   772  	}
   773  	return updated, err
   774  }
   775  
   776  func (r *reconcilerImpl) setFinalizerIfFinalizer(ctx {{.contextContext|raw}}, resource *{{.type|raw}}) (*{{.type|raw}}, error) {
   777  	if _, ok := r.reconciler.(Finalizer); !ok {
   778  		return resource, nil
   779  	}
   780  
   781  	finalizers := {{.setsNewString|raw}}(resource.Finalizers...)
   782  
   783  	// If this resource is not being deleted, mark the finalizer.
   784  	if resource.GetDeletionTimestamp().IsZero() {
   785  		finalizers.Insert(r.finalizerName)
   786  	}
   787  
   788  	// Synchronize the finalizers filtered by r.finalizerName.
   789  	return r.updateFinalizersFiltered(ctx, resource, finalizers)
   790  }
   791  
   792  func (r *reconcilerImpl) clearFinalizer(ctx {{.contextContext|raw}}, resource *{{.type|raw}}, reconcileEvent {{.reconcilerEvent|raw}}) (*{{.type|raw}}, error) {
   793  	if _, ok := r.reconciler.(Finalizer); !ok {
   794  		return resource, nil
   795  	}
   796  	if resource.GetDeletionTimestamp().IsZero() {
   797  		return resource, nil
   798  	}
   799  
   800  	finalizers := {{.setsNewString|raw}}(resource.Finalizers...)
   801  
   802  	if reconcileEvent != nil {
   803  		var event *{{.reconcilerReconcilerEvent|raw}}
   804  		if reconciler.EventAs(reconcileEvent, &event) {
   805  			if event.EventType == {{.corev1EventTypeNormal|raw}} {
   806  				finalizers.Delete(r.finalizerName)
   807  			}
   808  		}
   809  	} else {
   810  		finalizers.Delete(r.finalizerName)
   811  	}
   812  
   813  	// Synchronize the finalizers filtered by r.finalizerName.
   814  	updated, err := r.updateFinalizersFiltered(ctx, resource, finalizers)
   815  	if err != nil {
   816  		// Check if the resource still exists by querying the API server to avoid logging errors
   817  		// when reconciling stale object from cache while the object is actually deleted.
   818  		logger := {{.loggingFromContext|raw}}(ctx)
   819  
   820  		{{if .nonNamespaced}}
   821  		getter := r.Client.{{.group}}{{.version}}().{{.type|apiGroup}}()
   822  		{{else}}
   823  		getter := r.Client.{{.group}}{{.version}}().{{.type|apiGroup}}(resource.Namespace)
   824  		{{end}}
   825  
   826  		_, getErr := getter.Get(ctx, resource.Name, {{.metav1GetOptions|raw}}{})
   827  		if {{.apierrsIsNotFound|raw}}(getErr) {
   828  			// Resource no longer exists, which could happen during deletion
   829  			logger.Debugw("Resource no longer exists while clearing finalizers",
   830  				"resource", resource.GetName(),
   831  				"namespace", resource.GetNamespace(),
   832  				"originalError", err)
   833  			// Return the original resource since the finalizer clearing is effectively complete
   834  			return resource, nil
   835  		}
   836  
   837  		// For other errors, return the original error
   838  		return updated, err
   839  	}
   840  
   841  	return updated, nil
   842  }
   843  
   844  `