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 `