github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controllerutil/config_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 controllerutil 21 22 import ( 23 "context" 24 "encoding/json" 25 "reflect" 26 27 "github.com/StudioSol/set" 28 corev1 "k8s.io/api/core/v1" 29 "sigs.k8s.io/controller-runtime/pkg/log" 30 31 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 32 "github.com/1aal/kubeblocks/pkg/configuration/core" 33 "github.com/1aal/kubeblocks/pkg/configuration/util" 34 "github.com/1aal/kubeblocks/pkg/configuration/validate" 35 "github.com/1aal/kubeblocks/pkg/constant" 36 ) 37 38 type Result struct { 39 Phase v1alpha1.ConfigurationPhase `json:"phase"` 40 Revision string `json:"revision"` 41 Policy string `json:"policy"` 42 ExecResult string `json:"execResult"` 43 44 SucceedCount int32 `json:"succeedCount"` 45 ExpectedCount int32 `json:"expectedCount"` 46 47 Retry bool `json:"retry"` 48 Failed bool `json:"failed"` 49 Message string `json:"message"` 50 } 51 52 // MergeAndValidateConfigs merges and validates configuration files 53 func MergeAndValidateConfigs(configConstraint v1alpha1.ConfigConstraintSpec, baseConfigs map[string]string, cmKey []string, updatedParams []core.ParamPairs) (map[string]string, error) { 54 var ( 55 err error 56 fc = configConstraint.FormatterConfig 57 58 newCfg map[string]string 59 configOperator core.ConfigOperator 60 updatedKeys = util.NewSet() 61 ) 62 63 cmKeySet := core.FromCMKeysSelector(cmKey) 64 configLoaderOption := core.CfgOption{ 65 Type: core.CfgCmType, 66 Log: log.FromContext(context.TODO()), 67 CfgType: fc.Format, 68 ConfigResource: core.FromConfigData(baseConfigs, cmKeySet), 69 } 70 if configOperator, err = core.NewConfigLoader(configLoaderOption); err != nil { 71 return nil, err 72 } 73 74 // merge param to config file 75 for _, params := range updatedParams { 76 if err := configOperator.MergeFrom(params.UpdatedParams, core.NewCfgOptions(params.Key, core.WithFormatterConfig(fc))); err != nil { 77 return nil, err 78 } 79 updatedKeys.Add(params.Key) 80 } 81 82 if newCfg, err = configOperator.ToCfgContent(); err != nil { 83 return nil, core.WrapError(err, "failed to generate config file") 84 } 85 86 // The ToCfgContent interface returns the file contents of all keys, the configuration file is encoded and decoded into keys, 87 // the content may be different with the original file, such as comments, blank lines, etc, 88 // in order to minimize the impact on the original file, only update the changed part. 89 updatedCfg := fromUpdatedConfig(newCfg, updatedKeys) 90 if err = validate.NewConfigValidator(&configConstraint, validate.WithKeySelector(cmKey)).Validate(updatedCfg); err != nil { 91 return nil, core.WrapError(err, "failed to validate updated config") 92 } 93 return core.MergeUpdatedConfig(baseConfigs, updatedCfg), nil 94 } 95 96 // fromUpdatedConfig filters out changed file contents. 97 func fromUpdatedConfig(m map[string]string, sets *set.LinkedHashSetString) map[string]string { 98 if sets.Length() == 0 { 99 return map[string]string{} 100 } 101 102 r := make(map[string]string, sets.Length()) 103 for key, v := range m { 104 if sets.InArray(key) { 105 r[key] = v 106 } 107 } 108 return r 109 } 110 111 // IsApplyConfigChanged checks if the configuration is changed 112 func IsApplyConfigChanged(configMap *corev1.ConfigMap, item v1alpha1.ConfigurationItemDetail) bool { 113 if configMap == nil { 114 return false 115 } 116 117 lastAppliedVersion, ok := configMap.Annotations[constant.ConfigAppliedVersionAnnotationKey] 118 if !ok { 119 return false 120 } 121 var target v1alpha1.ConfigurationItemDetail 122 if err := json.Unmarshal([]byte(lastAppliedVersion), &target); err != nil { 123 return false 124 } 125 126 return reflect.DeepEqual(target, item) 127 } 128 129 // IsRerender checks if the configuration template is changed 130 func IsRerender(configMap *corev1.ConfigMap, item v1alpha1.ConfigurationItemDetail) bool { 131 if configMap == nil { 132 return true 133 } 134 if item.Version == "" { 135 return false 136 } 137 138 version, ok := configMap.Annotations[constant.CMConfigurationTemplateVersion] 139 if !ok || version != item.Version { 140 return true 141 } 142 return false 143 } 144 145 // GetConfigSpecReconcilePhase gets the configuration phase 146 func GetConfigSpecReconcilePhase(configMap *corev1.ConfigMap, 147 item v1alpha1.ConfigurationItemDetail, 148 status *v1alpha1.ConfigurationItemDetailStatus) v1alpha1.ConfigurationPhase { 149 if status == nil || status.Phase == "" { 150 return v1alpha1.CCreatingPhase 151 } 152 if !IsApplyConfigChanged(configMap, item) { 153 return v1alpha1.CPendingPhase 154 } 155 return status.Phase 156 }