github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/operations/reconfigure_util.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 "encoding/json" 24 "fmt" 25 26 "github.com/spf13/cast" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "sigs.k8s.io/controller-runtime/pkg/log" 29 30 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 31 "github.com/1aal/kubeblocks/pkg/configuration/core" 32 ) 33 34 type reconfiguringResult struct { 35 failed bool 36 noFormatFilesUpdated bool 37 configPatch *core.ConfigPatchInfo 38 lastAppliedConfigs map[string]string 39 err error 40 } 41 42 // type updateReconfigureStatus func(params []core.ParamPairs, orinalData map[string]string, formatter *appsv1alpha1.FormatterConfig) error 43 44 // Deprecated: use NewPipeline instead 45 // updateConfigConfigmapResource merges parameters of the config into the configmap, and verifies final configuration file. 46 // func updateConfigConfigmapResource(config appsv1alpha1.ConfigurationItem, 47 // configSpec appsv1alpha1.ComponentConfigSpec, 48 // cmKey client.ObjectKey, 49 // ctx context.Context, 50 // cli client.Client, 51 // opsCrName string, 52 // updater updateReconfigureStatus) reconfiguringResult { 53 // var ( 54 // cm = &corev1.ConfigMap{} 55 // cc = &appsv1alpha1.ConfigConstraint{} 56 // 57 // err error 58 // newCfg map[string]string 59 // ) 60 // 61 // if err := cli.Get(ctx, cmKey, cm); err != nil { 62 // return makeReconfiguringResult(err) 63 // } 64 // if err := cli.Get(ctx, client.ObjectKey{ 65 // Namespace: configSpec.Namespace, 66 // Name: configSpec.ConfigConstraintRef, 67 // }, cc); err != nil { 68 // return makeReconfiguringResult(err) 69 // } 70 // 71 // updatedFiles := make(map[string]string, len(config.Keys)) 72 // updatedParams := make([]core.ParamPairs, 0, len(config.Keys)) 73 // for _, key := range config.Keys { 74 // if key.FileContent != "" { 75 // updatedFiles[key.Key] = key.FileContent 76 // } 77 // if len(key.Parameters) > 0 { 78 // updatedParams = append(updatedParams, core.ParamPairs{ 79 // Key: key.Key, 80 // UpdatedParams: fromKeyValuePair(key.Parameters), 81 // }) 82 // } 83 // } 84 // 85 // if newCfg, err = mergeUpdatedParams(cm.Data, updatedFiles, updatedParams, cc, configSpec); err != nil { 86 // return makeReconfiguringResult(err, withFailed(true)) 87 // } 88 // configPatch, restart, err := core.CreateConfigPatch(cm.Data, newCfg, cc.Spec.FormatterConfig.Format, configSpec.Keys, len(updatedFiles) != 0) 89 // if err != nil { 90 // return makeReconfiguringResult(err) 91 // } 92 // if !restart && !configPatch.IsModify { 93 // return makeReconfiguringResult(nil, withReturned(newCfg, configPatch)) 94 // } 95 // if updater != nil { 96 // if err := updater(updatedParams, cm.Data, cc.Spec.FormatterConfig); err != nil { 97 // return makeReconfiguringResult(err) 98 // } 99 // } 100 // 101 // return makeReconfiguringResult( 102 // syncConfigmap(cm, newCfg, cli, ctx, opsCrName, configSpec, &cc.Spec, config.Policy), 103 // withReturned(newCfg, configPatch), 104 // withNoFormatFilesUpdated(restart)) 105 // } 106 107 // func mergeUpdatedParams(base map[string]string, 108 // updatedFiles map[string]string, 109 // updatedParams []core.ParamPairs, 110 // cc *appsv1alpha1.ConfigConstraint, 111 // tpl appsv1alpha1.ComponentConfigSpec) (map[string]string, error) { 112 // updatedConfig := base 113 // 114 // // merge updated files into configmap 115 // if len(updatedFiles) != 0 { 116 // return core.MergeUpdatedConfig(base, updatedFiles), nil 117 // } 118 // if cc == nil { 119 // return updatedConfig, nil 120 // } 121 // return intctrlutil.MergeAndValidateConfigs(cc.Spec, updatedConfig, tpl.Keys, updatedParams) 122 // } 123 124 // func syncConfigmap( 125 // cmObj *corev1.ConfigMap, 126 // newCfg map[string]string, 127 // cli client.Client, 128 // ctx context.Context, 129 // opsCrName string, 130 // configSpec appsv1alpha1.ComponentConfigSpec, 131 // cc *appsv1alpha1.ConfigConstraintSpec, 132 // policy *appsv1alpha1.UpgradePolicy) error { 133 // 134 // patch := client.MergeFrom(cmObj.DeepCopy()) 135 // cmObj.Data = newCfg 136 // if cmObj.Annotations == nil { 137 // cmObj.Annotations = make(map[string]string) 138 // } 139 // if policy != nil { 140 // cmObj.Annotations[constant.UpgradePolicyAnnotationKey] = string(*policy) 141 // } 142 // cmObj.Annotations[constant.LastAppliedOpsCRAnnotationKey] = opsCrName 143 // core.SetParametersUpdateSource(cmObj, constant.ReconfigureUserSource) 144 // if err := configuration.SyncEnvConfigmap(configSpec, cmObj, cc, cli, ctx); err != nil { 145 // return err 146 // } 147 // return cli.Patch(ctx, cmObj, patch) 148 // } 149 150 func updateOpsLabelWithReconfigure(obj *appsv1alpha1.OpsRequest, params []core.ParamPairs, orinalData map[string]string, formatter *appsv1alpha1.FormatterConfig) { 151 var maxLabelCount = 16 152 updateLabel := func(param map[string]interface{}) { 153 if obj.Labels == nil { 154 obj.Labels = make(map[string]string) 155 } 156 for key, val := range param { 157 if maxLabelCount <= 0 { 158 return 159 } 160 maxLabelCount-- 161 obj.Labels[key] = core.FromValueToString(val) 162 } 163 } 164 updateAnnotation := func(keyFile string, param map[string]interface{}) { 165 data, ok := orinalData[keyFile] 166 if !ok { 167 return 168 } 169 if obj.Annotations == nil { 170 obj.Annotations = make(map[string]string) 171 } 172 oldValue, err := fetchOriginalValue(keyFile, data, param, formatter) 173 if err != nil { 174 log.Log.Error(err, "failed to fetch original value") 175 return 176 } 177 obj.Annotations[keyFile] = oldValue 178 } 179 180 for _, param := range params { 181 updateLabel(param.UpdatedParams) 182 if maxLabelCount <= 0 { 183 return 184 } 185 updateAnnotation(param.Key, param.UpdatedParams) 186 } 187 } 188 189 func fetchOriginalValue(keyFile, data string, params map[string]interface{}, formatter *appsv1alpha1.FormatterConfig) (string, error) { 190 baseConfigObj, err := core.FromConfigObject(keyFile, data, formatter) 191 if err != nil { 192 return "", err 193 } 194 r := make(map[string]string, len(params)) 195 for key := range params { 196 oldVal := baseConfigObj.Get(key) 197 if oldVal != nil { 198 r[key] = cast.ToString(oldVal) 199 } 200 } 201 b, err := json.Marshal(r) 202 return string(b), err 203 } 204 205 func fromKeyValuePair(parameters []appsv1alpha1.ParameterPair) map[string]interface{} { 206 m := make(map[string]interface{}, len(parameters)) 207 for _, param := range parameters { 208 if param.Value != nil { 209 m[param.Key] = *param.Value 210 } else { 211 m[param.Key] = nil 212 } 213 } 214 return m 215 } 216 217 func withFailed(failed bool) func(result *reconfiguringResult) { 218 return func(result *reconfiguringResult) { 219 result.failed = failed 220 } 221 } 222 223 func withReturned(configs map[string]string, patch *core.ConfigPatchInfo) func(result *reconfiguringResult) { 224 return func(result *reconfiguringResult) { 225 result.lastAppliedConfigs = configs 226 result.configPatch = patch 227 } 228 } 229 230 func withNoFormatFilesUpdated(changed bool) func(result *reconfiguringResult) { 231 return func(result *reconfiguringResult) { 232 result.noFormatFilesUpdated = changed 233 } 234 } 235 236 func makeReconfiguringResult(err error, ops ...func(*reconfiguringResult)) reconfiguringResult { 237 result := reconfiguringResult{ 238 failed: false, 239 err: err, 240 } 241 for _, o := range ops { 242 o(&result) 243 } 244 return result 245 } 246 247 func constructReconfiguringConditions(result reconfiguringResult, resource *OpsResource, configSpec *appsv1alpha1.ComponentConfigSpec) *metav1.Condition { 248 if result.noFormatFilesUpdated || (result.configPatch != nil && result.configPatch.IsModify) { 249 return appsv1alpha1.NewReconfigureRunningCondition( 250 resource.OpsRequest, 251 appsv1alpha1.ReasonReconfigurePersisted, 252 configSpec.Name, 253 formatConfigPatchToMessage(result.configPatch, nil)) 254 } 255 return appsv1alpha1.NewReconfigureRunningCondition( 256 resource.OpsRequest, 257 appsv1alpha1.ReasonReconfigureNoChanged, 258 configSpec.Name, 259 formatConfigPatchToMessage(result.configPatch, nil)) 260 } 261 262 func i2sMap(config map[string]interface{}) map[string]string { 263 if len(config) == 0 { 264 return nil 265 } 266 m := make(map[string]string, len(config)) 267 for key, value := range config { 268 data, _ := json.Marshal(value) 269 m[key] = string(data) 270 } 271 return m 272 } 273 274 func b2sMap(config map[string][]byte) map[string]string { 275 if len(config) == 0 { 276 return nil 277 } 278 m := make(map[string]string, len(config)) 279 for key, value := range config { 280 m[key] = string(value) 281 } 282 return m 283 } 284 285 func processMergedFailed(resource *OpsResource, isInvalid bool, err error) error { 286 if !isInvalid { 287 return core.WrapError(err, "failed to update param!") 288 } 289 290 // if failed to validate configure, set opsRequest to failed and return 291 failedCondition := appsv1alpha1.NewReconfigureFailedCondition(resource.OpsRequest, err) 292 resource.OpsRequest.SetStatusCondition(*failedCondition) 293 return &FastFaileError{message: err.Error()} 294 } 295 296 func formatConfigPatchToMessage(configPatch *core.ConfigPatchInfo, execStatus *core.PolicyExecStatus) string { 297 policyName := "" 298 if execStatus != nil { 299 policyName = fmt.Sprintf("updated policy: <%s>, ", execStatus.PolicyName) 300 } 301 if configPatch == nil { 302 return fmt.Sprintf("%supdated full config files.", policyName) 303 } 304 return fmt.Sprintf("%supdated: %s, added: %s, deleted:%s", 305 policyName, 306 configPatch.UpdateConfig, 307 configPatch.AddConfig, 308 configPatch.DeleteConfig) 309 } 310 311 func updateFileContent(item *appsv1alpha1.ConfigurationItemDetail, key string, content string) { 312 params, ok := item.ConfigFileParams[key] 313 if !ok { 314 item.ConfigFileParams[key] = appsv1alpha1.ConfigParams{ 315 Content: &content, 316 } 317 return 318 } 319 item.ConfigFileParams[key] = appsv1alpha1.ConfigParams{ 320 Parameters: params.Parameters, 321 Content: &content, 322 } 323 } 324 325 func updateParameters(item *appsv1alpha1.ConfigurationItemDetail, key string, parameters []appsv1alpha1.ParameterPair) { 326 updatedParams := make(map[string]*string, len(parameters)) 327 for _, parameter := range parameters { 328 updatedParams[parameter.Key] = parameter.Value 329 } 330 331 params, ok := item.ConfigFileParams[key] 332 if !ok { 333 item.ConfigFileParams[key] = appsv1alpha1.ConfigParams{ 334 Parameters: updatedParams, 335 } 336 return 337 } 338 339 item.ConfigFileParams[key] = appsv1alpha1.ConfigParams{ 340 Content: params.Content, 341 Parameters: mergeMaps(params.Parameters, updatedParams), 342 } 343 } 344 345 func mergeMaps(m1 map[string]*string, m2 map[string]*string) map[string]*string { 346 merged := make(map[string]*string) 347 for key, value := range m1 { 348 merged[key] = value 349 } 350 for key, value := range m2 { 351 merged[key] = value 352 } 353 return merged 354 } 355 356 func hasFileUpdate(config appsv1alpha1.ConfigurationItem) bool { 357 for _, key := range config.Keys { 358 if key.FileContent != "" { 359 return true 360 } 361 } 362 return false 363 }