github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/operations/reconfigure_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 operations 21 22 import ( 23 "sigs.k8s.io/controller-runtime/pkg/client" 24 25 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 26 cfgcore "github.com/1aal/kubeblocks/pkg/configuration/core" 27 "github.com/1aal/kubeblocks/pkg/configuration/validate" 28 "github.com/1aal/kubeblocks/pkg/controller/configuration" 29 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 30 ) 31 32 type reconfigureContext struct { 33 // reconfiguring request 34 config appsv1alpha1.ConfigurationItem 35 36 cli client.Client 37 reqCtx intctrlutil.RequestCtx 38 resource *OpsResource 39 40 clusterName string 41 componentName string 42 } 43 44 type pipeline struct { 45 isFailed bool 46 47 updatedParameters []cfgcore.ParamPairs 48 mergedConfig map[string]string 49 configPatch *cfgcore.ConfigPatchInfo 50 isFileUpdated bool 51 52 updatedObject *appsv1alpha1.Configuration 53 configConstraint *appsv1alpha1.ConfigConstraint 54 configSpec *appsv1alpha1.ComponentConfigSpec 55 56 reconfigureContext 57 intctrlutil.ResourceFetcher[pipeline] 58 } 59 60 func newPipeline(ctx reconfigureContext) *pipeline { 61 pipeline := &pipeline{reconfigureContext: ctx} 62 pipeline.Init(&intctrlutil.ResourceCtx{ 63 Client: ctx.cli, 64 Context: ctx.reqCtx.Ctx, 65 Namespace: ctx.resource.OpsRequest.Namespace, 66 ClusterName: ctx.clusterName, 67 ComponentName: ctx.componentName, 68 }, pipeline) 69 pipeline.ClusterObj = ctx.resource.Cluster 70 return pipeline 71 } 72 73 func (p *pipeline) Validate() *pipeline { 74 validateFn := func() error { 75 if p.ConfigurationObj == nil { 76 return cfgcore.MakeError("failed to found configuration of component[%s] in the cluster[%s]", 77 p.reconfigureContext.componentName, 78 p.reconfigureContext.clusterName, 79 ) 80 } 81 82 item := p.ConfigurationObj.Spec.GetConfigurationItem(p.config.Name) 83 if item == nil || item.ConfigSpec == nil { 84 p.isFailed = true 85 return cfgcore.MakeError("failed to reconfigure, not existed config[%s]", p.config.Name) 86 } 87 88 p.configSpec = item.ConfigSpec 89 return nil 90 } 91 92 return p.Wrap(validateFn) 93 } 94 95 func (p *pipeline) ConfigConstraints() *pipeline { 96 validateFn := func() (err error) { 97 if !hasFileUpdate(p.config) { 98 p.isFailed = true 99 err = cfgcore.MakeError( 100 "current configSpec not support reconfigure, configSpec: %v", 101 p.configSpec.Name) 102 } 103 return 104 } 105 106 fetchCCFn := func() error { 107 ccKey := client.ObjectKey{ 108 Name: p.configSpec.ConfigConstraintRef, 109 } 110 p.configConstraint = &appsv1alpha1.ConfigConstraint{} 111 return p.cli.Get(p.reqCtx.Ctx, ccKey, p.configConstraint) 112 } 113 114 return p.Wrap(func() error { 115 if p.configSpec.ConfigConstraintRef == "" { 116 return validateFn() 117 } else { 118 return fetchCCFn() 119 } 120 }) 121 } 122 123 func (p *pipeline) doMergeImpl(parameters appsv1alpha1.ConfigurationItem) error { 124 newConfigObj := p.ConfigurationObj.DeepCopy() 125 126 item := newConfigObj.Spec.GetConfigurationItem(p.config.Name) 127 if item == nil { 128 return cfgcore.MakeError("not found config item: %s", parameters.Name) 129 } 130 131 configSpec := p.configSpec 132 if item.ConfigFileParams == nil { 133 item.ConfigFileParams = make(map[string]appsv1alpha1.ConfigParams) 134 } 135 filter := validate.WithKeySelector(configSpec.Keys) 136 for _, key := range parameters.Keys { 137 // patch parameters 138 if configSpec.ConfigConstraintRef != "" && filter(key.Key) { 139 if key.FileContent != "" { 140 return cfgcore.MakeError("not allowed to update file content: %s", key.Key) 141 } 142 updateParameters(item, key.Key, key.Parameters) 143 p.updatedParameters = append(p.updatedParameters, cfgcore.ParamPairs{ 144 Key: key.Key, 145 UpdatedParams: fromKeyValuePair(key.Parameters), 146 }) 147 continue 148 } 149 // update file content 150 if len(key.Parameters) != 0 { 151 return cfgcore.MakeError("not allowed to patch parameters: %s", key.Key) 152 } 153 updateFileContent(item, key.Key, key.FileContent) 154 p.isFileUpdated = true 155 } 156 p.updatedObject = newConfigObj 157 return p.createUpdatePatch(item, configSpec) 158 } 159 160 func (p *pipeline) createUpdatePatch(item *appsv1alpha1.ConfigurationItemDetail, configSpec *appsv1alpha1.ComponentConfigSpec) error { 161 if p.configConstraint == nil { 162 return nil 163 } 164 165 updatedData, err := configuration.DoMerge(p.ConfigMapObj.Data, item.ConfigFileParams, p.configConstraint, *configSpec) 166 if err != nil { 167 p.isFailed = true 168 return err 169 } 170 p.configPatch, _, err = cfgcore.CreateConfigPatch(p.ConfigMapObj.Data, 171 updatedData, 172 p.configConstraint.Spec.FormatterConfig.Format, 173 p.configSpec.Keys, 174 false) 175 return err 176 } 177 178 func (p *pipeline) doMerge() error { 179 if p.ConfigurationObj == nil { 180 return cfgcore.MakeError("not found config: %s", 181 cfgcore.GenerateComponentConfigurationName(p.clusterName, p.componentName)) 182 } 183 return p.doMergeImpl(p.config) 184 } 185 186 func (p *pipeline) Merge() *pipeline { 187 return p.Wrap(p.doMerge) 188 } 189 190 func (p *pipeline) UpdateOpsLabel() *pipeline { 191 updateFn := func() error { 192 if len(p.updatedParameters) == 0 || 193 p.configConstraint == nil || 194 p.configConstraint.Spec.FormatterConfig == nil { 195 return nil 196 } 197 198 request := p.resource.OpsRequest 199 deepObject := request.DeepCopy() 200 formatter := p.configConstraint.Spec.FormatterConfig 201 updateOpsLabelWithReconfigure(request, p.updatedParameters, p.ConfigMapObj.Data, formatter) 202 return p.cli.Patch(p.reqCtx.Ctx, request, client.MergeFrom(deepObject)) 203 } 204 205 return p.Wrap(updateFn) 206 } 207 208 func (p *pipeline) Sync() *pipeline { 209 return p.Wrap(func() error { 210 return p.Client.Patch(p.reqCtx.Ctx, p.updatedObject, client.MergeFrom(p.ConfigurationObj)) 211 }) 212 } 213 214 func (p *pipeline) Complete() reconfiguringResult { 215 if p.Err != nil { 216 return makeReconfiguringResult(p.Err, withFailed(p.isFailed)) 217 } 218 219 return makeReconfiguringResult(nil, 220 withReturned(p.mergedConfig, p.configPatch), 221 withNoFormatFilesUpdated(p.isFileUpdated), 222 ) 223 }