github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/extensions/addon_controller.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package extensions 21 22 import ( 23 "context" 24 "runtime" 25 26 ctrlerihandler "github.com/authzed/controller-idioms/handler" 27 batchv1 "k8s.io/api/batch/v1" 28 corev1 "k8s.io/api/core/v1" 29 k8sruntime "k8s.io/apimachinery/pkg/runtime" 30 "k8s.io/apimachinery/pkg/types" 31 "k8s.io/client-go/rest" 32 "k8s.io/client-go/tools/record" 33 ctrl "sigs.k8s.io/controller-runtime" 34 "sigs.k8s.io/controller-runtime/pkg/client" 35 "sigs.k8s.io/controller-runtime/pkg/controller" 36 "sigs.k8s.io/controller-runtime/pkg/handler" 37 "sigs.k8s.io/controller-runtime/pkg/log" 38 "sigs.k8s.io/controller-runtime/pkg/reconcile" 39 40 extensionsv1alpha1 "github.com/1aal/kubeblocks/apis/extensions/v1alpha1" 41 "github.com/1aal/kubeblocks/pkg/constant" 42 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 43 viper "github.com/1aal/kubeblocks/pkg/viperx" 44 ) 45 46 // AddonReconciler reconciles a Addon object 47 type AddonReconciler struct { 48 client.Client 49 Scheme *k8sruntime.Scheme 50 Recorder record.EventRecorder 51 RestConfig *rest.Config 52 } 53 54 var _ record.EventRecorder = &AddonReconciler{} 55 56 func init() { 57 viper.SetDefault(maxConcurrentReconcilesKey, runtime.NumCPU()*2) 58 } 59 60 // +kubebuilder:rbac:groups=extensions.kubeblocks.io,resources=addons,verbs=get;list;watch;create;update;patch;delete 61 // +kubebuilder:rbac:groups=extensions.kubeblocks.io,resources=addons/status,verbs=get;update;patch 62 // +kubebuilder:rbac:groups=extensions.kubeblocks.io,resources=addons/finalizers,verbs=update 63 64 // +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete;deletecollection 65 // +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;delete;deletecollection 66 // +kubebuilder:rbac:groups=core,resources=pods/log,verbs=get;list 67 68 // Reconcile is part of the main kubernetes reconciliation loop which aims to 69 // move the current state of the cluster closer to the desired state. 70 // 71 // For more details, check Reconcile and its Result here: 72 // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.1/pkg/reconcile 73 func (r *AddonReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 74 reqCtx := intctrlutil.RequestCtx{ 75 Ctx: ctx, 76 Req: req, 77 Log: log.FromContext(ctx).WithValues("addon", req.NamespacedName), 78 Recorder: r.Recorder, 79 } 80 81 buildStageCtx := func(next ...ctrlerihandler.Handler) stageCtx { 82 return stageCtx{ 83 reqCtx: &reqCtx, 84 reconciler: r, 85 next: ctrlerihandler.Handlers(next).MustOne(), 86 } 87 } 88 89 fetchNDeletionCheckStageBuilder := func(next ...ctrlerihandler.Handler) ctrlerihandler.Handler { 90 return ctrlerihandler.NewTypeHandler(&fetchNDeletionCheckStage{ 91 stageCtx: buildStageCtx(next...), 92 deletionStage: deletionStage{ 93 stageCtx: buildStageCtx(ctrlerihandler.NoopHandler), 94 }, 95 }) 96 } 97 98 genIDProceedStageBuilder := func(next ...ctrlerihandler.Handler) ctrlerihandler.Handler { 99 return ctrlerihandler.NewTypeHandler(&genIDProceedCheckStage{stageCtx: buildStageCtx(next...)}) 100 } 101 102 installableCheckStageBuilder := func(next ...ctrlerihandler.Handler) ctrlerihandler.Handler { 103 return ctrlerihandler.NewTypeHandler(&installableCheckStage{stageCtx: buildStageCtx(next...)}) 104 } 105 106 autoInstallCheckStageBuilder := func(next ...ctrlerihandler.Handler) ctrlerihandler.Handler { 107 return ctrlerihandler.NewTypeHandler(&autoInstallCheckStage{stageCtx: buildStageCtx(next...)}) 108 } 109 110 enabledAutoValuesStageBuilder := func(next ...ctrlerihandler.Handler) ctrlerihandler.Handler { 111 return ctrlerihandler.NewTypeHandler(&enabledWithDefaultValuesStage{stageCtx: buildStageCtx(next...)}) 112 } 113 114 progressingStageBuilder := func(next ...ctrlerihandler.Handler) ctrlerihandler.Handler { 115 return ctrlerihandler.NewTypeHandler(&progressingHandler{stageCtx: buildStageCtx(next...)}) 116 } 117 118 terminalStateStageBuilder := func(next ...ctrlerihandler.Handler) ctrlerihandler.Handler { 119 return ctrlerihandler.NewTypeHandler(&terminalStateStage{stageCtx: buildStageCtx(next...)}) 120 } 121 122 handlers := ctrlerihandler.Chain( 123 fetchNDeletionCheckStageBuilder, 124 genIDProceedStageBuilder, 125 installableCheckStageBuilder, 126 autoInstallCheckStageBuilder, 127 enabledAutoValuesStageBuilder, 128 progressingStageBuilder, 129 terminalStateStageBuilder, 130 ).Handler("") 131 132 handlers.Handle(ctx) 133 res, ok := reqCtx.Ctx.Value(resultValueKey).(*ctrl.Result) 134 if ok && res != nil { 135 err, ok := reqCtx.Ctx.Value(errorValueKey).(error) 136 if ok { 137 return *res, err 138 } 139 return *res, nil 140 } 141 142 return ctrl.Result{}, nil 143 } 144 145 // SetupWithManager sets up the controller with the Manager. 146 func (r *AddonReconciler) SetupWithManager(mgr ctrl.Manager) error { 147 return ctrl.NewControllerManagedBy(mgr). 148 For(&extensionsv1alpha1.Addon{}). 149 Watches(&batchv1.Job{}, handler.EnqueueRequestsFromMapFunc(r.findAddonJobs)). 150 WithOptions(controller.Options{ 151 MaxConcurrentReconciles: viper.GetInt(maxConcurrentReconcilesKey), 152 }). 153 Complete(r) 154 } 155 156 func (r *AddonReconciler) findAddonJobs(ctx context.Context, job client.Object) []reconcile.Request { 157 labels := job.GetLabels() 158 if _, ok := labels[constant.AddonNameLabelKey]; !ok { 159 return []reconcile.Request{} 160 } 161 if v, ok := labels[constant.AppManagedByLabelKey]; !ok || v != constant.AppName { 162 return []reconcile.Request{} 163 } 164 return []reconcile.Request{ 165 { 166 NamespacedName: types.NamespacedName{ 167 Namespace: job.GetNamespace(), 168 Name: job.GetName(), 169 }, 170 }, 171 } 172 } 173 174 func (r *AddonReconciler) cleanupJobPods(reqCtx intctrlutil.RequestCtx) error { 175 if err := r.DeleteAllOf(reqCtx.Ctx, &corev1.Pod{}, 176 client.InNamespace(viper.GetString(constant.CfgKeyCtrlrMgrNS)), 177 client.MatchingLabels{ 178 constant.AddonNameLabelKey: reqCtx.Req.Name, 179 constant.AppManagedByLabelKey: constant.AppName, 180 }, 181 ); err != nil { 182 return err 183 } 184 return nil 185 } 186 187 func (r *AddonReconciler) deleteExternalResources(reqCtx intctrlutil.RequestCtx, addon *extensionsv1alpha1.Addon) (*ctrl.Result, error) { 188 if addon.Annotations != nil && addon.Annotations[NoDeleteJobs] == trueVal { 189 return nil, nil 190 } 191 deleteJobIfExist := func(jobName string) error { 192 key := client.ObjectKey{ 193 Namespace: viper.GetString(constant.CfgKeyCtrlrMgrNS), 194 Name: jobName, 195 } 196 job := &batchv1.Job{} 197 if err := r.Get(reqCtx.Ctx, key, job); err != nil { 198 return client.IgnoreNotFound(err) 199 } 200 if !job.DeletionTimestamp.IsZero() { 201 return nil 202 } 203 if err := r.Delete(reqCtx.Ctx, job); err != nil { 204 return client.IgnoreNotFound(err) 205 } 206 return nil 207 } 208 for _, j := range []string{getInstallJobName(addon), getUninstallJobName(addon)} { 209 if err := deleteJobIfExist(j); err != nil { 210 return nil, err 211 } 212 } 213 if err := r.cleanupJobPods(reqCtx); err != nil { 214 return nil, err 215 } 216 return nil, nil 217 } 218 219 // following provide r.Recorder wrapper for safe operation if r.Recorder is not provided 220 221 func (r *AddonReconciler) Event(object k8sruntime.Object, eventtype, reason, message string) { 222 if r == nil || r.Recorder == nil { 223 return 224 } 225 r.Recorder.Event(object, eventtype, reason, message) 226 } 227 228 // Eventf is just like Event, but with Sprintf for the message field. 229 func (r *AddonReconciler) Eventf(object k8sruntime.Object, eventtype, reason, messageFmt string, args ...interface{}) { 230 if r == nil || r.Recorder == nil { 231 return 232 } 233 r.Recorder.Eventf(object, eventtype, reason, messageFmt, args...) 234 } 235 236 // AnnotatedEventf is just like eventf, but with annotations attached 237 func (r *AddonReconciler) AnnotatedEventf(object k8sruntime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...interface{}) { 238 if r == nil || r.Recorder == nil { 239 return 240 } 241 r.Recorder.AnnotatedEventf(object, annotations, eventtype, reason, messageFmt, args...) 242 }