github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controller/configuration/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 configuration
    21  
    22  import (
    23  	"strconv"
    24  
    25  	corev1 "k8s.io/api/core/v1"
    26  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    27  	"sigs.k8s.io/controller-runtime/pkg/client"
    28  
    29  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    30  	"github.com/1aal/kubeblocks/pkg/configuration/core"
    31  	cfgutil "github.com/1aal/kubeblocks/pkg/configuration/util"
    32  	"github.com/1aal/kubeblocks/pkg/constant"
    33  	"github.com/1aal/kubeblocks/pkg/controller/builder"
    34  	"github.com/1aal/kubeblocks/pkg/controller/component"
    35  	intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    36  )
    37  
    38  type ReconcileCtx struct {
    39  	*intctrlutil.ResourceCtx
    40  
    41  	Cluster    *appsv1alpha1.Cluster
    42  	ClusterVer *appsv1alpha1.ClusterVersion
    43  	Component  *component.SynthesizedComponent
    44  	PodSpec    *corev1.PodSpec
    45  
    46  	Object client.Object
    47  	Cache  []client.Object
    48  }
    49  
    50  type pipeline struct {
    51  	// configuration *appsv1alpha1.Configuration
    52  	renderWrapper renderWrapper
    53  
    54  	ctx ReconcileCtx
    55  	intctrlutil.ResourceFetcher[pipeline]
    56  }
    57  
    58  type updatePipeline struct {
    59  	reconcile     bool
    60  	renderWrapper renderWrapper
    61  
    62  	item       appsv1alpha1.ConfigurationItemDetail
    63  	itemStatus *appsv1alpha1.ConfigurationItemDetailStatus
    64  	configSpec *appsv1alpha1.ComponentConfigSpec
    65  	// replace of ConfigMapObj
    66  	// originalCM  *corev1.ConfigMap
    67  	newCM       *corev1.ConfigMap
    68  	configPatch *core.ConfigPatchInfo
    69  
    70  	ctx ReconcileCtx
    71  	intctrlutil.ResourceFetcher[updatePipeline]
    72  }
    73  
    74  func NewCreatePipeline(ctx ReconcileCtx) *pipeline {
    75  	p := &pipeline{ctx: ctx}
    76  	return p.Init(ctx.ResourceCtx, p)
    77  }
    78  
    79  func NewReconcilePipeline(ctx ReconcileCtx, item appsv1alpha1.ConfigurationItemDetail, itemStatus *appsv1alpha1.ConfigurationItemDetailStatus, configSpec *appsv1alpha1.ComponentConfigSpec) *updatePipeline {
    80  	p := &updatePipeline{
    81  		reconcile:  true,
    82  		item:       item,
    83  		itemStatus: itemStatus,
    84  		ctx:        ctx,
    85  		configSpec: configSpec,
    86  	}
    87  	return p.Init(ctx.ResourceCtx, p)
    88  }
    89  
    90  func (p *pipeline) Prepare() *pipeline {
    91  	buildTemplate := func() (err error) {
    92  		ctx := p.ctx
    93  		templateBuilder := newTemplateBuilder(p.ClusterName, p.Namespace, ctx.Cluster, ctx.ClusterVer, p.Context, p.Client)
    94  		// Prepare built-in objects and built-in functions
    95  		if err = templateBuilder.injectBuiltInObjectsAndFunctions(ctx.PodSpec, ctx.Component.ConfigTemplates, ctx.Component, ctx.Cache); err != nil {
    96  			return
    97  		}
    98  		p.renderWrapper = newTemplateRenderWrapper(templateBuilder, ctx.Cluster, p.Context, ctx.Client)
    99  		return
   100  	}
   101  
   102  	return p.Wrap(buildTemplate)
   103  }
   104  
   105  func (p *pipeline) RenderScriptTemplate() *pipeline {
   106  	return p.Wrap(func() error {
   107  		ctx := p.ctx
   108  		return p.renderWrapper.renderScriptTemplate(ctx.Cluster, ctx.Component, ctx.Cache)
   109  	})
   110  }
   111  
   112  func (p *pipeline) UpdateConfiguration() *pipeline {
   113  	buildConfiguration := func() (err error) {
   114  		expectedConfiguration := p.createConfiguration()
   115  		if intctrlutil.SetControllerReference(p.ctx.Cluster, expectedConfiguration) != nil {
   116  			return
   117  		}
   118  
   119  		existingConfiguration := appsv1alpha1.Configuration{}
   120  		err = p.ResourceFetcher.Client.Get(p.Context, client.ObjectKeyFromObject(expectedConfiguration), &existingConfiguration)
   121  		switch {
   122  		case err == nil:
   123  			return p.updateConfiguration(expectedConfiguration, &existingConfiguration)
   124  		case apierrors.IsNotFound(err):
   125  			return p.ResourceFetcher.Client.Create(p.Context, expectedConfiguration)
   126  		default:
   127  			return err
   128  		}
   129  	}
   130  	return p.Wrap(buildConfiguration)
   131  }
   132  
   133  func (p *pipeline) CreateConfigTemplate() *pipeline {
   134  	return p.Wrap(func() error {
   135  		ctx := p.ctx
   136  		return p.renderWrapper.renderConfigTemplate(ctx.Cluster, ctx.Component, ctx.Cache, p.ConfigurationObj)
   137  	})
   138  }
   139  
   140  func (p *pipeline) UpdateConfigurationStatus() *pipeline {
   141  	return p.Wrap(func() error {
   142  		if p.ConfigurationObj == nil {
   143  			return nil
   144  		}
   145  
   146  		existing := p.ConfigurationObj
   147  		reversion := fromConfiguration(existing)
   148  		patch := client.MergeFrom(existing)
   149  		updated := existing.DeepCopy()
   150  		for _, item := range existing.Spec.ConfigItemDetails {
   151  			CheckAndUpdateItemStatus(updated, item, reversion)
   152  		}
   153  		return p.ResourceFetcher.Client.Status().Patch(p.Context, updated, patch)
   154  	})
   155  }
   156  
   157  func CheckAndUpdateItemStatus(updated *appsv1alpha1.Configuration, item appsv1alpha1.ConfigurationItemDetail, reversion string) {
   158  	foundStatus := func(name string) *appsv1alpha1.ConfigurationItemDetailStatus {
   159  		for i := range updated.Status.ConfigurationItemStatus {
   160  			status := &updated.Status.ConfigurationItemStatus[i]
   161  			if status.Name == name {
   162  				return status
   163  			}
   164  		}
   165  		return nil
   166  	}
   167  
   168  	status := foundStatus(item.Name)
   169  	if status != nil && status.Phase == "" {
   170  		status.Phase = appsv1alpha1.CInitPhase
   171  	}
   172  	if status == nil {
   173  		updated.Status.ConfigurationItemStatus = append(updated.Status.ConfigurationItemStatus,
   174  			appsv1alpha1.ConfigurationItemDetailStatus{
   175  				Name:           item.Name,
   176  				Phase:          appsv1alpha1.CInitPhase,
   177  				UpdateRevision: reversion,
   178  			})
   179  	}
   180  }
   181  
   182  func (p *pipeline) UpdatePodVolumes() *pipeline {
   183  	return p.Wrap(func() error {
   184  		return intctrlutil.CreateOrUpdatePodVolumes(p.ctx.PodSpec, p.renderWrapper.volumes)
   185  	})
   186  }
   187  
   188  func (p *pipeline) BuildConfigManagerSidecar() *pipeline {
   189  	return p.Wrap(func() error {
   190  		return buildConfigManagerWithComponent(p.ctx.PodSpec, p.ctx.Component.ConfigTemplates, p.Context, p.Client, p.ctx.Cluster, p.ctx.Component)
   191  	})
   192  }
   193  
   194  func (p *pipeline) UpdateConfigRelatedObject() *pipeline {
   195  	updateMeta := func() error {
   196  		if p.ctx.Object != nil {
   197  			updateResourceAnnotationsWithTemplate(p.ctx.Object, p.renderWrapper.templateAnnotations)
   198  		}
   199  		if err := injectTemplateEnvFrom(p.ctx.Cluster, p.ctx.Component, p.ctx.PodSpec, p.Client, p.Context, p.renderWrapper.renderedObjs); err != nil {
   200  			return err
   201  		}
   202  		return createConfigObjects(p.Client, p.Context, p.renderWrapper.renderedObjs)
   203  	}
   204  
   205  	return p.Wrap(updateMeta)
   206  }
   207  
   208  func (p *pipeline) createConfiguration() *appsv1alpha1.Configuration {
   209  	builder := builder.NewConfigurationBuilder(p.Namespace,
   210  		core.GenerateComponentConfigurationName(p.ClusterName, p.ComponentName),
   211  	)
   212  	for _, template := range p.ctx.Component.ConfigTemplates {
   213  		builder.AddConfigurationItem(template)
   214  	}
   215  	return builder.Component(p.ComponentName).
   216  		ClusterRef(p.ClusterName).
   217  		GetObject()
   218  }
   219  
   220  func (p *pipeline) updateConfiguration(expected *appsv1alpha1.Configuration, existing *appsv1alpha1.Configuration) error {
   221  	fromMap := func(items []appsv1alpha1.ConfigurationItemDetail) *cfgutil.Sets {
   222  		sets := cfgutil.NewSet()
   223  		for _, item := range items {
   224  			sets.Add(item.Name)
   225  		}
   226  		return sets
   227  	}
   228  
   229  	oldSets := fromMap(existing.Spec.ConfigItemDetails)
   230  	newSets := fromMap(expected.Spec.ConfigItemDetails)
   231  
   232  	addSets := cfgutil.Difference(newSets, oldSets)
   233  	delSets := cfgutil.Difference(oldSets, newSets)
   234  
   235  	newConfigItems := make([]appsv1alpha1.ConfigurationItemDetail, 0)
   236  	for _, item := range existing.Spec.ConfigItemDetails {
   237  		if !delSets.InArray(item.Name) {
   238  			newConfigItems = append(newConfigItems, item)
   239  		}
   240  	}
   241  	for _, item := range expected.Spec.ConfigItemDetails {
   242  		if addSets.InArray(item.Name) {
   243  			newConfigItems = append(newConfigItems, item)
   244  		}
   245  	}
   246  
   247  	patch := client.MergeFrom(existing)
   248  	updated := existing.DeepCopy()
   249  	updated.Spec.ConfigItemDetails = newConfigItems
   250  	return p.Client.Patch(p.Context, updated, patch)
   251  }
   252  
   253  func (p *updatePipeline) isDone() bool {
   254  	return !p.reconcile
   255  }
   256  
   257  func (p *updatePipeline) PrepareForTemplate() *updatePipeline {
   258  	buildTemplate := func() (err error) {
   259  		p.reconcile = !intctrlutil.IsApplyConfigChanged(p.ConfigMapObj, p.item)
   260  		if p.isDone() {
   261  			return
   262  		}
   263  		templateBuilder := newTemplateBuilder(p.ClusterName, p.Namespace, p.ctx.Cluster, p.ctx.ClusterVer, p.Context, p.Client)
   264  		// Prepare built-in objects and built-in functions
   265  		if err = templateBuilder.injectBuiltInObjectsAndFunctions(p.ctx.PodSpec, []appsv1alpha1.ComponentConfigSpec{*p.configSpec}, p.ctx.Component, p.ctx.Cache); err != nil {
   266  			return
   267  		}
   268  		p.renderWrapper = newTemplateRenderWrapper(templateBuilder, p.ctx.Cluster, p.Context, p.Client)
   269  		return
   270  	}
   271  	return p.Wrap(buildTemplate)
   272  }
   273  
   274  func (p *updatePipeline) ConfigSpec() *appsv1alpha1.ComponentConfigSpec {
   275  	return p.configSpec
   276  }
   277  
   278  func (p *updatePipeline) InitConfigSpec() *updatePipeline {
   279  	return p.Wrap(func() (err error) {
   280  		if p.configSpec == nil {
   281  			p.configSpec = component.GetConfigSpecByName(p.ctx.Component, p.item.Name)
   282  			if p.configSpec == nil {
   283  				return core.MakeError("not found config spec: %s", p.item.Name)
   284  			}
   285  		}
   286  		return
   287  	})
   288  }
   289  
   290  func (p *updatePipeline) RerenderTemplate() *updatePipeline {
   291  	return p.Wrap(func() (err error) {
   292  		if p.isDone() {
   293  			return
   294  		}
   295  		if intctrlutil.IsRerender(p.ConfigMapObj, p.item) {
   296  			p.newCM, err = p.renderWrapper.rerenderConfigTemplate(p.ctx.Cluster, p.ctx.Component, *p.configSpec, &p.item)
   297  		} else {
   298  			p.newCM = p.ConfigMapObj.DeepCopy()
   299  		}
   300  		return
   301  	})
   302  }
   303  
   304  func (p *updatePipeline) ApplyParameters() *updatePipeline {
   305  	patchMerge := func(p *updatePipeline, spec appsv1alpha1.ComponentConfigSpec, cm *corev1.ConfigMap, item appsv1alpha1.ConfigurationItemDetail) error {
   306  		if p.isDone() || len(item.ConfigFileParams) == 0 {
   307  			return nil
   308  		}
   309  		newData, err := DoMerge(cm.Data, item.ConfigFileParams, p.ConfigConstraintObj, spec)
   310  		if err != nil {
   311  			return err
   312  		}
   313  		if p.ConfigConstraintObj == nil {
   314  			cm.Data = newData
   315  			return nil
   316  		}
   317  
   318  		p.configPatch, _, err = core.CreateConfigPatch(cm.Data,
   319  			newData,
   320  			p.ConfigConstraintObj.Spec.FormatterConfig.Format,
   321  			p.configSpec.Keys,
   322  			false)
   323  		if err != nil {
   324  			return err
   325  		}
   326  		cm.Data = newData
   327  		return nil
   328  	}
   329  
   330  	return p.Wrap(func() error {
   331  		if p.isDone() {
   332  			return nil
   333  		}
   334  		return patchMerge(p, *p.configSpec, p.newCM, p.item)
   335  	})
   336  }
   337  
   338  func (p *updatePipeline) UpdateConfigVersion(revision string) *updatePipeline {
   339  	return p.Wrap(func() error {
   340  		if p.isDone() {
   341  			return nil
   342  		}
   343  
   344  		if err := updateConfigMetaForCM(p.newCM, &p.item, revision); err != nil {
   345  			return err
   346  		}
   347  		annotations := p.newCM.Annotations
   348  		if annotations == nil {
   349  			annotations = make(map[string]string)
   350  		}
   351  
   352  		// delete disable reconcile annotation
   353  		if _, ok := annotations[constant.DisableUpgradeInsConfigurationAnnotationKey]; ok {
   354  			annotations[constant.DisableUpgradeInsConfigurationAnnotationKey] = strconv.FormatBool(false)
   355  		}
   356  		p.newCM.Annotations = annotations
   357  		// p.itemStatus.UpdateRevision = revision
   358  		return nil
   359  	})
   360  }
   361  
   362  func (p *updatePipeline) Sync() *updatePipeline {
   363  	return p.Wrap(func() error {
   364  		if p.ConfigConstraintObj != nil && !p.isDone() {
   365  			if err := SyncEnvConfigmap(*p.configSpec, p.newCM, &p.ConfigConstraintObj.Spec, p.Client, p.Context); err != nil {
   366  				return err
   367  			}
   368  		}
   369  		switch {
   370  		case p.isDone():
   371  			return nil
   372  		case p.ConfigMapObj == nil && p.newCM != nil:
   373  			return p.Client.Create(p.Context, p.newCM)
   374  		case p.ConfigMapObj != nil:
   375  			patch := client.MergeFrom(p.ConfigMapObj)
   376  			return p.Client.Patch(p.Context, p.newCM, patch)
   377  		}
   378  		return core.MakeError("unexpected condition")
   379  	})
   380  }
   381  
   382  func (p *updatePipeline) SyncStatus() *updatePipeline {
   383  	return p.Wrap(func() (err error) {
   384  		if p.isDone() {
   385  			return
   386  		}
   387  		if p.configSpec == nil || p.itemStatus == nil {
   388  			return
   389  		}
   390  		p.itemStatus.Phase = appsv1alpha1.CMergedPhase
   391  		return
   392  	})
   393  }