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 }