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  }