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  }