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