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  }