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