github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controller/configuration/pipeline.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 configuration 21 22 import ( 23 "strconv" 24 25 corev1 "k8s.io/api/core/v1" 26 apierrors "k8s.io/apimachinery/pkg/api/errors" 27 "sigs.k8s.io/controller-runtime/pkg/client" 28 29 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 30 "github.com/1aal/kubeblocks/pkg/configuration/core" 31 cfgutil "github.com/1aal/kubeblocks/pkg/configuration/util" 32 "github.com/1aal/kubeblocks/pkg/constant" 33 "github.com/1aal/kubeblocks/pkg/controller/builder" 34 "github.com/1aal/kubeblocks/pkg/controller/component" 35 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 36 ) 37 38 type ReconcileCtx struct { 39 *intctrlutil.ResourceCtx 40 41 Cluster *appsv1alpha1.Cluster 42 ClusterVer *appsv1alpha1.ClusterVersion 43 Component *component.SynthesizedComponent 44 PodSpec *corev1.PodSpec 45 46 Object client.Object 47 Cache []client.Object 48 } 49 50 type pipeline struct { 51 // configuration *appsv1alpha1.Configuration 52 renderWrapper renderWrapper 53 54 ctx ReconcileCtx 55 intctrlutil.ResourceFetcher[pipeline] 56 } 57 58 type updatePipeline struct { 59 reconcile bool 60 renderWrapper renderWrapper 61 62 item appsv1alpha1.ConfigurationItemDetail 63 itemStatus *appsv1alpha1.ConfigurationItemDetailStatus 64 configSpec *appsv1alpha1.ComponentConfigSpec 65 // replace of ConfigMapObj 66 // originalCM *corev1.ConfigMap 67 newCM *corev1.ConfigMap 68 configPatch *core.ConfigPatchInfo 69 70 ctx ReconcileCtx 71 intctrlutil.ResourceFetcher[updatePipeline] 72 } 73 74 func NewCreatePipeline(ctx ReconcileCtx) *pipeline { 75 p := &pipeline{ctx: ctx} 76 return p.Init(ctx.ResourceCtx, p) 77 } 78 79 func NewReconcilePipeline(ctx ReconcileCtx, item appsv1alpha1.ConfigurationItemDetail, itemStatus *appsv1alpha1.ConfigurationItemDetailStatus, configSpec *appsv1alpha1.ComponentConfigSpec) *updatePipeline { 80 p := &updatePipeline{ 81 reconcile: true, 82 item: item, 83 itemStatus: itemStatus, 84 ctx: ctx, 85 configSpec: configSpec, 86 } 87 return p.Init(ctx.ResourceCtx, p) 88 } 89 90 func (p *pipeline) Prepare() *pipeline { 91 buildTemplate := func() (err error) { 92 ctx := p.ctx 93 templateBuilder := newTemplateBuilder(p.ClusterName, p.Namespace, ctx.Cluster, ctx.ClusterVer, p.Context, p.Client) 94 // Prepare built-in objects and built-in functions 95 if err = templateBuilder.injectBuiltInObjectsAndFunctions(ctx.PodSpec, ctx.Component.ConfigTemplates, ctx.Component, ctx.Cache); err != nil { 96 return 97 } 98 p.renderWrapper = newTemplateRenderWrapper(templateBuilder, ctx.Cluster, p.Context, ctx.Client) 99 return 100 } 101 102 return p.Wrap(buildTemplate) 103 } 104 105 func (p *pipeline) RenderScriptTemplate() *pipeline { 106 return p.Wrap(func() error { 107 ctx := p.ctx 108 return p.renderWrapper.renderScriptTemplate(ctx.Cluster, ctx.Component, ctx.Cache) 109 }) 110 } 111 112 func (p *pipeline) UpdateConfiguration() *pipeline { 113 buildConfiguration := func() (err error) { 114 expectedConfiguration := p.createConfiguration() 115 if intctrlutil.SetControllerReference(p.ctx.Cluster, expectedConfiguration) != nil { 116 return 117 } 118 119 existingConfiguration := appsv1alpha1.Configuration{} 120 err = p.ResourceFetcher.Client.Get(p.Context, client.ObjectKeyFromObject(expectedConfiguration), &existingConfiguration) 121 switch { 122 case err == nil: 123 return p.updateConfiguration(expectedConfiguration, &existingConfiguration) 124 case apierrors.IsNotFound(err): 125 return p.ResourceFetcher.Client.Create(p.Context, expectedConfiguration) 126 default: 127 return err 128 } 129 } 130 return p.Wrap(buildConfiguration) 131 } 132 133 func (p *pipeline) CreateConfigTemplate() *pipeline { 134 return p.Wrap(func() error { 135 ctx := p.ctx 136 return p.renderWrapper.renderConfigTemplate(ctx.Cluster, ctx.Component, ctx.Cache, p.ConfigurationObj) 137 }) 138 } 139 140 func (p *pipeline) UpdateConfigurationStatus() *pipeline { 141 return p.Wrap(func() error { 142 if p.ConfigurationObj == nil { 143 return nil 144 } 145 146 existing := p.ConfigurationObj 147 reversion := fromConfiguration(existing) 148 patch := client.MergeFrom(existing) 149 updated := existing.DeepCopy() 150 for _, item := range existing.Spec.ConfigItemDetails { 151 CheckAndUpdateItemStatus(updated, item, reversion) 152 } 153 return p.ResourceFetcher.Client.Status().Patch(p.Context, updated, patch) 154 }) 155 } 156 157 func CheckAndUpdateItemStatus(updated *appsv1alpha1.Configuration, item appsv1alpha1.ConfigurationItemDetail, reversion string) { 158 foundStatus := func(name string) *appsv1alpha1.ConfigurationItemDetailStatus { 159 for i := range updated.Status.ConfigurationItemStatus { 160 status := &updated.Status.ConfigurationItemStatus[i] 161 if status.Name == name { 162 return status 163 } 164 } 165 return nil 166 } 167 168 status := foundStatus(item.Name) 169 if status != nil && status.Phase == "" { 170 status.Phase = appsv1alpha1.CInitPhase 171 } 172 if status == nil { 173 updated.Status.ConfigurationItemStatus = append(updated.Status.ConfigurationItemStatus, 174 appsv1alpha1.ConfigurationItemDetailStatus{ 175 Name: item.Name, 176 Phase: appsv1alpha1.CInitPhase, 177 UpdateRevision: reversion, 178 }) 179 } 180 } 181 182 func (p *pipeline) UpdatePodVolumes() *pipeline { 183 return p.Wrap(func() error { 184 return intctrlutil.CreateOrUpdatePodVolumes(p.ctx.PodSpec, p.renderWrapper.volumes) 185 }) 186 } 187 188 func (p *pipeline) BuildConfigManagerSidecar() *pipeline { 189 return p.Wrap(func() error { 190 return buildConfigManagerWithComponent(p.ctx.PodSpec, p.ctx.Component.ConfigTemplates, p.Context, p.Client, p.ctx.Cluster, p.ctx.Component) 191 }) 192 } 193 194 func (p *pipeline) UpdateConfigRelatedObject() *pipeline { 195 updateMeta := func() error { 196 if p.ctx.Object != nil { 197 updateResourceAnnotationsWithTemplate(p.ctx.Object, p.renderWrapper.templateAnnotations) 198 } 199 if err := injectTemplateEnvFrom(p.ctx.Cluster, p.ctx.Component, p.ctx.PodSpec, p.Client, p.Context, p.renderWrapper.renderedObjs); err != nil { 200 return err 201 } 202 return createConfigObjects(p.Client, p.Context, p.renderWrapper.renderedObjs) 203 } 204 205 return p.Wrap(updateMeta) 206 } 207 208 func (p *pipeline) createConfiguration() *appsv1alpha1.Configuration { 209 builder := builder.NewConfigurationBuilder(p.Namespace, 210 core.GenerateComponentConfigurationName(p.ClusterName, p.ComponentName), 211 ) 212 for _, template := range p.ctx.Component.ConfigTemplates { 213 builder.AddConfigurationItem(template) 214 } 215 return builder.Component(p.ComponentName). 216 ClusterRef(p.ClusterName). 217 GetObject() 218 } 219 220 func (p *pipeline) updateConfiguration(expected *appsv1alpha1.Configuration, existing *appsv1alpha1.Configuration) error { 221 fromMap := func(items []appsv1alpha1.ConfigurationItemDetail) *cfgutil.Sets { 222 sets := cfgutil.NewSet() 223 for _, item := range items { 224 sets.Add(item.Name) 225 } 226 return sets 227 } 228 229 oldSets := fromMap(existing.Spec.ConfigItemDetails) 230 newSets := fromMap(expected.Spec.ConfigItemDetails) 231 232 addSets := cfgutil.Difference(newSets, oldSets) 233 delSets := cfgutil.Difference(oldSets, newSets) 234 235 newConfigItems := make([]appsv1alpha1.ConfigurationItemDetail, 0) 236 for _, item := range existing.Spec.ConfigItemDetails { 237 if !delSets.InArray(item.Name) { 238 newConfigItems = append(newConfigItems, item) 239 } 240 } 241 for _, item := range expected.Spec.ConfigItemDetails { 242 if addSets.InArray(item.Name) { 243 newConfigItems = append(newConfigItems, item) 244 } 245 } 246 247 patch := client.MergeFrom(existing) 248 updated := existing.DeepCopy() 249 updated.Spec.ConfigItemDetails = newConfigItems 250 return p.Client.Patch(p.Context, updated, patch) 251 } 252 253 func (p *updatePipeline) isDone() bool { 254 return !p.reconcile 255 } 256 257 func (p *updatePipeline) PrepareForTemplate() *updatePipeline { 258 buildTemplate := func() (err error) { 259 p.reconcile = !intctrlutil.IsApplyConfigChanged(p.ConfigMapObj, p.item) 260 if p.isDone() { 261 return 262 } 263 templateBuilder := newTemplateBuilder(p.ClusterName, p.Namespace, p.ctx.Cluster, p.ctx.ClusterVer, p.Context, p.Client) 264 // Prepare built-in objects and built-in functions 265 if err = templateBuilder.injectBuiltInObjectsAndFunctions(p.ctx.PodSpec, []appsv1alpha1.ComponentConfigSpec{*p.configSpec}, p.ctx.Component, p.ctx.Cache); err != nil { 266 return 267 } 268 p.renderWrapper = newTemplateRenderWrapper(templateBuilder, p.ctx.Cluster, p.Context, p.Client) 269 return 270 } 271 return p.Wrap(buildTemplate) 272 } 273 274 func (p *updatePipeline) ConfigSpec() *appsv1alpha1.ComponentConfigSpec { 275 return p.configSpec 276 } 277 278 func (p *updatePipeline) InitConfigSpec() *updatePipeline { 279 return p.Wrap(func() (err error) { 280 if p.configSpec == nil { 281 p.configSpec = component.GetConfigSpecByName(p.ctx.Component, p.item.Name) 282 if p.configSpec == nil { 283 return core.MakeError("not found config spec: %s", p.item.Name) 284 } 285 } 286 return 287 }) 288 } 289 290 func (p *updatePipeline) RerenderTemplate() *updatePipeline { 291 return p.Wrap(func() (err error) { 292 if p.isDone() { 293 return 294 } 295 if intctrlutil.IsRerender(p.ConfigMapObj, p.item) { 296 p.newCM, err = p.renderWrapper.rerenderConfigTemplate(p.ctx.Cluster, p.ctx.Component, *p.configSpec, &p.item) 297 } else { 298 p.newCM = p.ConfigMapObj.DeepCopy() 299 } 300 return 301 }) 302 } 303 304 func (p *updatePipeline) ApplyParameters() *updatePipeline { 305 patchMerge := func(p *updatePipeline, spec appsv1alpha1.ComponentConfigSpec, cm *corev1.ConfigMap, item appsv1alpha1.ConfigurationItemDetail) error { 306 if p.isDone() || len(item.ConfigFileParams) == 0 { 307 return nil 308 } 309 newData, err := DoMerge(cm.Data, item.ConfigFileParams, p.ConfigConstraintObj, spec) 310 if err != nil { 311 return err 312 } 313 if p.ConfigConstraintObj == nil { 314 cm.Data = newData 315 return nil 316 } 317 318 p.configPatch, _, err = core.CreateConfigPatch(cm.Data, 319 newData, 320 p.ConfigConstraintObj.Spec.FormatterConfig.Format, 321 p.configSpec.Keys, 322 false) 323 if err != nil { 324 return err 325 } 326 cm.Data = newData 327 return nil 328 } 329 330 return p.Wrap(func() error { 331 if p.isDone() { 332 return nil 333 } 334 return patchMerge(p, *p.configSpec, p.newCM, p.item) 335 }) 336 } 337 338 func (p *updatePipeline) UpdateConfigVersion(revision string) *updatePipeline { 339 return p.Wrap(func() error { 340 if p.isDone() { 341 return nil 342 } 343 344 if err := updateConfigMetaForCM(p.newCM, &p.item, revision); err != nil { 345 return err 346 } 347 annotations := p.newCM.Annotations 348 if annotations == nil { 349 annotations = make(map[string]string) 350 } 351 352 // delete disable reconcile annotation 353 if _, ok := annotations[constant.DisableUpgradeInsConfigurationAnnotationKey]; ok { 354 annotations[constant.DisableUpgradeInsConfigurationAnnotationKey] = strconv.FormatBool(false) 355 } 356 p.newCM.Annotations = annotations 357 // p.itemStatus.UpdateRevision = revision 358 return nil 359 }) 360 } 361 362 func (p *updatePipeline) Sync() *updatePipeline { 363 return p.Wrap(func() error { 364 if p.ConfigConstraintObj != nil && !p.isDone() { 365 if err := SyncEnvConfigmap(*p.configSpec, p.newCM, &p.ConfigConstraintObj.Spec, p.Client, p.Context); err != nil { 366 return err 367 } 368 } 369 switch { 370 case p.isDone(): 371 return nil 372 case p.ConfigMapObj == nil && p.newCM != nil: 373 return p.Client.Create(p.Context, p.newCM) 374 case p.ConfigMapObj != nil: 375 patch := client.MergeFrom(p.ConfigMapObj) 376 return p.Client.Patch(p.Context, p.newCM, patch) 377 } 378 return core.MakeError("unexpected condition") 379 }) 380 } 381 382 func (p *updatePipeline) SyncStatus() *updatePipeline { 383 return p.Wrap(func() (err error) { 384 if p.isDone() { 385 return 386 } 387 if p.configSpec == nil || p.itemStatus == nil { 388 return 389 } 390 p.itemStatus.Phase = appsv1alpha1.CMergedPhase 391 return 392 }) 393 }