github.com/banzaicloud/operator-tools@v0.28.10/pkg/reconciler/component.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  
    20  	"emperror.dev/errors"
    21  	"github.com/banzaicloud/operator-tools/pkg/resources"
    22  	"github.com/banzaicloud/operator-tools/pkg/types"
    23  	"github.com/banzaicloud/operator-tools/pkg/utils"
    24  	"github.com/go-logr/logr"
    25  	"k8s.io/apimachinery/pkg/runtime"
    26  	ctrl "sigs.k8s.io/controller-runtime"
    27  	"sigs.k8s.io/controller-runtime/pkg/builder"
    28  	"sigs.k8s.io/controller-runtime/pkg/client"
    29  	"sigs.k8s.io/controller-runtime/pkg/controller"
    30  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    31  )
    32  
    33  type ComponentReconciler interface {
    34  	Reconcile(object runtime.Object) (*reconcile.Result, error)
    35  	RegisterWatches(*builder.Builder)
    36  }
    37  
    38  type Watches interface {
    39  	SetupAdditionalWatches(c controller.Controller) error
    40  }
    41  
    42  type ComponentWithStatus interface {
    43  	Update(object runtime.Object, status types.ReconcileStatus, msg string) error
    44  	IsSkipped(object runtime.Object) bool
    45  	IsEnabled(object runtime.Object) bool
    46  }
    47  
    48  type ComponentLifecycle interface {
    49  	OnFinished(object runtime.Object) error
    50  }
    51  
    52  // ComponentReconcilers is a list of component reconcilers that support getting components in Install and Uninstall order.
    53  type ComponentReconcilers []ComponentReconciler
    54  
    55  func (c ComponentReconcilers) Get(order utils.ResourceOrder) []ComponentReconciler {
    56  	if order != utils.UninstallResourceOrder {
    57  		return c
    58  	}
    59  
    60  	components := []ComponentReconciler{}
    61  	for i := len(c) - 1; i >= 0; i-- {
    62  		components = append(components, c[i])
    63  	}
    64  	return components
    65  }
    66  
    67  // Dispatcher orchestrates reconciliation of multiple ComponentReconciler objects
    68  // focusing on handing off reconciled object to all of its components and calculating an aggregated result to return.
    69  // It requires a ResourceGetter callback and optionally can leverage a ResourceFilter and a CompletionHandler
    70  type Dispatcher struct {
    71  	client.Client
    72  	Log                  logr.Logger
    73  	ResourceGetter       func(req ctrl.Request) (runtime.Object, error)
    74  	ResourceFilter       func(runtime.Object) (bool, error)
    75  	CompletionHandler    func(runtime.Object, ctrl.Result, error) (ctrl.Result, error)
    76  	ComponentReconcilers ComponentReconcilers
    77  	// ForceResourceOrder can be used to force a given resource ordering regardless of an object being deleted with
    78  	// finalizers.
    79  	ForceResourceOrder utils.ResourceOrder
    80  }
    81  
    82  // Reconcile implements reconcile.Reconciler in a generic way from the controller-runtime library
    83  func (r *Dispatcher) Reconcile(_ context.Context, req ctrl.Request) (ctrl.Result, error) {
    84  	object, err := r.ResourceGetter(req)
    85  	if err != nil {
    86  		return reconcile.Result{}, errors.WithStack(err)
    87  	}
    88  	if object == nil {
    89  		return reconcile.Result{}, nil
    90  	}
    91  	if r.ResourceFilter != nil {
    92  		shouldReconcile, err := r.ResourceFilter(object)
    93  		if err != nil || !shouldReconcile {
    94  			return reconcile.Result{}, errors.WithStack(err)
    95  		}
    96  	}
    97  	result, err := r.Handle(object)
    98  	if r.CompletionHandler != nil {
    99  		return r.CompletionHandler(object, result, errors.WithStack(err))
   100  	}
   101  	if err != nil {
   102  		return result, errors.WithStack(err)
   103  	}
   104  	return result, nil
   105  }
   106  
   107  // Handle receives a single object and dispatches it to all the components
   108  // Components need to understand how to interpret the object
   109  func (r *Dispatcher) Handle(object runtime.Object) (ctrl.Result, error) {
   110  	isBeingDeleted, err := resources.IsObjectBeingDeleted(object)
   111  	if err != nil {
   112  		return ctrl.Result{}, err
   113  	}
   114  
   115  	componentExecutionOrder := utils.InstallResourceOrder
   116  	if isBeingDeleted {
   117  		componentExecutionOrder = utils.UninstallResourceOrder
   118  	}
   119  
   120  	if r.ForceResourceOrder != "" {
   121  		componentExecutionOrder = r.ForceResourceOrder
   122  	}
   123  
   124  	combinedResult := &CombinedResult{}
   125  	for _, cr := range r.ComponentReconcilers.Get(componentExecutionOrder) {
   126  		if cr, ok := cr.(ComponentWithStatus); ok {
   127  			status := types.ReconcileStatusReconciling
   128  			if cr.IsSkipped(object) {
   129  				status = types.ReconcileStatusUnmanaged
   130  			}
   131  			if uerr := cr.Update(object, status, ""); uerr != nil {
   132  				combinedResult.CombineErr(errors.WrapIf(uerr, "unable to update status for component"))
   133  			}
   134  			if cr.IsSkipped(object) {
   135  				continue
   136  			}
   137  		}
   138  
   139  		// Any patch/update command can update the object, thus sequent steps will be
   140  		// remove steps instead. To work around that let's requeue with the correct order.
   141  		currentDeletionStatus, err := resources.IsObjectBeingDeleted(object)
   142  		if err != nil {
   143  			return ctrl.Result{}, err
   144  		}
   145  
   146  		if currentDeletionStatus != isBeingDeleted {
   147  			// Requeue object so that we can start reconciling in inverse order
   148  			combinedResult.Combine(&reconcile.Result{Requeue: true, RequeueAfter: 0}, errors.New("object being deleted requeing object"))
   149  			break
   150  		}
   151  
   152  		result, err := cr.Reconcile(object)
   153  		if cr, ok := cr.(ComponentWithStatus); ok {
   154  			if err != nil {
   155  				if uerr := cr.Update(object, types.ReconcileStatusFailed, err.Error()); uerr != nil {
   156  					combinedResult.CombineErr(errors.WrapIf(uerr, "unable to update status for component"))
   157  				}
   158  			} else {
   159  				if result == nil || (!result.Requeue && result.RequeueAfter == 0) {
   160  					status := types.ReconcileStatusRemoved
   161  					if cr.IsEnabled(object) {
   162  						status = types.ReconcileStatusAvailable
   163  					}
   164  					if uerr := cr.Update(object, status, ""); uerr != nil {
   165  						combinedResult.CombineErr(errors.WrapIf(uerr, "unable to update status for component"))
   166  					}
   167  				}
   168  			}
   169  		}
   170  		if cr, ok := cr.(ComponentLifecycle); ok {
   171  			if err := cr.OnFinished(object); err != nil {
   172  				combinedResult.Combine(result, errors.WrapIf(err, "failed to notify component on finish"))
   173  			}
   174  		}
   175  		combinedResult.Combine(result, errors.WithStack(err))
   176  		if cr, ok := cr.(interface{ IsOptional() bool }); ok {
   177  			if err != nil && !cr.IsOptional() {
   178  				break
   179  			}
   180  		}
   181  	}
   182  	return combinedResult.Result, combinedResult.Err
   183  }
   184  
   185  // RegisterWatches dispatches the watch registration builder to all its components
   186  func (r *Dispatcher) RegisterWatches(b *builder.Builder) *builder.Builder {
   187  	for _, cr := range r.ComponentReconcilers {
   188  		cr.RegisterWatches(b)
   189  	}
   190  	return b
   191  }
   192  
   193  // SetupAdditionalWatches dispatches the controller for watch registration to all its components
   194  func (r *Dispatcher) SetupAdditionalWatches(c controller.Controller) error {
   195  	for _, cr := range r.ComponentReconcilers {
   196  		if cr, ok := cr.(Watches); ok {
   197  			err := cr.SetupAdditionalWatches(c)
   198  			if err != nil {
   199  				return errors.WithStack(err)
   200  			}
   201  		}
   202  	}
   203  
   204  	return nil
   205  }