knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/client/injection/kube/reconciler/batch/v1beta1/cronjob/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 cronjob 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/batch/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 batchv1beta1 "k8s.io/client-go/listers/batch/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.CronJob. 48 type Interface interface { 49 // ReconcileKind implements custom logic to reconcile v1beta1.CronJob. 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.CronJob) reconciler.Event 56 } 57 58 // Finalizer defines the strongly typed interfaces to be implemented by a 59 // controller finalizing v1beta1.CronJob. 60 type Finalizer interface { 61 // FinalizeKind implements custom logic to finalize v1beta1.CronJob. 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.CronJob) reconciler.Event 67 } 68 69 // ReadOnlyInterface defines the strongly typed interfaces to be implemented by a 70 // controller reconciling v1beta1.CronJob 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.CronJob. 74 // This method should not write to the API. 75 ObserveKind(ctx context.Context, o *v1beta1.CronJob) reconciler.Event 76 } 77 78 type doReconcile func(ctx context.Context, o *v1beta1.CronJob) reconciler.Event 79 80 // reconcilerImpl implements controller.Reconciler for v1beta1.CronJob 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 batchv1beta1.CronJobLister 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 batchv1beta1.CronJobLister, 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.CronJobs(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.CronJob, desired *v1beta1.CronJob) 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.BatchV1beta1().CronJobs(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.BatchV1beta1().CronJobs(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.CronJob, desiredFinalizers sets.Set[string]) (*v1beta1.CronJob, 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.CronJob, desiredFinalizers sets.Set[string]) (*v1beta1.CronJob, 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.BatchV1beta1().CronJobs(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.CronJob, desiredFinalizers sets.Set[string]) (*v1beta1.CronJob, 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.BatchV1beta1().CronJobs(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.CronJob) (*v1beta1.CronJob, 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.CronJob, reconcileEvent reconciler.Event) (*v1beta1.CronJob, 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.BatchV1beta1().CronJobs(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 }