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  }