github.com/banzaicloud/operator-tools@v0.28.10/pkg/reconciler/resource.go (about) 1 // Copyright © 2020 Banzai Cloud 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package reconciler 16 17 import ( 18 "context" 19 "fmt" 20 "reflect" 21 "strings" 22 "time" 23 24 "emperror.dev/errors" 25 "github.com/go-logr/logr" 26 v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 27 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" 28 apierrors "k8s.io/apimachinery/pkg/api/errors" 29 "k8s.io/apimachinery/pkg/api/meta" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 32 "k8s.io/apimachinery/pkg/runtime" 33 "k8s.io/apimachinery/pkg/runtime/schema" 34 "k8s.io/apimachinery/pkg/util/wait" 35 clientgoscheme "k8s.io/client-go/kubernetes/scheme" 36 "sigs.k8s.io/controller-runtime/pkg/client" 37 "sigs.k8s.io/controller-runtime/pkg/client/apiutil" 38 "sigs.k8s.io/controller-runtime/pkg/reconcile" 39 40 "github.com/banzaicloud/k8s-objectmatcher/patch" 41 "github.com/banzaicloud/operator-tools/pkg/types" 42 "github.com/banzaicloud/operator-tools/pkg/utils" 43 ) 44 45 const ( 46 DefaultRecreateRequeueDelay int32 = 10 47 StateCreated StaticDesiredState = "Created" 48 StateAbsent StaticDesiredState = "Absent" 49 StatePresent StaticDesiredState = "Present" 50 ) 51 52 var DefaultRecreateEnabledGroupKinds = []schema.GroupKind{ 53 {Group: "", Kind: "Service"}, 54 {Group: "apps", Kind: "StatefulSet"}, 55 {Group: "apps", Kind: "DaemonSet"}, 56 {Group: "apps", Kind: "Deployment"}, 57 } 58 59 type DesiredState interface { 60 BeforeUpdate(current, desired runtime.Object) error 61 BeforeCreate(desired runtime.Object) error 62 BeforeDelete(current runtime.Object) error 63 } 64 65 type DesiredStateShouldCreate interface { 66 ShouldCreate(desired runtime.Object) (bool, error) 67 } 68 69 type DesiredStateShouldUpdate interface { 70 ShouldUpdate(current, desired runtime.Object) (bool, error) 71 } 72 73 type DesiredStateShouldDelete interface { 74 ShouldDelete(desired runtime.Object) (bool, error) 75 } 76 77 type DesiredStateWithDeleteOptions interface { 78 GetDeleteOptions() []client.DeleteOption 79 } 80 81 type DesiredStateWithCreateOptions interface { 82 GetCreateOptions() []client.CreateOption 83 } 84 85 type DesiredStateWithUpdateOptions interface { 86 GetUpdateOptions() []client.UpdateOption 87 } 88 89 type DesiredStateWithStaticState interface { 90 DesiredState() StaticDesiredState 91 } 92 93 type DesiredStateWithGetter interface { 94 GetDesiredState() DesiredState 95 } 96 97 type ResourceReconciler interface { 98 CreateIfNotExist(runtime.Object, DesiredState) (created bool, object runtime.Object, err error) 99 ReconcileResource(runtime.Object, DesiredState) (*reconcile.Result, error) 100 } 101 102 type StaticDesiredState string 103 104 func (s StaticDesiredState) BeforeUpdate(current, desired runtime.Object) error { 105 return nil 106 } 107 108 func (s StaticDesiredState) BeforeCreate(desired runtime.Object) error { 109 return nil 110 } 111 112 func (s StaticDesiredState) BeforeDelete(current runtime.Object) error { 113 return nil 114 } 115 116 type DesiredStateHook func(object runtime.Object) error 117 118 func (d DesiredStateHook) BeforeUpdate(current, desired runtime.Object) error { 119 return d(current) 120 } 121 122 func (d DesiredStateHook) BeforeCreate(desired runtime.Object) error { 123 return d(desired) 124 } 125 126 func (d DesiredStateHook) BeforeDelete(current runtime.Object) error { 127 return d(current) 128 } 129 130 // GenericResourceReconciler generic resource reconciler 131 type GenericResourceReconciler struct { 132 Log logr.Logger 133 Client client.Client 134 Options ReconcilerOpts 135 } 136 137 type ResourceReconcilerOption func(*ReconcilerOpts) 138 139 type RecreateResourceCondition func(kind schema.GroupVersionKind, status metav1.Status) bool 140 141 type ErrorMessageCondition func(string) bool 142 143 // Recommended to use NewReconcilerWith + ResourceReconcilerOptions 144 type ReconcilerOpts struct { 145 Log logr.Logger 146 Scheme *runtime.Scheme 147 // Enable recreating workloads and services when the API server rejects an update 148 EnableRecreateWorkloadOnImmutableFieldChange bool 149 // Custom log message to help when a workload or service needs to be recreated 150 EnableRecreateWorkloadOnImmutableFieldChangeHelp string 151 // The delay in seconds to wait before checking back after deleting the resource (10s by default) 152 RecreateRequeueDelay *int32 153 // List of callbacks evaluated to decide whether a given gvk is enabled to be recreated or not 154 RecreateEnabledResourceCondition RecreateResourceCondition 155 // Immediately recreate the resource instead of deleting and returning with a requeue 156 RecreateImmediately bool 157 // Configure the recreate PropagationPolicy. "Orphan" avoids deleting pods simultaneously. 158 RecreatePropagationPolicy client.PropagationPolicy 159 // Check the update error message contains this substring before recreate. Default: "immutable" 160 RecreateErrorMessageSubstring *string 161 // Custom logic to decide if an error message indicates a resource should be recreated. 162 // Takes precedence over RecreateErrorMessageSubstring if set. 163 RecreateErrorMessageCondition ErrorMessageCondition 164 // K8s object matcher patch maker implementation 165 PatchMaker patch.Maker 166 // K8s object matcher patch calculate options 167 PatchCalculateOptions []patch.CalculateOption 168 } 169 170 func MatchImmutableNoStatefulSet(errorMessage string) bool { 171 if strings.Contains(errorMessage, "immutable") { 172 return true 173 } 174 if strings.Contains(errorMessage, "may not change once set") { 175 return true 176 } 177 return false 178 } 179 180 func MatchImmutableErrorMessages(errorMessage string) bool { 181 if strings.Contains(errorMessage, "immutable") { 182 return true 183 } 184 if strings.Contains(errorMessage, "may not change once set") { 185 return true 186 } 187 // StatefulSet is a special case because it has a different error message 188 if strings.Contains(errorMessage, "updates to statefulset spec for fields other than") { 189 return true 190 } 191 return false 192 } 193 194 // NewGenericReconciler returns GenericResourceReconciler 195 // Deprecated, use NewReconcilerWith 196 func NewGenericReconciler(c client.Client, log logr.Logger, opts ReconcilerOpts) *GenericResourceReconciler { 197 if opts.Scheme == nil { 198 opts.Scheme = runtime.NewScheme() 199 _ = clientgoscheme.AddToScheme(opts.Scheme) 200 } 201 if opts.RecreateRequeueDelay == nil { 202 opts.RecreateRequeueDelay = utils.IntPointer(DefaultRecreateRequeueDelay) 203 } 204 if opts.RecreateErrorMessageSubstring == nil { 205 if opts.RecreateErrorMessageCondition == nil { 206 opts.RecreateErrorMessageCondition = MatchImmutableNoStatefulSet 207 } else { 208 opts.RecreateErrorMessageSubstring = utils.StringPointer("immutable") 209 } 210 } 211 if opts.RecreateEnabledResourceCondition == nil { 212 // only allow a custom set of types and only specific errors 213 opts.RecreateEnabledResourceCondition = func(kind schema.GroupVersionKind, status metav1.Status) bool { 214 for _, gk := range DefaultRecreateEnabledGroupKinds { 215 if gk == kind.GroupKind() { 216 return true 217 } 218 } 219 return false 220 } 221 } 222 if len(opts.RecreatePropagationPolicy) == 0 { 223 // DO NOT wait until all dependent resources get cleared up 224 opts.RecreatePropagationPolicy = client.PropagationPolicy(metav1.DeletePropagationBackground) 225 } 226 if opts.PatchMaker == nil { 227 opts.PatchMaker = patch.DefaultPatchMaker 228 } 229 if opts.PatchCalculateOptions == nil { 230 opts.PatchCalculateOptions = []patch.CalculateOption{patch.IgnoreStatusFields()} 231 } 232 return &GenericResourceReconciler{ 233 Log: log, 234 Client: c, 235 Options: opts, 236 } 237 } 238 239 func WithLog(log logr.Logger) ResourceReconcilerOption { 240 return func(o *ReconcilerOpts) { 241 o.Log = log 242 } 243 } 244 245 func WithScheme(scheme *runtime.Scheme) ResourceReconcilerOption { 246 return func(o *ReconcilerOpts) { 247 o.Scheme = scheme 248 } 249 } 250 251 func WithEnableRecreateWorkload() ResourceReconcilerOption { 252 return func(o *ReconcilerOpts) { 253 o.EnableRecreateWorkloadOnImmutableFieldChange = true 254 } 255 } 256 257 // Apply the given amount of delay before recreating a resource after it has been removed 258 func WithRecreateRequeueDelay(delay int32) ResourceReconcilerOption { 259 return func(o *ReconcilerOpts) { 260 o.RecreateRequeueDelay = utils.IntPointer(delay) 261 } 262 } 263 264 // Use this option for the legacy behaviour 265 func WithRecreateEnabledForAll() ResourceReconcilerOption { 266 return func(o *ReconcilerOpts) { 267 o.RecreateEnabledResourceCondition = func(_ schema.GroupVersionKind, _ metav1.Status) bool { 268 return true 269 } 270 } 271 } 272 273 // Use this option for the legacy behaviour 274 func WithRecreateEnabledFor(condition RecreateResourceCondition) ResourceReconcilerOption { 275 return func(o *ReconcilerOpts) { 276 o.RecreateEnabledResourceCondition = condition 277 } 278 } 279 280 // Matches no GVK 281 func WithRecreateEnabledForNothing() ResourceReconcilerOption { 282 return func(o *ReconcilerOpts) { 283 o.RecreateEnabledResourceCondition = func(kind schema.GroupVersionKind, status metav1.Status) bool { 284 return false 285 } 286 } 287 } 288 289 // Recreate workloads immediately without waiting for dependents to get GCd 290 func WithRecreateImmediately() ResourceReconcilerOption { 291 return func(o *ReconcilerOpts) { 292 o.RecreateImmediately = true 293 } 294 } 295 296 // Recreate only if the error message contains the given substring 297 func WithRecreateErrorMessageSubstring(substring string) ResourceReconcilerOption { 298 return func(o *ReconcilerOpts) { 299 o.RecreateErrorMessageSubstring = utils.StringPointer(substring) 300 } 301 } 302 303 // Recreate only if the error message contains the given substring 304 func WithRecreateErrorMessageCondition(condition ErrorMessageCondition) ResourceReconcilerOption { 305 return func(o *ReconcilerOpts) { 306 o.RecreateErrorMessageCondition = condition 307 } 308 } 309 310 // Disable checking the error message before recreating resources 311 func WithRecreateErrorMessageIgnored() ResourceReconcilerOption { 312 return func(o *ReconcilerOpts) { 313 o.RecreateErrorMessageSubstring = utils.StringPointer("") 314 } 315 } 316 317 // Set patch maker implementation 318 func WithPatchMaker(maker patch.Maker) ResourceReconcilerOption { 319 return func(o *ReconcilerOpts) { 320 o.PatchMaker = maker 321 } 322 } 323 324 // Set patch maker calculate options 325 func WithPatchCalculateOptions(options ...patch.CalculateOption) ResourceReconcilerOption { 326 return func(o *ReconcilerOpts) { 327 o.PatchCalculateOptions = options 328 } 329 } 330 331 func NewReconcilerWith(client client.Client, opts ...ResourceReconcilerOption) ResourceReconciler { 332 options := ReconcilerOpts{ 333 Log: logr.Discard(), 334 EnableRecreateWorkloadOnImmutableFieldChangeHelp: "recreating object on immutable field change has to be enabled explicitly through the reconciler options", 335 } 336 for _, opt := range opts { 337 opt(&options) 338 } 339 return NewGenericReconciler(client, options.Log, options) 340 } 341 342 // CreateResource creates a resource if it doesn't exist 343 func (r *GenericResourceReconciler) CreateResource(desired runtime.Object) error { 344 _, _, err := r.CreateIfNotExist(desired, nil) 345 return err 346 } 347 348 func (r *GenericResourceReconciler) shouldRecreate(sErr *apierrors.StatusError) bool { 349 // If a condition function is set, use it 350 if r.Options.RecreateErrorMessageCondition != nil { 351 return r.Options.RecreateErrorMessageCondition(sErr.ErrStatus.Message) 352 } 353 // Fall back to substring matching 354 return strings.Contains(sErr.ErrStatus.Message, utils.PointerToString(r.Options.RecreateErrorMessageSubstring)) 355 } 356 357 // ReconcileResource reconciles various kubernetes types 358 func (r *GenericResourceReconciler) ReconcileResource(desired runtime.Object, desiredState DesiredState) (*reconcile.Result, error) { 359 resourceDetails, gvk, err := r.resourceDetails(desired) 360 if err != nil { 361 return nil, errors.WrapIf(err, "failed to get resource details") 362 } 363 log := r.resourceLog(desired, resourceDetails...) 364 debugLog := log.V(1) 365 traceLog := log.V(3) 366 state := desiredState 367 if ds, ok := desiredState.(DesiredStateWithStaticState); ok { 368 state = ds.DesiredState() 369 } else if ds, ok := desiredState.(DesiredStateWithGetter); ok { 370 state = ds.GetDesiredState() 371 } 372 switch state { 373 case StateCreated: 374 created, _, err := r.CreateIfNotExist(desired, desiredState) 375 if err == nil && created { 376 return nil, nil 377 } 378 if err != nil { 379 return nil, errors.WrapIfWithDetails(err, "failed to create resource", resourceDetails...) 380 } 381 default: 382 created, current, err := r.CreateIfNotExist(desired, desiredState) 383 if err == nil && created { 384 return nil, nil 385 } 386 if err != nil { 387 return nil, errors.WrapIfWithDetails(err, "failed to create resource", resourceDetails...) 388 } 389 390 if metaObject, ok := current.(metav1.Object); ok { 391 if metaObject.GetDeletionTimestamp() != nil { 392 log.Info(fmt.Sprintf("object %s is being deleted, backing off", metaObject.GetSelfLink())) 393 return &reconcile.Result{RequeueAfter: time.Second * 2}, nil 394 } 395 if !created { 396 if desiredMetaObject, ok := desired.(metav1.Object); ok { 397 base := types.MetaBase{ 398 Annotations: desiredMetaObject.GetAnnotations(), 399 Labels: desiredMetaObject.GetLabels(), 400 } 401 if metaObject, ok := current.DeepCopyObject().(metav1.Object); ok { 402 merged := base.Merge(metav1.ObjectMeta{ 403 Labels: metaObject.GetLabels(), 404 Annotations: metaObject.GetAnnotations(), 405 }) 406 desiredMetaObject.SetAnnotations(merged.Annotations) 407 desiredMetaObject.SetLabels(merged.Labels) 408 } 409 } 410 411 if _, ok := metaObject.GetAnnotations()[types.BanzaiCloudManagedComponent]; !ok { 412 if desiredMetaObject, ok := desired.(metav1.Object); ok { 413 a := desiredMetaObject.GetAnnotations() 414 delete(a, types.BanzaiCloudManagedComponent) 415 desiredMetaObject.SetAnnotations(a) 416 } 417 } 418 } 419 } 420 421 if ds, ok := desiredState.(DesiredStateShouldUpdate); ok { 422 should, err := ds.ShouldUpdate(current.DeepCopyObject(), desired.DeepCopyObject()) 423 if err != nil { 424 return nil, err 425 } 426 if !should { 427 return nil, nil 428 } 429 } 430 431 // last chance to hook into the desired state armed with the knowledge of the current state 432 err = desiredState.BeforeUpdate(current, desired) 433 if err != nil { 434 return nil, errors.WrapIfWithDetails(err, "failed to get desired state dynamically", resourceDetails...) 435 } 436 437 patchResult, err := r.Options.PatchMaker.Calculate(current, desired, r.Options.PatchCalculateOptions...) 438 if err != nil { 439 debugLog.Info("could not match objects", "error", err) 440 } else if patchResult.IsEmpty() { 441 debugLog.Info("resource is in sync") 442 return nil, nil 443 } else { 444 if gvk.Kind == "Secret" { 445 debugLog.Info("resource diff") 446 } else { 447 debugLog.Info("resource diff", "patch", string(patchResult.Patch)) 448 traceLog.Info("resource states", 449 "current", string(patchResult.Current), 450 "modified", string(patchResult.Modified), 451 "original", string(patchResult.Original)) 452 } 453 } 454 455 if err := patch.DefaultAnnotator.SetLastAppliedAnnotation(desired); err != nil { 456 log.Error(err, "Failed to set last applied annotation", "desired", desired) 457 } 458 459 metaAccessor := meta.NewAccessor() 460 461 currentResourceVersion, err := metaAccessor.ResourceVersion(current) 462 if err != nil { 463 return nil, errors.WrapIfWithDetails(err, "failed to access resourceVersion from metadata", resourceDetails...) 464 } 465 if err := metaAccessor.SetResourceVersion(desired, currentResourceVersion); err != nil { 466 return nil, errors.WrapIfWithDetails(err, "failed to set resourceVersion in metadata", resourceDetails...) 467 } 468 469 debugLog.Info("updating resource") 470 var updateOptions []client.UpdateOption 471 if ds, ok := desiredState.(DesiredStateWithUpdateOptions); ok { 472 updateOptions = append(updateOptions, ds.GetUpdateOptions()...) 473 } 474 if err := r.Client.Update(context.TODO(), desired.(client.Object), updateOptions...); err != nil { 475 sErr, ok := err.(*apierrors.StatusError) 476 if ok && (sErr.ErrStatus.Code == 422 && sErr.ErrStatus.Reason == metav1.StatusReasonInvalid) && r.shouldRecreate(sErr) { 477 if r.Options.EnableRecreateWorkloadOnImmutableFieldChange { 478 if !r.Options.RecreateEnabledResourceCondition(gvk, sErr.ErrStatus) { 479 return nil, errors.WrapIfWithDetails(err, "resource type is not allowed to be recreated", resourceDetails...) 480 } 481 log.Error(err, "failed to update resource, trying to recreate", resourceDetails...) 482 if r.Options.RecreateImmediately { 483 err := r.Client.Delete(context.TODO(), current.(client.Object), 484 r.Options.RecreatePropagationPolicy, 485 ) 486 if err != nil { 487 return nil, errors.WrapIfWithDetails(err, "failed to delete current resource", resourceDetails...) 488 } 489 if err := metaAccessor.SetResourceVersion(desired, ""); err != nil { 490 return nil, errors.WrapIfWithDetails(err, "unable to clear resourceVersion", resourceDetails...) 491 } 492 created, _, err := r.CreateIfNotExist(desired, desiredState) 493 if err == nil { 494 if !created { 495 return nil, errors.New("resource already exists") 496 } 497 return nil, nil 498 } 499 if err != nil { 500 return nil, errors.WrapIfWithDetails(err, "failed to recreate resource", resourceDetails...) 501 } 502 } 503 err := r.Client.Delete(context.TODO(), current.(client.Object), 504 // wait until all dependent resources get cleared up 505 client.PropagationPolicy(metav1.DeletePropagationForeground), 506 ) 507 if err != nil { 508 return nil, errors.WrapIfWithDetails(err, "failed to delete current resource", resourceDetails...) 509 } 510 return &reconcile.Result{ 511 Requeue: true, 512 RequeueAfter: time.Second * time.Duration(utils.PointerToInt32(r.Options.RecreateRequeueDelay)), 513 }, nil 514 } else { 515 return nil, errors.WrapIf(sErr, r.Options.EnableRecreateWorkloadOnImmutableFieldChangeHelp) 516 } 517 } 518 return nil, errors.WrapIfWithDetails(err, "updating resource failed", resourceDetails...) 519 } 520 debugLog.Info("resource updated") 521 522 case StateAbsent: 523 _, err := r.delete(desired, desiredState) 524 if err != nil { 525 return nil, errors.WrapIfWithDetails(err, "failed to delete resource", resourceDetails...) 526 } 527 } 528 return nil, nil 529 } 530 531 func (r *GenericResourceReconciler) fromDesired(desired runtime.Object) (runtime.Object, error) { 532 if _, ok := desired.(*unstructured.Unstructured); ok { 533 if r.Options.Scheme != nil { 534 object, err := r.Options.Scheme.New(desired.GetObjectKind().GroupVersionKind()) 535 if err == nil { 536 return object, nil 537 } 538 r.Log.V(2).Info("unable to detect correct type for the resource, falling back to unstructured") 539 } 540 current := &unstructured.Unstructured{} 541 desiredGVK := desired.GetObjectKind() 542 current.SetKind(desiredGVK.GroupVersionKind().Kind) 543 current.SetAPIVersion(desiredGVK.GroupVersionKind().GroupVersion().String()) 544 return current, nil 545 } 546 return reflect.New(reflect.Indirect(reflect.ValueOf(desired)).Type()).Interface().(runtime.Object), nil 547 } 548 549 func (r *GenericResourceReconciler) CreateIfNotExist(desired runtime.Object, desiredState DesiredState) (bool, runtime.Object, error) { 550 current, err := r.fromDesired(desired) 551 if err != nil { 552 return false, nil, errors.WrapIf(err, "failed to create new object based on desired") 553 } 554 m, err := meta.Accessor(desired) 555 if err != nil { 556 return false, nil, errors.WrapIf(err, "failed to get object key") 557 } 558 key := client.ObjectKey{Namespace: m.GetNamespace(), Name: m.GetName()} 559 resourceDetails, _, err := r.resourceDetails(desired) 560 if err != nil { 561 return false, nil, errors.WrapIf(err, "failed to get resource details") 562 } 563 log := r.resourceLog(desired, resourceDetails...) 564 traceLog := log.V(2) 565 err = r.Client.Get(context.TODO(), key, current.(client.Object)) 566 current.GetObjectKind().SetGroupVersionKind(desired.GetObjectKind().GroupVersionKind()) 567 if err != nil && !apierrors.IsNotFound(err) { 568 return false, nil, errors.WrapIfWithDetails(err, "getting resource failed", resourceDetails...) 569 } 570 if apierrors.IsNotFound(err) { 571 if err := patch.DefaultAnnotator.SetLastAppliedAnnotation(desired); err != nil { 572 log.Error(err, "Failed to set last applied annotation", "desired", desired) 573 } 574 if desiredState != nil { 575 err = desiredState.BeforeCreate(desired) 576 if err != nil { 577 return false, nil, errors.WrapIfWithDetails(err, "failed to prepare desired state before creation", resourceDetails...) 578 } 579 if ds, ok := desiredState.(DesiredStateShouldCreate); ok { 580 should, err := ds.ShouldCreate(desired) 581 if err != nil { 582 return false, desired, err 583 } 584 if !should { 585 return false, desired, nil 586 } 587 } 588 } 589 var createOptions []client.CreateOption 590 if ds, ok := desiredState.(DesiredStateWithCreateOptions); ok { 591 createOptions = append(createOptions, ds.GetCreateOptions()...) 592 } 593 if err := r.Client.Create(context.TODO(), desired.(client.Object), createOptions...); err != nil { 594 return false, nil, errors.WrapIfWithDetails(err, "creating resource failed", resourceDetails...) 595 } 596 switch t := desired.DeepCopyObject().(type) { 597 case *v1beta1.CustomResourceDefinition: 598 err = wait.Poll(time.Second*1, time.Second*10, func() (done bool, err error) { 599 err = r.Client.Get(context.TODO(), client.ObjectKey{Namespace: t.Namespace, Name: t.Name}, t) 600 if err != nil { 601 return false, err 602 } 603 return crdReady(t), nil 604 }) 605 if err != nil { 606 return false, nil, errors.WrapIfWithDetails(err, "failed to wait for the crd to get ready", resourceDetails...) 607 } 608 case *v1.CustomResourceDefinition: 609 err = wait.Poll(time.Second*1, time.Second*10, func() (done bool, err error) { 610 err = r.Client.Get(context.TODO(), client.ObjectKey{Namespace: t.Namespace, Name: t.Name}, t) 611 if err != nil { 612 return false, err 613 } 614 return crdReadyV1(t), nil 615 }) 616 if err != nil { 617 return false, nil, errors.WrapIfWithDetails(err, "failed to wait for the crd to get ready", resourceDetails...) 618 } 619 } 620 log.Info("resource created") 621 return true, current, nil 622 } 623 traceLog.Info("resource already exists") 624 return false, current, nil 625 } 626 627 func (r *GenericResourceReconciler) delete(desired runtime.Object, desiredState DesiredState) (bool, error) { 628 current, err := r.fromDesired(desired) 629 if err != nil { 630 return false, errors.WrapIf(err, "failed to create new object based on desired") 631 } 632 m, err := meta.Accessor(desired) 633 if err != nil { 634 return false, errors.WrapIf(err, "failed to get object key") 635 } 636 key := client.ObjectKey{Namespace: m.GetNamespace(), Name: m.GetName()} 637 resourceDetails, _, err := r.resourceDetails(desired) 638 if err != nil { 639 return false, errors.WrapIf(err, "failed to get resource details") 640 } 641 log := r.resourceLog(desired, resourceDetails...) 642 debugLog := log.V(1) 643 traceLog := log.V(2) 644 err = r.Client.Get(context.TODO(), key, current.(client.Object)) 645 if err != nil { 646 // If the resource type does not exist we should be ok to move on 647 if meta.IsNoMatchError(err) || runtime.IsNotRegisteredError(err) { 648 return false, nil 649 } 650 if !apierrors.IsNotFound(err) { 651 return false, errors.WrapIfWithDetails(err, "getting resource failed", resourceDetails...) 652 } else { 653 traceLog.Info("resource not found skipping delete") 654 return false, nil 655 } 656 } 657 if desiredState != nil { 658 err = desiredState.BeforeDelete(current) 659 if err != nil { 660 return false, errors.WrapIfWithDetails(err, "failed to prepare desired state before deletion", resourceDetails...) 661 } 662 if ds, ok := desiredState.(DesiredStateShouldDelete); ok { 663 should, err := ds.ShouldDelete(desired) 664 if err != nil { 665 return false, err 666 } 667 if !should { 668 return false, nil 669 } 670 } 671 } 672 var deleteOptions []client.DeleteOption 673 if ds, ok := desiredState.(DesiredStateWithDeleteOptions); ok { 674 deleteOptions = append(deleteOptions, ds.GetDeleteOptions()...) 675 } 676 err = r.Client.Delete(context.TODO(), current.(client.Object), deleteOptions...) 677 if err != nil { 678 return false, errors.WrapIfWithDetails(err, "failed to delete resource", resourceDetails...) 679 } 680 debugLog.Info("resource deleted") 681 return true, nil 682 } 683 684 func crdReady(crd *v1beta1.CustomResourceDefinition) bool { 685 for _, cond := range crd.Status.Conditions { 686 switch cond.Type { 687 case v1beta1.Established: 688 if cond.Status == v1beta1.ConditionTrue { 689 return true 690 } 691 } 692 } 693 return false 694 } 695 696 func crdReadyV1(crd *v1.CustomResourceDefinition) bool { 697 for _, cond := range crd.Status.Conditions { 698 switch cond.Type { 699 case v1.Established: 700 if cond.Status == v1.ConditionTrue { 701 return true 702 } 703 } 704 } 705 return false 706 } 707 708 func (r *GenericResourceReconciler) resourceDetails(desired runtime.Object) ([]interface{}, schema.GroupVersionKind, error) { 709 gvk := schema.GroupVersionKind{} 710 m, err := meta.Accessor(desired) 711 if err != nil { 712 return nil, gvk, errors.WithStackIf(err) 713 } 714 key := client.ObjectKey{Namespace: m.GetNamespace(), Name: m.GetName()} 715 values := []interface{}{"name", key.Name} 716 if key.Namespace != "" { 717 values = append(values, "namespace", key.Namespace) 718 } 719 defaultValues := append(values, "type", reflect.TypeOf(desired).String()) 720 if r.Options.Scheme == nil { 721 return defaultValues, gvk, nil 722 } 723 gvk, err = apiutil.GVKForObject(desired, r.Options.Scheme) 724 if err != nil { 725 r.Log.V(2).Info("unable to get gvk for resource, falling back to type") 726 return values, gvk, nil 727 } 728 values = append(values, 729 "apiVersion", gvk.GroupVersion().String(), 730 "kind", gvk.Kind) 731 return values, gvk, nil 732 } 733 734 func (r *GenericResourceReconciler) resourceLog(desired runtime.Object, details ...interface{}) logr.Logger { 735 if len(details) > 0 { 736 return r.Log.WithValues(details...) 737 } 738 return r.Log 739 }