From d5ea34ff1269a8da7871b47105f43568b10e2465 Mon Sep 17 00:00:00 2001 From: sophon Date: Mon, 10 Mar 2025 11:34:33 +0800 Subject: [PATCH 1/6] chore: support reconfigure for kb 1.0 api --- go.mod | 2 +- go.sum | 4 +- .../template/cluster_operations_template.cue | 20 +- pkg/cluster/cluster.go | 37 +- pkg/cmd/cluster/cluster.go | 1 - pkg/cmd/cluster/config_diff.go | 279 ------------- pkg/cmd/cluster/config_edit.go | 84 ++-- pkg/cmd/cluster/config_observer.go | 261 +++--------- pkg/cmd/cluster/config_ops.go | 131 +++--- pkg/cmd/cluster/config_ops_test.go | 29 +- pkg/cmd/cluster/config_resource.go | 206 ++-------- pkg/cmd/cluster/config_util.go | 36 +- pkg/cmd/cluster/config_wrapper.go | 387 +++++++++++------- pkg/cmd/cluster/describe_ops.go | 12 +- pkg/cmd/cluster/list_ops.go | 27 -- pkg/printer/describe.go | 33 -- pkg/util/util.go | 153 ++++--- 17 files changed, 585 insertions(+), 1117 deletions(-) delete mode 100644 pkg/cmd/cluster/config_diff.go diff --git a/go.mod b/go.mod index 3f3ea3c31..33ced0326 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/Masterminds/semver/v3 v3.3.0 github.com/NimbleMarkets/ntcharts v0.1.2 github.com/apecloud/dbctl v0.0.0-20240827084000-68a1980b1a46 - github.com/apecloud/kubeblocks v1.0.0-beta.29 + github.com/apecloud/kubeblocks v1.0.0-beta.33 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/briandowns/spinner v1.23.0 github.com/chaos-mesh/chaos-mesh/api v0.0.0-20230912020346-a5d89c1c90ad diff --git a/go.sum b/go.sum index e417f9ce2..ffacc6c8a 100644 --- a/go.sum +++ b/go.sum @@ -677,8 +677,8 @@ github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4x github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/apecloud/dbctl v0.0.0-20240827084000-68a1980b1a46 h1:+Jcc7IjDGxPgIfIkGX2Q5Yxj35U65zgcfjh0B9rDhjo= github.com/apecloud/dbctl v0.0.0-20240827084000-68a1980b1a46/go.mod h1:eksJtZ7z1nVcVLqDzAdcN5EfpHwXllIAvHZEks2zWys= -github.com/apecloud/kubeblocks v1.0.0-beta.29 h1:7t1K7mQxjFz1s7OiBG8XQ8MkP1aW0yQLeTv73LmM0GM= -github.com/apecloud/kubeblocks v1.0.0-beta.29/go.mod h1:b656nTyvHhwRwOuwNpOPG87Q0Lba3ygGRWoSOacPt5o= +github.com/apecloud/kubeblocks v1.0.0-beta.33 h1:A6lWmz/yZEP7s1lP3TXGfyPTixO9BAPJ+6TGwdqiKbk= +github.com/apecloud/kubeblocks v1.0.0-beta.33/go.mod h1:b656nTyvHhwRwOuwNpOPG87Q0Lba3ygGRWoSOacPt5o= github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= diff --git a/pkg/action/template/cluster_operations_template.cue b/pkg/action/template/cluster_operations_template.cue index 0b9f8af92..b31748a26 100644 --- a/pkg/action/template/cluster_operations_template.cue +++ b/pkg/action/template/cluster_operations_template.cue @@ -219,23 +219,9 @@ content: { if options.type == "Reconfiguring" { reconfigures: [ for _, cName in options.componentNames { componentName: cName - configurations: [ { - name: options.cfgTemplateName - if options.forceRestart { - policy: "simple" - } - keys: [{ - key: options.cfgFile - if options.fileContent != "" { - fileContent: options.fileContent - } - if options.hasPatch { - parameters: [ for k, v in options.keyValues { - key: k - value: v - }] - } - }] + parameters: [ for k, v in options.keyValues { + key: k + value: v }] }] } diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index d5f8c1490..6827652f0 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -376,8 +376,7 @@ func (o *ClusterObjects) GetComponentInfo() []*ComponentInfo { if ins.Resources != nil { resources = *ins.Resources } - vcts := o.getCompTemplateVolumeClaimTemplates(&compSpec, ins) - setComponentInfos(compSpec, resources, vcts, ins.GetReplicas(), clusterCompName, ins.Name, isSharding) + setComponentInfos(compSpec, resources, compSpec.VolumeClaimTemplates, ins.GetReplicas(), clusterCompName, ins.Name, isSharding) } setComponentInfos(compSpec, compSpec.Resources, compSpec.VolumeClaimTemplates, compSpec.Replicas-tplReplicas, clusterCompName, "", isSharding) @@ -391,27 +390,6 @@ func (o *ClusterObjects) GetComponentInfo() []*ComponentInfo { return comps } -// getCompTemplateVolumeClaimTemplates merges volume claim for instance template -func (o *ClusterObjects) getCompTemplateVolumeClaimTemplates(compSpec *kbappsv1.ClusterComponentSpec, - template kbappsv1.InstanceTemplate) []kbappsv1.ClusterComponentVolumeClaimTemplate { - var vcts []kbappsv1.ClusterComponentVolumeClaimTemplate - for i := range compSpec.VolumeClaimTemplates { - insVctIndex := -1 - for j := range template.VolumeClaimTemplates { - if template.VolumeClaimTemplates[j].Name == compSpec.VolumeClaimTemplates[i].Name { - insVctIndex = j - break - } - } - if insVctIndex != -1 { - vcts = append(vcts, template.VolumeClaimTemplates[insVctIndex]) - } else { - vcts = append(vcts, compSpec.VolumeClaimTemplates[i]) - } - } - return vcts -} - func (o *ClusterObjects) GetInstanceInfo() []*InstanceInfo { var instances []*InstanceInfo for _, pod := range o.Pods.Items { @@ -441,18 +419,7 @@ func (o *ClusterObjects) GetInstanceInfo() []*InstanceInfo { } } } - templateName := kbappsv1.GetInstanceTemplateName(o.Cluster.Name, componentName, pod.Name) - template := kbappsv1.InstanceTemplate{} - if templateName != "" { - for _, v := range componentSpec.Instances { - if v.Name == templateName { - template = v - break - } - } - } - vcts := o.getCompTemplateVolumeClaimTemplates(componentSpec, template) - instance.Storage = o.getStorageInfo(vcts, pod.Labels[constant.KBAppComponentLabelKey]) + instance.Storage = o.getStorageInfo(componentSpec.VolumeClaimTemplates, pod.Labels[constant.KBAppComponentLabelKey]) instance.ServiceVersion = componentSpec.ServiceVersion getInstanceNodeInfo(o.Nodes, &pod, instance) instance.CPU, instance.Memory = getResourceInfo(resource.PodRequestsAndLimits(&pod)) diff --git a/pkg/cmd/cluster/cluster.go b/pkg/cmd/cluster/cluster.go index 79da59c8b..6291c8fbd 100644 --- a/pkg/cmd/cluster/cluster.go +++ b/pkg/cmd/cluster/cluster.go @@ -78,7 +78,6 @@ func NewClusterCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra NewEditConfigureCmd(f, streams), NewDescribeReconfigureCmd(f, streams), NewExplainReconfigureCmd(f, streams), - NewDiffConfigureCmd(f, streams), }, }, { diff --git a/pkg/cmd/cluster/config_diff.go b/pkg/cmd/cluster/config_diff.go deleted file mode 100644 index ac252a56c..000000000 --- a/pkg/cmd/cluster/config_diff.go +++ /dev/null @@ -1,279 +0,0 @@ -/* -Copyright (C) 2022-2025 ApeCloud Co., Ltd - -This file is part of KubeBlocks project - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package cluster - -import ( - "fmt" - "reflect" - - "github.com/spf13/cast" - "github.com/spf13/cobra" - "k8s.io/cli-runtime/pkg/genericiooptions" - cmdutil "k8s.io/kubectl/pkg/cmd/util" - "k8s.io/kubectl/pkg/util/templates" - "sigs.k8s.io/controller-runtime/pkg/client" - - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" - "github.com/apecloud/kubeblocks/pkg/configuration/core" - "github.com/apecloud/kubeblocks/pkg/constant" - "github.com/apecloud/kubeblocks/pkg/unstructured" - - opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" - - "github.com/apecloud/kbcli/pkg/printer" - "github.com/apecloud/kbcli/pkg/types" - "github.com/apecloud/kbcli/pkg/util" -) - -type configDiffOptions struct { - baseOptions *describeOpsOptions - - clusterName string - componentName string - templateNames []string - baseVersion *opsv1alpha1.OpsRequest - diffVersion *opsv1alpha1.OpsRequest -} - -var ( - diffConfigureExample = templates.Examples(` - # compare config files - kbcli cluster diff-config opsrequest1 opsrequest2`) -) - -func (o *configDiffOptions) complete(args []string) error { - isValidReconfigureOps := func(ops *opsv1alpha1.OpsRequest) bool { - return ops.Spec.Type == opsv1alpha1.ReconfiguringType && ops.Spec.Reconfigures != nil - } - - if len(args) != 2 { - return core.MakeError("missing opsrequest name") - } - - if err := o.baseOptions.complete(args); err != nil { - return err - } - - baseVersion := &opsv1alpha1.OpsRequest{} - diffVersion := &opsv1alpha1.OpsRequest{} - if err := util.GetResourceObjectFromGVR(types.OpsGVR(), client.ObjectKey{ - Namespace: o.baseOptions.namespace, - Name: args[0], - }, o.baseOptions.dynamic, baseVersion); err != nil { - return core.WrapError(err, "failed to get ops CR [%s]", args[0]) - } - if err := util.GetResourceObjectFromGVR(types.OpsGVR(), client.ObjectKey{ - Namespace: o.baseOptions.namespace, - Name: args[1], - }, o.baseOptions.dynamic, diffVersion); err != nil { - return core.WrapError(err, "failed to get ops CR [%s]", args[1]) - } - - if !isValidReconfigureOps(baseVersion) { - return core.MakeError("opsrequest is not valid reconfiguring operation [%s]", client.ObjectKeyFromObject(baseVersion)) - } - - if !isValidReconfigureOps(diffVersion) { - return core.MakeError("opsrequest is not valid reconfiguring operation [%s]", client.ObjectKeyFromObject(diffVersion)) - } - - if !o.maybeCompareOps(baseVersion, diffVersion) { - return core.MakeError("failed to diff, not same cluster, or same component, or template.") - } - - o.baseVersion = baseVersion - o.diffVersion = diffVersion - return nil -} - -func findTemplateStatusByName(status *opsv1alpha1.ReconfiguringStatus, tplName string) *opsv1alpha1.ConfigurationItemStatus { - if status == nil { - return nil - } - - for i := range status.ConfigurationStatus { - s := &status.ConfigurationStatus[i] - if s.Name == tplName { - return s - } - } - return nil -} - -func (o *configDiffOptions) validate() error { - var ( - baseStatus = o.baseVersion.Status - diffStatus = o.diffVersion.Status - ) - - if baseStatus.Phase != opsv1alpha1.OpsSucceedPhase { - return core.MakeError("require reconfiguring phase is success!, name: %s, phase: %s", o.baseVersion.Name, baseStatus.Phase) - } - if diffStatus.Phase != opsv1alpha1.OpsSucceedPhase { - return core.MakeError("require reconfiguring phase is success!, name: %s, phase: %s", o.diffVersion.Name, diffStatus.Phase) - } - - for _, tplName := range o.templateNames { - s1 := findTemplateStatusByName(baseStatus.ReconfiguringStatusAsComponent[o.componentName], tplName) - s2 := findTemplateStatusByName(diffStatus.ReconfiguringStatusAsComponent[o.componentName], tplName) - if s1 == nil || len(s1.LastAppliedConfiguration) == 0 { - return core.MakeError("invalid reconfiguring status. CR[%v]", client.ObjectKeyFromObject(o.baseVersion)) - } - if s2 == nil || len(s2.LastAppliedConfiguration) == 0 { - return core.MakeError("invalid reconfiguring status. CR[%v]", client.ObjectKeyFromObject(o.diffVersion)) - } - } - return nil -} - -func (o *configDiffOptions) run() error { - configDiffs := make(map[string][]core.VisualizedParam, len(o.templateNames)) - baseConfigs := make(map[string]map[string]unstructured.ConfigObject) - for _, tplName := range o.templateNames { - diff, baseObj, err := o.diffConfig(tplName) - if err != nil { - return err - } - configDiffs[tplName] = diff - baseConfigs[tplName] = baseObj - } - - printer.PrintTitle("DIFF-CONFIG RESULT") - for tplName, diff := range configDiffs { - configObjects := baseConfigs[tplName] - for _, params := range diff { - printer.PrintLineWithTabSeparator( - printer.NewPair(" ConfigFile", printer.BoldYellow(params.Key)), - printer.NewPair("TemplateName", tplName), - printer.NewPair("ComponentName", o.componentName), - printer.NewPair("ClusterName", o.clusterName), - printer.NewPair("UpdateType", string(params.UpdateType)), - ) - fmt.Fprintf(o.baseOptions.Out, "\n") - tbl := printer.NewTablePrinter(o.baseOptions.Out) - tbl.SetHeader("ParameterName", o.baseVersion.Name, o.diffVersion.Name) - configObj := configObjects[params.Key] - for _, v := range params.Parameters { - baseValue := "null" - if configObj != nil { - baseValue = cast.ToString(configObj.Get(v.Key)) - } - tbl.AddRow(v.Key, baseValue, v.Value) - } - tbl.Print() - fmt.Fprintf(o.baseOptions.Out, "\n\n") - } - } - return nil -} - -func (o *configDiffOptions) maybeCompareOps(base *opsv1alpha1.OpsRequest, diff *opsv1alpha1.OpsRequest) bool { - getClusterName := func(ops client.Object) string { - labels := ops.GetLabels() - if len(labels) == 0 { - return "" - } - return labels[constant.AppInstanceLabelKey] - } - // TODO: compare all reconfigures - getComponentName := func(ops opsv1alpha1.OpsRequestSpec) string { - return ops.Reconfigures[0].ComponentName - } - getTemplateName := func(ops opsv1alpha1.OpsRequestSpec) []string { - configs := ops.Reconfigures[0].Configurations - names := make([]string, len(configs)) - for i, config := range configs { - names[i] = config.Name - } - return names - } - - clusterName := getClusterName(base) - if len(clusterName) == 0 || clusterName != getClusterName(diff) { - return false - } - componentName := getComponentName(base.Spec) - if len(componentName) == 0 || componentName != getComponentName(diff.Spec) { - return false - } - templateNames := getTemplateName(base.Spec) - if len(templateNames) == 0 || !reflect.DeepEqual(templateNames, getTemplateName(diff.Spec)) { - return false - } - - o.clusterName = clusterName - o.componentName = componentName - o.templateNames = templateNames - return true -} - -func (o *configDiffOptions) diffConfig(tplName string) ([]core.VisualizedParam, map[string]unstructured.ConfigObject, error) { - var ( - tpl *appsv1alpha1.ComponentConfigSpec - configConstraint = &appsv1beta1.ConfigConstraint{} - ) - - tplList, err := util.GetConfigSpecsFromComponentName(o.baseOptions.dynamic, o.baseOptions.namespace, o.clusterName, o.componentName, true) - if err != nil { - return nil, nil, err - } - if tpl = findTplByName(tplList, tplName); tpl == nil { - return nil, nil, core.MakeError("not found template: %s", tplName) - } - if err := util.GetResourceObjectFromGVR(types.ConfigConstraintGVR(), client.ObjectKey{ - Namespace: "", - Name: tpl.ConfigConstraintRef, - }, o.baseOptions.dynamic, configConstraint); err != nil { - return nil, nil, err - } - - formatCfg := configConstraint.Spec.FileFormatConfig - base := findTemplateStatusByName(o.baseVersion.Status.ReconfiguringStatusAsComponent[o.componentName], tplName) - diff := findTemplateStatusByName(o.diffVersion.Status.ReconfiguringStatusAsComponent[o.componentName], tplName) - patch, _, err := core.CreateConfigPatch(base.LastAppliedConfiguration, diff.LastAppliedConfiguration, formatCfg.Format, tpl.Keys, false) - if err != nil { - return nil, nil, err - } - - baseConfigObj, err := core.LoadRawConfigObject(base.LastAppliedConfiguration, formatCfg, tpl.Keys) - if err != nil { - return nil, nil, err - } - return core.GenerateVisualizedParamsList(patch, formatCfg, nil), baseConfigObj, nil -} - -// NewDiffConfigureCmd shows the difference between two configuration version. -func NewDiffConfigureCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { - o := &configDiffOptions{baseOptions: newDescribeOpsOptions(f, streams)} - cmd := &cobra.Command{ - Use: "diff-config", - Short: "Show the difference in parameters between the two submitted OpsRequest.", - Aliases: []string{"diff"}, - Example: diffConfigureExample, - ValidArgsFunction: util.ResourceNameCompletionFunc(f, types.OpsGVR()), - Run: func(cmd *cobra.Command, args []string) { - util.CheckErr(o.complete(args)) - util.CheckErr(o.validate()) - util.CheckErr(o.run()) - }, - } - return cmd -} diff --git a/pkg/cmd/cluster/config_edit.go b/pkg/cmd/cluster/config_edit.go index 8f2dd5d1d..067aba045 100644 --- a/pkg/cmd/cluster/config_edit.go +++ b/pkg/cmd/cluster/config_edit.go @@ -27,6 +27,11 @@ import ( "strings" opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" + cfgcm "github.com/apecloud/kubeblocks/pkg/configuration/config_manager" + "github.com/apecloud/kubeblocks/pkg/configuration/core" + "github.com/apecloud/kubeblocks/pkg/configuration/validate" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" "github.com/spf13/cobra" "golang.org/x/exp/slices" "k8s.io/apimachinery/pkg/util/sets" @@ -34,13 +39,6 @@ import ( cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/cmd/util/editor" "k8s.io/kubectl/pkg/util/templates" - "sigs.k8s.io/controller-runtime/pkg/client" - - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" - cfgcm "github.com/apecloud/kubeblocks/pkg/configuration/config_manager" - "github.com/apecloud/kubeblocks/pkg/configuration/core" - "github.com/apecloud/kubeblocks/pkg/configuration/validate" "github.com/apecloud/kbcli/pkg/printer" "github.com/apecloud/kbcli/pkg/types" @@ -92,9 +90,8 @@ func (o *editConfigOptions) Run(fn func() error) error { } util.DisplayDiffWithColor(o.IOStreams.Out, diff) - configSpec := wrapper.ConfigTemplateSpec() - if hasSchemaForFile(configSpec, wrapper.ConfigFile()) { - return o.runWithConfigConstraints(cfgEditContext, configSpec, fn) + if hasSchemaForFile(wrapper.rctx, wrapper.ConfigFile()) { + return o.runWithConfigConstraints(cfgEditContext, wrapper.rctx, fn) } yes, err := o.confirmReconfigure(fmt.Sprintf(fullRestartConfirmPrompt, printer.BoldRed(o.CfgFile))) @@ -110,17 +107,14 @@ func (o *editConfigOptions) Run(fn func() error) error { return fn() } -func hasSchemaForFile(configSpec *appsv1alpha1.ComponentConfigSpec, configFile string) bool { - if configSpec.ConfigConstraintRef == "" { +func hasSchemaForFile(rctx *ReconfigureContext, configFile string) bool { + if rctx.ConfigRender == nil { return false } - if len(configSpec.Keys) == 0 || configFile == "" { - return true - } - return slices.Contains(configSpec.Keys, configFile) + return intctrlutil.GetComponentConfigDescription(&rctx.ConfigRender.Spec, configFile) != nil } -func (o *editConfigOptions) runWithConfigConstraints(cfgEditContext *configEditContext, configSpec *appsv1alpha1.ComponentConfigSpec, fn func() error) error { +func (o *editConfigOptions) runWithConfigConstraints(cfgEditContext *configEditContext, rctx *ReconfigureContext, fn func() error) error { oldVersion := map[string]string{ o.CfgFile: cfgEditContext.getOriginal(), } @@ -128,19 +122,7 @@ func (o *editConfigOptions) runWithConfigConstraints(cfgEditContext *configEditC o.CfgFile: cfgEditContext.getEdited(), } - configConstraintKey := client.ObjectKey{ - Namespace: "", - Name: configSpec.ConfigConstraintRef, - } - configConstraint := appsv1beta1.ConfigConstraint{} - if err := util.GetResourceObjectFromGVR(types.ConfigConstraintGVR(), configConstraintKey, o.Dynamic, &configConstraint); err != nil { - return err - } - formatterConfig := configConstraint.Spec.FileFormatConfig - if formatterConfig == nil { - return core.MakeError("config spec[%s] not support reconfiguring!", configSpec.Name) - } - configPatch, fileUpdated, err := core.CreateConfigPatch(oldVersion, newVersion, formatterConfig.Format, configSpec.Keys, true) + configPatch, fileUpdated, err := core.CreateConfigPatch(oldVersion, newVersion, rctx.ConfigRender.Spec, true) if err != nil { return err } @@ -151,20 +133,31 @@ func (o *editConfigOptions) runWithConfigConstraints(cfgEditContext *configEditC fmt.Fprintf(o.Out, "Config patch(updated parameters): \n%s\n\n", string(configPatch.UpdateConfig[o.CfgFile])) if !o.enableDelete { - if err := core.ValidateConfigPatch(configPatch, configConstraint.Spec.FileFormatConfig); err != nil { + if err := core.ValidateConfigPatch(configPatch, rctx.ConfigRender.Spec); err != nil { return err } } - params := core.GenerateVisualizedParamsList(configPatch, configConstraint.Spec.FileFormatConfig, nil) + params := core.GenerateVisualizedParamsList(configPatch, rctx.ConfigRender.Spec.Configs) + o.KeyValues = fromKeyValuesToMap(params, o.CfgFile) // check immutable parameters - if len(configConstraint.Spec.ImmutableParameters) > 0 { - if err = util.ValidateParametersModified2(sets.KeySet(fromKeyValuesToMap(params, o.CfgFile)), configConstraint.Spec); err != nil { - return err + if err = util.ValidateParametersModified2(sets.KeySet(fromKeyValuesToMap(params, o.CfgFile)), rctx.ParametersDefs, o.CfgFile); err != nil { + return err + } + + var config *parametersv1alpha1.ComponentConfigDescription + if config = intctrlutil.GetComponentConfigDescription(&rctx.ConfigRender.Spec, o.CfgFile); config == nil { + return fn() + } + var pd *parametersv1alpha1.ParametersDefinition + for _, paramsDef := range rctx.ParametersDefs { + if paramsDef.Spec.FileName == o.CfgFile { + pd = paramsDef + break } } - confirmPrompt, err := generateReconfiguringPrompt(fileUpdated, configPatch, &configConstraint.Spec, o.CfgFile) + confirmPrompt, err := generateReconfiguringPrompt(fileUpdated, configPatch, pd, o.CfgFile, config.FileFormatConfig) if err != nil { return err } @@ -176,29 +169,26 @@ func (o *editConfigOptions) runWithConfigConstraints(cfgEditContext *configEditC return nil } - validatedData := map[string]string{ - o.CfgFile: cfgEditContext.getEdited(), - } - options := validate.WithKeySelector(configSpec.Keys) - if err = validate.NewConfigValidator(&configConstraint.Spec, options).Validate(validatedData); err != nil { - return core.WrapError(err, "failed to validate edited config") + if pd != nil && pd.Spec.ParametersSchema != nil { + if err = validate.NewConfigValidator(pd.Spec.ParametersSchema, config.FileFormatConfig).Validate(cfgEditContext.getEdited()); err != nil { + return core.WrapError(err, "failed to validate edited config") + } } - o.KeyValues = fromKeyValuesToMap(params, o.CfgFile) return fn() } -func generateReconfiguringPrompt(fileUpdated bool, configPatch *core.ConfigPatchInfo, cc *appsv1beta1.ConfigConstraintSpec, fileName string) (string, error) { - if fileUpdated { +func generateReconfiguringPrompt(fileUpdated bool, configPatch *core.ConfigPatchInfo, pd *parametersv1alpha1.ParametersDefinition, fileName string, config *parametersv1alpha1.FileFormatConfig) (string, error) { + if fileUpdated || pd == nil { return restartConfirmPrompt, nil } - dynamicUpdated, err := core.IsUpdateDynamicParameters(cc, configPatch) + dynamicUpdated, err := core.IsUpdateDynamicParameters(config, &pd.Spec, configPatch) if err != nil { return "", nil } confirmPrompt := confirmApplyReconfigurePrompt - if !dynamicUpdated || !cfgcm.IsSupportReload(cc.ReloadAction) { + if !dynamicUpdated || !cfgcm.IsSupportReload(pd.Spec.ReloadAction) { confirmPrompt = restartConfirmPrompt } return confirmPrompt, nil diff --git a/pkg/cmd/cluster/config_observer.go b/pkg/cmd/cluster/config_observer.go index 9995dd1a8..63919f592 100644 --- a/pkg/cmd/cluster/config_observer.go +++ b/pkg/cmd/cluster/config_observer.go @@ -20,28 +20,20 @@ along with this program. If not, see . package cluster import ( - "context" - "encoding/json" "fmt" - "sort" - "strings" - opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" "github.com/spf13/cobra" apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/cli-runtime/pkg/genericiooptions" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/util/templates" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" "github.com/apecloud/kubeblocks/pkg/configuration/openapi" cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" - "github.com/apecloud/kubeblocks/pkg/constant" - "github.com/apecloud/kbcli/pkg/action" "github.com/apecloud/kbcli/pkg/printer" "github.com/apecloud/kbcli/pkg/types" "github.com/apecloud/kbcli/pkg/util" @@ -60,8 +52,7 @@ type configObserverOptions struct { truncDocument bool paramName string - keys []string - showDetail bool + keys []string } var ( @@ -104,85 +95,79 @@ func (r *configObserverOptions) complete2(args []string) error { return r.complete(args) } -func (r *configObserverOptions) run(printFn func(objects *ConfigRelatedObjects, component string) error) error { - objects, err := New(r.clusterName, r.namespace, r.dynamic, r.componentNames...).GetObjects() +func (r *configObserverOptions) run(printFn func(*ReconfigureContext) error) error { + wrapper, err := New(r.clusterName, r.namespace, r.describeOpsOptions, r.componentNames...) if err != nil { return err } - - components := r.componentNames - if len(components) == 0 { - components = getComponentNames(objects.Cluster) - } - - for _, component := range components { - fmt.Fprintf(r.Out, "component: %s\n", component) - if _, ok := objects.ConfigSpecs[component]; !ok { - fmt.Fprintf(r.Out, "not found component: %s and pass\n\n", component) - } - if err := printFn(objects, component); err != nil { + for _, rctx := range wrapper.rctxMap { + fmt.Fprintf(r.Out, "component: %s\n", rctx.CompName) + if err := printFn(rctx); err != nil { return err } } return nil } -func (r *configObserverOptions) printComponentConfigSpecsDescribe(objects *ConfigRelatedObjects, component string) error { - configSpecs, ok := objects.ConfigSpecs[component] - if !ok { - return cfgcore.MakeError("not found component: %s", component) +func (r *configObserverOptions) printComponentConfigSpecsDescribe(rctx *ReconfigureContext) error { + resolveParameterTemplate := func(tpl string) string { + for _, config := range rctx.Cmpd.Spec.Configs { + if config.Name == tpl { + return config.TemplateRef + } + } + return "" } - configs, err := r.getReconfigureMeta(configSpecs) - if err != nil { - return err + + if rctx.ConfigRender == nil || len(rctx.ConfigRender.Spec.Configs) == 0 { + return nil } - if r.showDetail { - r.printConfigureContext(configs, component) + tbl := printer.NewTablePrinter(r.Out) + printer.PrintTitle("ConfigSpecs Meta") + tbl.SetHeader("CONFIG-SPEC-NAME", "FILE", "TEMPLATE-NAME", "COMPONENT", "CLUSTER") + for _, info := range rctx.ConfigRender.Spec.Configs { + tbl.AddRow( + printer.BoldYellow(info.TemplateName), + printer.BoldYellow(info.Name), + printer.BoldYellow(resolveParameterTemplate(info.TemplateName)), + rctx.CompName, + rctx.Cluster.Name) } - printer.PrintComponentConfigMeta(configs, r.clusterName, component, r.Out) - return r.printConfigureHistory(component) + tbl.Print() + return nil } -func (r *configObserverOptions) printComponentExplainConfigure(objects *ConfigRelatedObjects, component string) error { - configSpecs := r.configSpecs - if len(configSpecs) == 0 { - configSpecs = objects.ConfigSpecs[component].listConfigSpecs(true) - } - for _, templateName := range configSpecs { - fmt.Println("template meta:") - printer.PrintLineWithTabSeparator( - printer.NewPair(" ConfigSpec", templateName), - printer.NewPair("ComponentName", component), - printer.NewPair("ClusterName", r.clusterName), - ) - if err := r.printExplainConfigure(objects.ConfigSpecs[component], templateName); err != nil { +func (r *configObserverOptions) printComponentExplainConfigure(rctx *ReconfigureContext) error { + for _, pd := range rctx.ParametersDefs { + if rctx.ConfigRender != nil { + config := intctrlutil.GetComponentConfigDescription(&rctx.ConfigRender.Spec, pd.Spec.FileName) + if config != nil { + fmt.Println("template meta:") + printer.PrintLineWithTabSeparator( + printer.NewPair(" FileName", pd.Spec.FileName), + printer.NewPair(" ConfigSpec", config.TemplateName), + printer.NewPair("ComponentName", rctx.CompName), + printer.NewPair("ClusterName", r.clusterName), + ) + } + } + if err := r.printExplainConfigure(&pd.Spec); err != nil { return err } } return nil } -func (r *configObserverOptions) printExplainConfigure(configSpecs configSpecsType, tplName string) error { - tpl := configSpecs.findByName(tplName) - if tpl == nil { - return nil - } - - if tpl.ConfigConstraint == nil { - fmt.Printf("\n%s\n", fmt.Sprintf(noConfigConstraintPrompt, printer.BoldYellow(tplName))) +func (r *configObserverOptions) printExplainConfigure(pdSpec *parametersv1alpha1.ParametersDefinitionSpec) error { + if pdSpec.ParametersSchema == nil { + fmt.Printf("\n%s\n", fmt.Sprintf(notConfigSchemaPrompt, printer.BoldYellow(pdSpec.FileName))) return nil } - confSpec := tpl.ConfigConstraint.Spec - if confSpec.ParametersSchema == nil { - fmt.Printf("\n%s\n", fmt.Sprintf(notConfigSchemaPrompt, printer.BoldYellow(tplName))) - return nil - } - - schema := confSpec.ParametersSchema.DeepCopy() + schema := pdSpec.ParametersSchema.DeepCopy() if schema.SchemaInJSON == nil { if schema.CUE == "" { - fmt.Printf("\n%s\n", fmt.Sprintf(notConfigSchemaPrompt, printer.BoldYellow(tplName))) + fmt.Printf("\n%s\n", fmt.Sprintf(notConfigSchemaPrompt, printer.BoldYellow(pdSpec.FileName))) return nil } apiSchema, err := openapi.GenerateOpenAPISchema(schema.CUE, schema.TopLevelKey) @@ -196,96 +181,9 @@ func (r *configObserverOptions) printExplainConfigure(configSpecs configSpecsTyp schema.SchemaInJSON = apiSchema } return r.printConfigConstraint(schema.SchemaInJSON, - cfgutil.NewSet(confSpec.StaticParameters...), - cfgutil.NewSet(confSpec.DynamicParameters...), - cfgutil.NewSet(confSpec.ImmutableParameters...)) -} - -func (r *configObserverOptions) getReconfigureMeta(configSpecs configSpecsType) ([]types.ConfigTemplateInfo, error) { - configs := make([]types.ConfigTemplateInfo, 0) - configList := r.configSpecs - if len(configList) == 0 { - configList = configSpecs.listConfigSpecs(false) - } - for _, tplName := range configList { - tpl := configSpecs.findByName(tplName) - if tpl == nil || tpl.ConfigSpec == nil { - fmt.Fprintf(r.Out, "not found config spec: %s, and pass\n", tplName) - continue - } - if tpl.ConfigSpec == nil { - fmt.Fprintf(r.Out, "current configSpec[%s] not support reconfiguring and pass\n", tplName) - continue - } - configs = append(configs, types.ConfigTemplateInfo{ - Name: tplName, - TPL: *tpl.ConfigSpec, - CMObj: tpl.ConfigMap, - }) - } - return configs, nil -} - -func (r *configObserverOptions) printConfigureContext(configs []types.ConfigTemplateInfo, component string) { - printer.PrintTitle("Configures Context[${component-name}/${config-spec}/${file-name}]") - - keys := cfgutil.NewSet(r.keys...) - for _, info := range configs { - for key, context := range info.CMObj.Data { - if keys.Length() != 0 && !keys.InArray(key) { - continue - } - fmt.Fprintf(r.Out, "%s%s\n", - printer.BoldYellow(fmt.Sprintf("%s/%s/%s:\n", component, info.Name, key)), context) - } - } -} - -func (r *configObserverOptions) printConfigureHistory(component string) error { - printer.PrintTitle("History modifications") - - // filter reconfigure - // kubernetes not support fieldSelector with CRD: https://github.com/kubernetes/kubernetes/issues/51046 - listOptions := metav1.ListOptions{ - LabelSelector: strings.Join([]string{constant.AppInstanceLabelKey, r.clusterName}, "="), - } - - opsList, err := r.dynamic.Resource(types.OpsGVR()).Namespace(r.namespace).List(context.TODO(), listOptions) - if err != nil { - return err - } - // sort the unstructured objects with the creationTimestamp in positive order - sort.Sort(action.UnstructuredList(opsList.Items)) - tbl := printer.NewTablePrinter(r.Out) - tbl.SetHeader("OPS-NAME", "CLUSTER", "COMPONENT", "CONFIG-SPEC-NAME", "FILE", "STATUS", "POLICY", "PROGRESS", "CREATED-TIME", "VALID-UPDATED") - for _, obj := range opsList.Items { - ops := &opsv1alpha1.OpsRequest{} - if err = runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, ops); err != nil { - return err - } - if ops.Spec.Type != opsv1alpha1.ReconfiguringType { - continue - } - components := getComponentNameFromOps(ops) - if !strings.Contains(components, component) { - continue - } - phase := string(ops.Status.Phase) - tplNames := getTemplateNameFromOps(ops.Spec) - keyNames := getKeyNameFromOps(ops.Spec) - tbl.AddRow(ops.Name, - ops.Spec.GetClusterName(), - components, - tplNames, - keyNames, - phase, - getReconfigurePolicy(ops.Status, component), - ops.Status.Progress, - util.TimeFormat(&ops.CreationTimestamp), - getValidUpdatedParams(ops.Status, component)) - } - tbl.Print() - return nil + cfgutil.NewSet(pdSpec.StaticParameters...), + cfgutil.NewSet(pdSpec.DynamicParameters...), + cfgutil.NewSet(pdSpec.ImmutableParameters...)) } func (r *configObserverOptions) hasSpecificParam() bool { @@ -336,55 +234,6 @@ func (r *configObserverOptions) printConfigConstraint(schema *apiext.JSONSchemaP return nil } -func getReconfigurePolicy(status opsv1alpha1.OpsRequestStatus, component string) string { - reconfigureStatus := getReconfigureStatus(status, component) - if reconfigureStatus == nil || len(reconfigureStatus.ConfigurationStatus) == 0 { - return "" - } - - var policy string - reStatus := reconfigureStatus.ConfigurationStatus[0] - switch reStatus.UpdatePolicy { - case appsv1alpha1.SyncDynamicReloadPolicy: - policy = "syncDynamicReload" - case appsv1alpha1.AsyncDynamicReloadPolicy: - policy = "asyncDynamicReload" - case appsv1alpha1.DynamicReloadAndRestartPolicy: - policy = "dynamicReloadBeginRestart" - case appsv1alpha1.NormalPolicy, appsv1alpha1.RestartPolicy, appsv1alpha1.RollingPolicy: - policy = "restart" - default: - return "" - } - return printer.BoldYellow(policy) -} - -func getReconfigureStatus(status opsv1alpha1.OpsRequestStatus, component string) *opsv1alpha1.ReconfiguringStatus { - rStatus := status.ReconfiguringStatusAsComponent - var compRSStatus *opsv1alpha1.ReconfiguringStatus - if rStatus == nil && len(status.ReconfiguringStatusAsComponent) != 0 { - compRSStatus = status.ReconfiguringStatusAsComponent[component] - } - return compRSStatus -} - -func getValidUpdatedParams(status opsv1alpha1.OpsRequestStatus, component string) string { - reconfigureStatus := getReconfigureStatus(status, component) - if reconfigureStatus == nil || len(reconfigureStatus.ConfigurationStatus) == 0 { - return "" - } - - reStatus := reconfigureStatus.ConfigurationStatus[0] - if len(reStatus.UpdatedParameters.UpdatedKeys) == 0 { - return "" - } - b, err := json.Marshal(reStatus.UpdatedParameters.UpdatedKeys) - if err != nil { - return err.Error() - } - return string(b) -} - func isDynamicType(pt *parameterSchema, staticParameters, dynamicParameters, immutableParameters *cfgutil.Sets) bool { switch { case immutableParameters.InArray(pt.name): @@ -406,7 +255,6 @@ func isDynamicType(pt *parameterSchema, staticParameters, dynamicParameters, imm func NewDescribeReconfigureCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { o := &configObserverOptions{ isExplain: false, - showDetail: false, describeOpsOptions: newDescribeOpsOptions(f, streams), } cmd := &cobra.Command{ @@ -421,7 +269,6 @@ func NewDescribeReconfigureCmd(f cmdutil.Factory, streams genericiooptions.IOStr }, } o.addCommonFlags(cmd, f) - cmd.Flags().BoolVar(&o.showDetail, "show-detail", o.showDetail, "If true, the content of the files specified by config-file will be printed.") cmd.Flags().StringSliceVar(&o.keys, "config-file", nil, "Specify the name of the configuration file to be describe (e.g. for mysql: --config-file=my.cnf). If unset, all files.") return cmd } diff --git a/pkg/cmd/cluster/config_ops.go b/pkg/cmd/cluster/config_ops.go index f8ae11543..34656d01c 100644 --- a/pkg/cmd/cluster/config_ops.go +++ b/pkg/cmd/cluster/config_ops.go @@ -25,18 +25,16 @@ import ( "strings" opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" + cfgcm "github.com/apecloud/kubeblocks/pkg/configuration/config_manager" + "github.com/apecloud/kubeblocks/pkg/configuration/core" + configctrl "github.com/apecloud/kubeblocks/pkg/controller/configuration" + "github.com/apecloud/kubeblocks/pkg/controllerutil" + "github.com/apecloud/kubeblocks/pkg/generics" "github.com/spf13/cobra" - "k8s.io/apimachinery/pkg/util/sets" "k8s.io/cli-runtime/pkg/genericiooptions" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/util/templates" - "sigs.k8s.io/controller-runtime/pkg/client" - - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" - cfgcm "github.com/apecloud/kubeblocks/pkg/configuration/config_manager" - "github.com/apecloud/kubeblocks/pkg/configuration/core" - "github.com/apecloud/kubeblocks/pkg/controllerutil" "github.com/apecloud/kbcli/pkg/printer" "github.com/apecloud/kbcli/pkg/types" @@ -48,7 +46,7 @@ type configOpsOptions struct { *OperationsOptions editMode bool - wrapper *configWrapper + wrapper *ReconfigureWrapper // config file replace replaceFile bool @@ -91,7 +89,8 @@ func (o *configOpsOptions) Complete() error { } o.wrapper = wrapper - return wrapper.AutoFillRequiredParam() + // return wrapper.AutoFillRequiredParam() + return nil } func (o *configOpsOptions) validateReconfigureOptions() error { @@ -116,10 +115,6 @@ func (o *configOpsOptions) validateReconfigureOptions() error { // Validate command flags or args is legal func (o *configOpsOptions) Validate() error { - if err := o.wrapper.ValidateRequiredParam(o.replaceFile); err != nil { - return err - } - o.CfgFile = o.wrapper.ConfigFile() o.CfgTemplateName = o.wrapper.ConfigSpecName() if len(o.ComponentNames) == 0 { @@ -129,71 +124,77 @@ func (o *configOpsOptions) Validate() error { if o.editMode { return nil } - if err := o.validateConfigParams(o.wrapper.ConfigTemplateSpec()); err != nil { + + rctx := o.wrapper.rctx + tplObjs, err := resolveConfigTemplate(rctx, o.Dynamic) + if err != nil { return err } - if err := util.ValidateParametersModified(o.wrapper.ConfigTemplateSpec(), sets.KeySet(o.KeyValues), o.Dynamic); err != nil { + + classifyParams := configctrl.ClassifyComponentParameters(o.KeyValues, rctx.ParametersDefs, rctx.Cmpd.Spec.Configs, tplObjs) + if err := util.ValidateParametersModified(classifyParams, rctx.ParametersDefs); err != nil { return err } - o.printConfigureTips() - return nil -} -func (o *configOpsOptions) validateConfigParams(tpl *appsv1alpha1.ComponentConfigSpec) error { - configConstraintKey := client.ObjectKey{ - Namespace: "", - Name: tpl.ConfigConstraintRef, - } - configConstraint := appsv1beta1.ConfigConstraint{} - if err := util.GetResourceObjectFromGVR(types.ConfigConstraintGVR(), configConstraintKey, o.Dynamic, &configConstraint); err != nil { + if err := o.validateConfigParams(o.wrapper.rctx, classifyParams); err != nil { return err } - var err error - var newConfigData map[string]string + o.printConfigureTips(classifyParams) + return nil +} + +func (o *configOpsOptions) validateConfigParams(rctx *ReconfigureContext, classifyParameters map[string]map[string]*parametersv1alpha1.ParametersInFile) error { if o.FileContent != "" { - newConfigData = map[string]string{o.CfgFile: o.FileContent} - } else { - newConfigData, err = controllerutil.MergeAndValidateConfigs(configConstraint.Spec, map[string]string{o.CfgFile: ""}, tpl.Keys, []core.ParamPairs{{ - Key: o.CfgFile, - UpdatedParams: core.FromStringMap(o.KeyValues), - }}) - } - if err != nil { - return err + return o.confirmReconfigureWithRestart() } - return o.checkChangedParamsAndDoubleConfirm(&configConstraint.Spec, newConfigData, tpl) -} -func (o *configOpsOptions) checkChangedParamsAndDoubleConfirm(cc *appsv1beta1.ConfigConstraintSpec, data map[string]string, tpl *appsv1alpha1.ComponentConfigSpec) error { - mockEmptyData := func(m map[string]string) map[string]string { - r := make(map[string]string, len(data)) - for key := range m { - r[key] = "" + checkRestart := func(params map[string]*parametersv1alpha1.ParametersInFile) bool { + for file := range params { + match := func(pd *parametersv1alpha1.ParametersDefinition) bool { + return pd.Spec.FileName == file && cfgcm.IsSupportReload(pd.Spec.ReloadAction) + } + if generics.FindFirstFunc(rctx.ParametersDefs, match) < 0 { + return true + } } - return r + return false } - if !cfgcm.IsSupportReload(cc.ReloadAction) { - return o.confirmReconfigureWithRestart() + transform := func(params map[string]*parametersv1alpha1.ParametersInFile) []core.ParamPairs { + var result []core.ParamPairs + for file, ps := range params { + result = append(result, core.ParamPairs{ + Key: file, + UpdatedParams: core.FromStringMap(ps.Parameters), + }) + } + return result } - configPatch, restart, err := core.CreateConfigPatch(mockEmptyData(data), data, cc.FileFormatConfig.Format, tpl.Keys, o.FileContent != "") - if err != nil { - return err + restart := false + for _, parameters := range classifyParameters { + _, err := controllerutil.MergeAndValidateConfigs(mockEmptyData(parameters), transform(parameters), rctx.ParametersDefs, rctx.ConfigRender.Spec.Configs) + if err != nil { + return err + } + if !restart { + restart = checkRestart(parameters) + } } + if restart { return o.confirmReconfigureWithRestart() } + return nil +} - dynamicUpdated, err := core.IsUpdateDynamicParameters(cc, configPatch) - if err != nil { - return nil - } - if dynamicUpdated { - return nil +func mockEmptyData(m map[string]*parametersv1alpha1.ParametersInFile) map[string]string { + r := make(map[string]string, len(m)) + for key := range m { + r[key] = "" } - return o.confirmReconfigureWithRestart() + return r } func (o *configOpsOptions) confirmReconfigureWithRestart() error { @@ -231,13 +232,17 @@ func (o *configOpsOptions) parseUpdatedParams() (map[string]string, error) { return keyValues, nil } -func (o *configOpsOptions) printConfigureTips() { +func (o *configOpsOptions) printConfigureTips(classifyParameters map[string]map[string]*parametersv1alpha1.ParametersInFile) { fmt.Println("Will updated configure file meta:") - printer.PrintLineWithTabSeparator( - printer.NewPair(" ConfigSpec", printer.BoldYellow(o.CfgTemplateName)), - printer.NewPair(" ConfigFile", printer.BoldYellow(o.CfgFile)), - printer.NewPair("ComponentName", o.ComponentName), - printer.NewPair("ClusterName", o.Name)) + for tpl, tplParams := range classifyParameters { + for file := range tplParams { + printer.PrintLineWithTabSeparator( + printer.NewPair(" ConfigSpec", printer.BoldYellow(tpl)), + printer.NewPair(" ConfigFile", printer.BoldYellow(file)), + printer.NewPair("ComponentName", o.ComponentName), + printer.NewPair("ClusterName", o.Name)) + } + } } // buildReconfigureCommonFlags build common flags for reconfigure command diff --git a/pkg/cmd/cluster/config_ops_test.go b/pkg/cmd/cluster/config_ops_test.go index 8964fb10a..ff70119cb 100644 --- a/pkg/cmd/cluster/config_ops_test.go +++ b/pkg/cmd/cluster/config_ops_test.go @@ -35,7 +35,6 @@ import ( kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" - "github.com/apecloud/kubeblocks/pkg/controller/builder" testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" "github.com/apecloud/kbcli/pkg/testing" @@ -86,24 +85,24 @@ var _ = Describe("reconfigure test", func() { &appsv1beta1.ConfigConstraint{}) componentConfig := testapps.NewConfigMap(ns, cfgcore.GetComponentCfgName(clusterName, statefulCompName, configSpecName), testapps.SetConfigMapData("my.cnf", "")) By("Create a configuration obj") - configObj := builder.NewConfigurationBuilder(ns, cfgcore.GenerateComponentConfigurationName(clusterName, statefulCompName)). - ClusterRef(clusterName). - Component(statefulCompName). - AddConfigurationItem(kbappsv1.ComponentConfigSpec{ - ComponentTemplateSpec: kbappsv1.ComponentTemplateSpec{ - Name: configSpecName, - TemplateRef: configmap.Name, - Namespace: ns, - VolumeName: configVolumeName, - }, - ConfigConstraintRef: constraint.Name, - }). - GetObject() + // configObj := builder.NewConfigurationBuilder(ns, cfgcore.GenerateComponentConfigurationName(clusterName, statefulCompName)). + // ClusterRef(clusterName). + // Component(statefulCompName). + // AddConfigurationItem(kbappsv1.ComponentConfigSpec{ + // ComponentTemplateSpec: kbappsv1.ComponentTemplateSpec{ + // Name: configSpecName, + // TemplateRef: configmap.Name, + // Namespace: ns, + // VolumeName: configVolumeName, + // }, + // ConfigConstraintRef: constraint.Name, + // }). + // GetObject() By("creating a cluster") clusterObj := testapps.NewClusterFactory(ns, clusterName, ""). AddComponent(statefulCompName, statefulCompDefName).GetObject() - objs := []runtime.Object{configmap, constraint, clusterObj, componentConfig, configObj} + objs := []runtime.Object{configmap, constraint, clusterObj, componentConfig} ttf, ops := NewFakeOperationsOptions(ns, clusterObj.Name, objs...) o := &configOpsOptions{ // nil cannot be set to a map struct in CueLang, so init the map of KeyValues. diff --git a/pkg/cmd/cluster/config_resource.go b/pkg/cmd/cluster/config_resource.go index 49f97ad27..660bed0b0 100644 --- a/pkg/cmd/cluster/config_resource.go +++ b/pkg/cmd/cluster/config_resource.go @@ -20,205 +20,55 @@ along with this program. If not, see . package cluster import ( - "golang.org/x/exp/slices" - corev1 "k8s.io/api/core/v1" - "k8s.io/client-go/dynamic" - "sigs.k8s.io/controller-runtime/pkg/client" + "context" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" - "github.com/apecloud/kubeblocks/pkg/configuration/core" - - "github.com/apecloud/kbcli/pkg/types" - "github.com/apecloud/kbcli/pkg/util" + appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" + "github.com/apecloud/kubeblocks/pkg/client/clientset/versioned" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -type configSpecsType []*configSpecMeta - -type configSpecMeta struct { - Spec appsv1alpha1.ComponentTemplateSpec - ConfigMap *corev1.ConfigMap - - ConfigSpec *appsv1alpha1.ComponentConfigSpec - ConfigConstraint *appsv1beta1.ConfigConstraint -} - -type ConfigRelatedObjects struct { - Cluster *appsv1alpha1.Cluster - ConfigSpecs map[string]configSpecsType - Configurations []*appsv1alpha1.Configuration -} - -type configObjectsWrapper struct { +type ConfigObjectsWrapper struct { namespace string clusterName string components []string - err error - cli dynamic.Interface -} - -func (c configSpecsType) findByName(name string) *configSpecMeta { - for _, spec := range c { - if spec.Spec.Name == name { - return spec - } - } - return nil -} - -func (c configSpecsType) listConfigSpecs(ccFilter bool) []string { - var names []string - for _, spec := range c { - if spec.ConfigSpec != nil && (!ccFilter || spec.ConfigConstraint != nil) { - names = append(names, spec.Spec.Name) - } - } - return names -} - -func New(clusterName string, namespace string, cli dynamic.Interface, component ...string) *configObjectsWrapper { - return &configObjectsWrapper{namespace, clusterName, component, nil, cli} + rctxMap map[string]*ReconfigureContext } -func (w *configObjectsWrapper) GetObjects() (*ConfigRelatedObjects, error) { - objects := &ConfigRelatedObjects{} - err := w.cluster(objects). - // clusterDefinition(objects). - // clusterVersion(objects). - compConfigurations(objects). - // comps(objects). - configSpecsObjects(objects). - finish() +func GetCluster(clientSet *versioned.Clientset, ns, clusterName string) (*appsv1.Cluster, error) { + clusterObj, err := clientSet.AppsV1().Clusters(ns).Get(context.TODO(), clusterName, metav1.GetOptions{}) if err != nil { return nil, err } - return objects, nil -} - -func (w *configObjectsWrapper) configMap(specName string, component string, out *configSpecMeta) *configObjectsWrapper { - fn := func() error { - key := client.ObjectKey{ - Namespace: w.namespace, - Name: core.GetComponentCfgName(w.clusterName, component, specName), - } - out.ConfigMap = &corev1.ConfigMap{} - return util.GetResourceObjectFromGVR(types.ConfigmapGVR(), key, w.cli, out.ConfigMap) - } - return w.objectWrapper(fn) -} - -func (w *configObjectsWrapper) configConstraint(specName string, out *configSpecMeta) *configObjectsWrapper { - fn := func() error { - if specName == "" { - return nil - } - key := client.ObjectKey{ - Namespace: "", - Name: specName, - } - out.ConfigConstraint = &appsv1beta1.ConfigConstraint{} - return util.GetResourceObjectFromGVR(types.ConfigConstraintGVR(), key, w.cli, out.ConfigConstraint) - } - return w.objectWrapper(fn) + return clusterObj, nil } -func (w *configObjectsWrapper) cluster(objects *ConfigRelatedObjects) *configObjectsWrapper { - fn := func() error { - clusterKey := client.ObjectKey{ - Namespace: w.namespace, - Name: w.clusterName, - } - objects.Cluster = &appsv1alpha1.Cluster{} - if err := util.GetResourceObjectFromGVR(types.ClusterGVR(), clusterKey, w.cli, objects.Cluster); err != nil { - return makeClusterNotExistErr(w.clusterName) - } - return nil - } - return w.objectWrapper(fn) -} - -func (w *configObjectsWrapper) compConfigurations(object *ConfigRelatedObjects) *configObjectsWrapper { - fn := func() error { - for _, comp := range object.Cluster.Spec.ComponentSpecs { - configKey := client.ObjectKey{ - Namespace: w.namespace, - Name: core.GenerateComponentConfigurationName(w.clusterName, comp.Name), - } - config := appsv1alpha1.Configuration{} - if err := util.GetResourceObjectFromGVR(types.ConfigurationGVR(), configKey, w.cli, &config); err != nil { - return err - } - object.Configurations = append(object.Configurations, &config) - } - return nil +func New(clusterName string, namespace string, options *describeOpsOptions, components ...string) (*ConfigObjectsWrapper, error) { + clientSet, err := GetClientFromOptions(options.factory) + if err != nil { + return nil, err } - return w.objectWrapper(fn) -} - -func (w *configObjectsWrapper) configSpecsObjects(objects *ConfigRelatedObjects) *configObjectsWrapper { - fn := func() error { - configSpecs := make(map[string]configSpecsType, len(objects.Configurations)) - for _, component := range objects.Configurations { - componentName := component.Spec.ComponentName - if len(w.components) != 0 && !slices.Contains(w.components, componentName) { - continue - } - if _, ok := configSpecs[componentName]; !ok { - configSpecs[componentName] = make(configSpecsType, 0) - } - componentConfigSpecs, err := w.genConfigSpecsByConfiguration(&component.Spec) - if err != nil { - return err - } - configSpecs[componentName] = append(configSpecs[componentName], componentConfigSpecs...) - } - objects.ConfigSpecs = configSpecs - return nil + clusterObj, err := GetCluster(clientSet, namespace, clusterName) + if err != nil { + return nil, err } - return w.objectWrapper(fn) -} - -func (w *configObjectsWrapper) finish() error { - return w.err -} - -func (w *configObjectsWrapper) genConfigSpecsByConfiguration(config *appsv1alpha1.ConfigurationSpec) (rets []*configSpecMeta, err error) { - if len(config.ConfigItemDetails) == 0 { - return + if len(components) == 0 { + components = getComponentNames(clusterObj) } - for _, item := range config.ConfigItemDetails { - if item.ConfigSpec == nil { - continue - } - specMeta, err := w.transformConfigSpecMeta(*item.ConfigSpec, config.ComponentName) + rctxAsMap := make(map[string]*ReconfigureContext) + for _, compName := range components { + rctx, err := generateReconfigureContext(context.TODO(), clientSet, clusterName, compName, namespace) if err != nil { return nil, err } - rets = append(rets, specMeta) + rctxAsMap[rctx.CompName] = rctx } - return -} -func (w *configObjectsWrapper) transformConfigSpecMeta(spec appsv1alpha1.ComponentConfigSpec, component string) (*configSpecMeta, error) { - specMeta := &configSpecMeta{ - Spec: spec.ComponentTemplateSpec, - ConfigSpec: spec.DeepCopy(), - } - err := w.configMap(spec.Name, component, specMeta). - configConstraint(spec.ConfigConstraintRef, specMeta). - finish() - if err != nil { - return nil, err - } - return specMeta, nil -} - -func (w *configObjectsWrapper) objectWrapper(fn func() error) *configObjectsWrapper { - if w.err != nil { - return w - } - w.err = fn() - return w + return &ConfigObjectsWrapper{ + namespace: namespace, + components: components, + clusterName: clusterName, + rctxMap: rctxAsMap, + }, nil } diff --git a/pkg/cmd/cluster/config_util.go b/pkg/cmd/cluster/config_util.go index 1b8f7880e..2b0e075dc 100644 --- a/pkg/cmd/cluster/config_util.go +++ b/pkg/cmd/cluster/config_util.go @@ -28,9 +28,13 @@ import ( "sort" "strings" + appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" + "github.com/apecloud/kubeblocks/pkg/generics" "github.com/spf13/cast" corev1 "k8s.io/api/core/v1" apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/client-go/dynamic" "k8s.io/kubectl/pkg/cmd/util/editor" "sigs.k8s.io/controller-runtime/pkg/client" @@ -257,11 +261,14 @@ func generateParameterSchema(paramName string, property apiext.JSONSchemaProps) return pt, nil } -func getComponentNames(cluster *appsv1alpha1.Cluster) []string { +func getComponentNames(cluster *appsv1.Cluster) []string { var components []string for _, component := range cluster.Spec.ComponentSpecs { components = append(components, component.Name) } + for _, component := range cluster.Spec.Shardings { + components = append(components, component.Name) + } return components } @@ -274,3 +281,30 @@ func findTplByName(tpls []appsv1alpha1.ComponentConfigSpec, tplName string) *app } return nil } + +func resolveConfigTemplate(rctx *ReconfigureContext, dynamic dynamic.Interface) (map[string]*corev1.ConfigMap, error) { + tpls := generics.Map(rctx.ConfigRender.Spec.Configs, func(parameter parametersv1alpha1.ComponentConfigDescription) string { + return parameter.TemplateName + }) + tplObjs := make(map[string]*corev1.ConfigMap, len(tpls)) + for _, tpl := range tpls { + if _, ok := tplObjs[tpl]; ok { + continue + } + index := generics.FindFirstFunc(rctx.Cmpd.Spec.Configs, func(spec appsv1.ComponentTemplateSpec) bool { + return spec.Name == tpl + }) + if index < 0 { + return nil, makeConfigSpecNotExistErr(rctx.Cluster.Name, rctx.CompName, tpl) + } + var cm = &corev1.ConfigMap{} + tplMeta := rctx.Cmpd.Spec.Configs[index] + key := client.ObjectKey{Namespace: tplMeta.Namespace, Name: tplMeta.TemplateRef} + if err := util.GetResourceObjectFromGVR(types.ConfigmapGVR(), key, dynamic, cm); err != nil { + return nil, err + } + tplObjs[tpl] = cm + } + + return tplObjs, nil +} diff --git a/pkg/cmd/cluster/config_wrapper.go b/pkg/cmd/cluster/config_wrapper.go index c53386fe1..bd72194cd 100644 --- a/pkg/cmd/cluster/config_wrapper.go +++ b/pkg/cmd/cluster/config_wrapper.go @@ -20,208 +20,321 @@ along with this program. If not, see . package cluster import ( - kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - corev1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/apecloud/kubeblocks/pkg/configuration/core" + "context" + "errors" + "fmt" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" + kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" + "github.com/apecloud/kubeblocks/pkg/client/clientset/versioned" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" + "github.com/apecloud/kubeblocks/pkg/generics" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + cmdutil "k8s.io/kubectl/pkg/cmd/util" "github.com/apecloud/kbcli/pkg/action" - "github.com/apecloud/kbcli/pkg/cluster" - "github.com/apecloud/kbcli/pkg/types" - "github.com/apecloud/kbcli/pkg/util" ) -type configWrapper struct { +type ReconfigureContext struct { + Client *versioned.Clientset + Context context.Context + + Cluster *kbappsv1.Cluster + Cmpd *kbappsv1.ComponentDefinition + ConfigRender *parametersv1alpha1.ParamConfigRenderer + ParametersDefs []*parametersv1alpha1.ParametersDefinition + + Sharding bool + CompName string +} + +type ReconfigureWrapper struct { action.CreateOptions - *kbappsv1.Cluster - clusterName string + rctx *ReconfigureContext + updatedParams map[string]*string // autofill field - componentName string configSpecName string configFileKey string - configTemplateSpec appsv1alpha1.ComponentConfigSpec + configTemplateSpec kbappsv1.ComponentTemplateSpec } -func (w *configWrapper) ConfigTemplateSpec() *appsv1alpha1.ComponentConfigSpec { - return &w.configTemplateSpec -} - -func (w *configWrapper) ConfigSpecName() string { - return w.configSpecName -} - -func (w *configWrapper) ComponentName() string { - return w.componentName +func (w *ReconfigureWrapper) ConfigSpecName() string { + if w.configFileKey != "" { + return w.configFileKey + } + file := w.ConfigFile() + if file != "" && w.rctx.ConfigRender != nil { + config := intctrlutil.GetComponentConfigDescription(&w.rctx.ConfigRender.Spec, file) + if config != nil { + return config.TemplateName + } + } + return "" } -func (w *configWrapper) ConfigFile() string { - return w.configFileKey +func (w *ReconfigureWrapper) ComponentName() string { + return w.rctx.CompName } -// AutoFillRequiredParam auto fills required param. -func (w *configWrapper) AutoFillRequiredParam() error { - if err := w.fillComponent(); err != nil { - return err +func (w *ReconfigureWrapper) ConfigFile() string { + if w.configFileKey != "" { + return w.configFileKey } - if err := w.fillConfigSpec(); err != nil { - return err + if w.rctx.ConfigRender != nil && len(w.rctx.ConfigRender.Spec.Configs) > 0 { + return w.rctx.ConfigRender.Spec.Configs[0].Name } - return w.fillConfigFile() + return "" } +// AutoFillRequiredParam auto fills required param. +// func (w *ReconfigureWrapper) AutoFillRequiredParam() error { +// if err := w.fillConfigSpec(); err != nil { +// return err +// } +// return w.fillConfigFile() +// } + // ValidateRequiredParam validates required param. -func (w *configWrapper) ValidateRequiredParam(forceReplace bool) error { - // step1: check existence of component. - if w.Spec.GetComponentByName(w.componentName) == nil { - return makeComponentNotExistErr(w.clusterName, w.componentName) +// func (w *ReconfigureWrapper) ValidateRequiredParam(forceReplace bool) error { +// // step1: check existence of component. +// if w.Spec.GetComponentByName(w.componentName) == nil { +// return makeComponentNotExistErr(w.clusterName, w.componentName) +// } +// +// // step2: check existence of configmap +// cmObj := corev1.ConfigMap{} +// cmKey := client.ObjectKey{ +// Name: core.GetComponentCfgName(w.clusterName, w.componentName, w.configSpecName), +// Namespace: w.Namespace, +// } +// if err := util.GetResourceObjectFromGVR(types.ConfigmapGVR(), cmKey, w.Dynamic, &cmObj); err != nil { +// return err +// } +// +// // step3: check existence of config file +// if _, ok := cmObj.Data[w.configFileKey]; !ok { +// return makeNotFoundConfigFileErr(w.configFileKey, w.configSpecName, cfgutil.ToSet(cmObj.Data).AsSlice()) +// } +// +// if !forceReplace && !util.IsSupportConfigFileReconfigure(w.configTemplateSpec, w.configFileKey) { +// return makeNotSupportConfigFileUpdateErr(w.configFileKey, w.configTemplateSpec) +// } +// return nil +// } + +func (w *ReconfigureWrapper) fillConfigSpec() error { + var rctx = w.rctx + + if rctx.ConfigRender == nil || len(rctx.ConfigRender.Spec.Configs) == 0 { + return makeNotFoundTemplateErr(w.Name, rctx.CompName) } - // step2: check existence of configmap - cmObj := corev1.ConfigMap{} - cmKey := client.ObjectKey{ - Name: core.GetComponentCfgName(w.clusterName, w.componentName, w.configSpecName), - Namespace: w.Namespace, - } - if err := util.GetResourceObjectFromGVR(types.ConfigmapGVR(), cmKey, w.Dynamic, &cmObj); err != nil { - return err + var configs []parametersv1alpha1.ComponentConfigDescription + if w.configSpecName != "" { + configs = intctrlutil.GetComponentConfigDescriptions(&rctx.ConfigRender.Spec, w.configSpecName) + if len(configs) == 0 { + return makeConfigSpecNotExistErr(w.Name, rctx.CompName, w.configSpecName) + } } + return nil +} - // step3: check existence of config file - if _, ok := cmObj.Data[w.configFileKey]; !ok { - return makeNotFoundConfigFileErr(w.configFileKey, w.configSpecName, cfgutil.ToSet(cmObj.Data).AsSlice()) +// func (w *ReconfigureWrapper) fillConfigFile() error { +// if w.configFileKey != "" { +// return nil +// } +// +// if w.configTemplateSpec.TemplateRef == "" { +// return makeNotFoundTemplateErr(w.clusterName, w.componentName) +// } +// +// cmObj := corev1.ConfigMap{} +// cmKey := client.ObjectKey{ +// Name: core.GetComponentCfgName(w.clusterName, w.componentName, w.configSpecName), +// Namespace: w.Namespace, +// } +// if err := util.GetResourceObjectFromGVR(types.ConfigmapGVR(), cmKey, w.Dynamic, &cmObj); err != nil { +// return err +// } +// if len(cmObj.Data) == 0 { +// return core.MakeError("not supported reconfiguring because there is no config file.") +// } +// +// keys := w.filterForReconfiguring(cmObj.Data) +// if len(keys) == 1 { +// w.configFileKey = keys[0] +// return nil +// } +// return core.MakeError(multiConfigFileErrorMessage) +// } + +// func (w *ReconfigureWrapper) filterForReconfiguring(data map[string]string) []string { +// keys := make([]string, 0, len(data)) +// for configFileKey := range data { +// if util.IsSupportConfigFileReconfigure(w.configTemplateSpec, configFileKey) { +// keys = append(keys, configFileKey) +// } +// } +// return keys +// } + +func GetClientFromOptions(factory cmdutil.Factory) (*versioned.Clientset, error) { + config, err := factory.ToRESTConfig() + if err != nil { + return nil, err } - if !forceReplace && !util.IsSupportConfigFileReconfigure(w.configTemplateSpec, w.configFileKey) { - return makeNotSupportConfigFileUpdateErr(w.configFileKey, w.configTemplateSpec) - } - return nil + return versioned.NewForConfigOrDie(config), err } -func (w *configWrapper) fillComponent() error { - if w.componentName != "" { - return nil +func newConfigWrapper(baseOptions action.CreateOptions, componentName, templateName, fileName string, params map[string]*string) (*ReconfigureWrapper, error) { + cli, err := GetClientFromOptions(baseOptions.Factory) + if err != nil { + return nil, err } - componentNames, err := util.GetComponentsFromResource(w.Namespace, w.clusterName, w.Spec.ComponentSpecs, w.Dynamic) + + rctx, err := generateReconfigureContext(context.TODO(), cli, baseOptions.Name, componentName, baseOptions.Namespace) if err != nil { - return err + return nil, err } - if len(componentNames) != 1 { - return core.MakeError(multiComponentsErrorMessage) + if len(rctx.ParametersDefs) == 0 && rctx.ConfigRender == nil { + return nil, fmt.Errorf("the referenced component[%s] has no ParametersDefinitions or ParamConfigRenderer, and disable reconfigure", componentName) } - w.componentName = componentNames[0] - return nil + + return &ReconfigureWrapper{ + CreateOptions: baseOptions, + rctx: rctx, + configSpecName: templateName, + configFileKey: fileName, + updatedParams: params, + }, nil } -func (w *configWrapper) fillConfigSpec() error { - foundConfigSpec := func(configSpecs []appsv1alpha1.ComponentConfigSpec, name string) *appsv1alpha1.ComponentConfigSpec { - for _, configSpec := range configSpecs { - if configSpec.Name == name { - w.configTemplateSpec = configSpec - return &configSpec - } +func generateReconfigureContext(ctx context.Context, clientSet *versioned.Clientset, clusterName, componentName, ns string) (*ReconfigureContext, error) { + defaultCompName := func(clusterSpec kbappsv1.ClusterSpec) string { + switch { + case len(clusterSpec.ComponentSpecs) != 0: + return clusterSpec.ComponentSpecs[0].Name + case len(clusterSpec.Shardings) == 0: + return clusterSpec.Shardings[0].Name + default: + panic("cluster not have any component or sharding") } - return nil } - configSpecs, err := util.GetConfigSpecsFromComponentName(w.Dynamic, w.GetNamespace(), w.clusterName, w.componentName, w.configSpecName == "") + clusterObj, err := clientSet.AppsV1().Clusters(ns).Get(ctx, clusterName, metav1.GetOptions{}) if err != nil { - return err + return nil, err } - if len(configSpecs) == 0 { - return makeNotFoundTemplateErr(w.clusterName, w.componentName) + if componentName == "" { + componentName = defaultCompName(clusterObj.Spec) } - if w.configSpecName != "" { - if foundConfigSpec(configSpecs, w.configSpecName) == nil { - return makeConfigSpecNotExistErr(w.clusterName, w.componentName, w.configSpecName) - } - return nil + sharding, cmpd, err := resolveComponentDefObj(ctx, clientSet, clusterObj, componentName) + if err != nil { + return nil, err } - - w.configTemplateSpec = configSpecs[0] - if len(configSpecs) == 1 { - w.configSpecName = configSpecs[0].Name - return nil + rctx := &ReconfigureContext{ + Context: ctx, + Sharding: sharding, + Cmpd: cmpd, + Cluster: clusterObj, + Client: clientSet, + CompName: componentName, } - if len(w.updatedParams) == 0 { - return core.MakeError(multiConfigTemplateErrorMessage) + if err = resolveCmpdParametersDefs(rctx); err != nil { + return nil, err } - supportUpdatedTpl := make([]appsv1alpha1.ComponentConfigSpec, 0) - for _, configSpec := range configSpecs { - if ok, err := util.IsSupportReconfigureParams(configSpec, w.updatedParams, w.Dynamic); err == nil && ok { - supportUpdatedTpl = append(supportUpdatedTpl, configSpec) + return rctx, nil +} + +func resolveComponentDefObj(ctx context.Context, client *versioned.Clientset, clusterObj *kbappsv1.Cluster, componentName string) (sharding bool, cmpd *kbappsv1.ComponentDefinition, err error) { + resolveCmpd := func(cmpdName string) (*kbappsv1.ComponentDefinition, error) { + if cmpdName == "" { + return nil, errors.New("the referenced ComponentDefinition is empty") } + return client.AppsV1(). + ComponentDefinitions(). + Get(ctx, cmpdName, metav1.GetOptions{}) } - if len(supportUpdatedTpl) == 1 { - w.configTemplateSpec = configSpecs[0] - w.configSpecName = supportUpdatedTpl[0].Name - return nil + resolveShardingCmpd := func(cmpdName string) (*kbappsv1.ComponentDefinition, error) { + shardingCmpd, err := client.AppsV1(). + ShardingDefinitions(). + Get(ctx, cmpdName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + if shardingCmpd.Spec.Template.CompDef == "" { + return nil, errors.New("the referenced ShardingDefinition has no ComponentDefinition") + } + return resolveCmpd(shardingCmpd.Spec.Template.CompDef) } - return core.MakeError(multiConfigTemplateErrorMessage) -} -func (w *configWrapper) fillConfigFile() error { - if w.configFileKey != "" { - return nil + compSpec := clusterObj.Spec.GetComponentByName(componentName) + if compSpec != nil { + cmpd, err = resolveCmpd(compSpec.ComponentDef) + return } - - if w.configTemplateSpec.TemplateRef == "" { - return makeNotFoundTemplateErr(w.clusterName, w.componentName) + shardingSpec := clusterObj.Spec.GetShardingByName(componentName) + if shardingSpec == nil { + err = makeComponentNotExistErr(clusterObj.Name, componentName) + return } - cmObj := corev1.ConfigMap{} - cmKey := client.ObjectKey{ - Name: core.GetComponentCfgName(w.clusterName, w.componentName, w.configSpecName), - Namespace: w.Namespace, + sharding = true + if shardingSpec.ShardingDef != "" { + cmpd, err = resolveShardingCmpd(shardingSpec.ShardingDef) } - if err := util.GetResourceObjectFromGVR(types.ConfigmapGVR(), cmKey, w.Dynamic, &cmObj); err != nil { + + cmpd, err = resolveCmpd(shardingSpec.Template.ComponentDef) + return +} + +func resolveCmpdParametersDefs(rctx *ReconfigureContext) error { + configRender, err := resolveComponentConfigRender(rctx, rctx.Cmpd) + if err != nil { return err } - if len(cmObj.Data) == 0 { - return core.MakeError("not supported reconfiguring because there is no config file.") - } - - keys := w.filterForReconfiguring(cmObj.Data) - if len(keys) == 1 { - w.configFileKey = keys[0] + if configRender == nil || len(configRender.Spec.ParametersDefs) == 0 { return nil } - return core.MakeError(multiConfigFileErrorMessage) -} - -func (w *configWrapper) filterForReconfiguring(data map[string]string) []string { - keys := make([]string, 0, len(data)) - for configFileKey := range data { - if util.IsSupportConfigFileReconfigure(w.configTemplateSpec, configFileKey) { - keys = append(keys, configFileKey) + rctx.ConfigRender = configRender + for _, defName := range configRender.Spec.ParametersDefs { + pd, err := rctx.Client.ParametersV1alpha1().ParametersDefinitions().Get(rctx.Context, defName, metav1.GetOptions{}) + if err != nil { + return err } + rctx.ParametersDefs = append(rctx.ParametersDefs, pd) } - return keys + return nil } -func newConfigWrapper(baseOptions action.CreateOptions, componentName, configSpec, configKey string, params map[string]*string) (*configWrapper, error) { - var err error - var clusterObj *kbappsv1.Cluster - - if clusterObj, err = cluster.GetClusterByName(baseOptions.Dynamic, baseOptions.Name, baseOptions.Namespace); err != nil { +func resolveComponentConfigRender(rctx *ReconfigureContext, cmpd *kbappsv1.ComponentDefinition) (*parametersv1alpha1.ParamConfigRenderer, error) { + pcrList, err := rctx.Client.ParametersV1alpha1().ParamConfigRenderers().List(rctx.Context, metav1.ListOptions{}) + if err != nil { return nil, err } - return &configWrapper{ - CreateOptions: baseOptions, - Cluster: clusterObj, - clusterName: baseOptions.Name, - componentName: componentName, - configSpecName: configSpec, - configFileKey: configKey, - updatedParams: params, - }, nil + + var prcs []parametersv1alpha1.ParamConfigRenderer + for i, item := range pcrList.Items { + if item.Spec.ComponentDef != cmpd.Name { + continue + } + if item.Spec.ServiceVersion == "" || item.Spec.ServiceVersion == cmpd.Spec.ServiceVersion { + prcs = append(prcs, pcrList.Items[i]) + } + } + if len(prcs) == 1 { + return &prcs[0], nil + } + if len(prcs) > 1 { + return nil, fmt.Errorf("the ParamConfigRenderer is ambiguous which referenced cmpd[%s], prcs: [%s]", cmpd.Namespace, + generics.Map(prcs, func(pcr parametersv1alpha1.ParamConfigRenderer) string { return pcr.Name })) + } + return nil, nil } diff --git a/pkg/cmd/cluster/describe_ops.go b/pkg/cmd/cluster/describe_ops.go index 1d1df9df0..c94d03d4c 100644 --- a/pkg/cmd/cluster/describe_ops.go +++ b/pkg/cmd/cluster/describe_ops.go @@ -368,12 +368,7 @@ func (o *describeOpsOptions) getReconfiguringCommand(spec opsv1alpha1.OpsRequest } func generateReconfiguringCommand(clusterName string, updatedParams *opsv1alpha1.Reconfigure, components []string) []string { - if len(updatedParams.Configurations) == 0 { - return nil - } - - configuration := updatedParams.Configurations[0] - if len(configuration.Keys) == 0 { + if len(updatedParams.Parameters) == 0 { return nil } @@ -383,11 +378,8 @@ func generateReconfiguringCommand(clusterName string, updatedParams *opsv1alpha1 commandArgs = append(commandArgs, "configure") commandArgs = append(commandArgs, clusterName) commandArgs = append(commandArgs, fmt.Sprintf("--components=%s", strings.Join(components, ","))) - commandArgs = append(commandArgs, fmt.Sprintf("--config-spec=%s", configuration.Name)) - config := configuration.Keys[0] - commandArgs = append(commandArgs, fmt.Sprintf("--config-file=%s", config.Key)) - for _, p := range config.Parameters { + for _, p := range updatedParams.Parameters { if p.Value == nil { continue } diff --git a/pkg/cmd/cluster/list_ops.go b/pkg/cmd/cluster/list_ops.go index bd2a070b3..c2aa82d49 100644 --- a/pkg/cmd/cluster/list_ops.go +++ b/pkg/cmd/cluster/list_ops.go @@ -194,33 +194,6 @@ func getComponentNameFromOps(ops *opsv1alpha1.OpsRequest) string { return strings.Join(components, ",") } -func getTemplateNameFromOps(ops opsv1alpha1.OpsRequestSpec) string { - if ops.Type != opsv1alpha1.ReconfiguringType { - return "" - } - - tpls := make([]string, 0) - // TODO: support reconfigures - for _, config := range ops.Reconfigures[0].Configurations { - tpls = append(tpls, config.Name) - } - return strings.Join(tpls, ",") -} - -func getKeyNameFromOps(ops opsv1alpha1.OpsRequestSpec) string { - if ops.Type != opsv1alpha1.ReconfiguringType { - return "" - } - - keys := make([]string, 0) - for _, config := range ops.Reconfigures[0].Configurations { - for _, key := range config.Keys { - keys = append(keys, key.Key) - } - } - return strings.Join(keys, ",") -} - func (o *opsListOptions) containsIgnoreCase(s []string, e string) bool { for i := range s { if strings.EqualFold(s[i], e) { diff --git a/pkg/printer/describe.go b/pkg/printer/describe.go index ec8d2e872..0545eb4cc 100644 --- a/pkg/printer/describe.go +++ b/pkg/printer/describe.go @@ -30,9 +30,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" - - "github.com/apecloud/kbcli/pkg/types" "github.com/apecloud/kbcli/pkg/util" ) @@ -71,36 +68,6 @@ func PrintConditions(conditions []metav1.Condition, out io.Writer) { tbl.Print() } -// PrintComponentConfigMeta prints the conditions of resource. -func PrintComponentConfigMeta(tplInfos []types.ConfigTemplateInfo, clusterName, componentName string, out io.Writer) { - if len(tplInfos) == 0 { - return - } - tbl := NewTablePrinter(out) - PrintTitle("ConfigSpecs Meta") - enableReconfiguring := func(tpl appsv1alpha1.ComponentConfigSpec, configFileKey string) string { - if len(tpl.ConfigConstraintRef) > 0 && util.IsSupportConfigFileReconfigure(tpl, configFileKey) { - return "true" - } - return "false" - } - tbl.SetHeader("CONFIG-SPEC-NAME", "FILE", "ENABLED", "TEMPLATE", "CONSTRAINT", "RENDERED", "COMPONENT", "CLUSTER") - for _, info := range tplInfos { - for configFileKey := range info.CMObj.Data { - tbl.AddRow( - BoldYellow(info.Name), - configFileKey, - BoldYellow(enableReconfiguring(info.TPL, configFileKey)), - info.TPL.TemplateRef, - info.TPL.ConfigConstraintRef, - info.CMObj.Name, - componentName, - clusterName) - } - } - tbl.Print() -} - // PrintHelmValues prints the helm values file of the release in specified format, supports JSON、YAML and Table func PrintHelmValues(configs map[string]interface{}, format Format, out io.Writer) { inTable := func() { diff --git a/pkg/util/util.go b/pkg/util/util.go index 0e448f103..ae4862df3 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -44,7 +44,9 @@ import ( kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" + "github.com/apecloud/kubeblocks/pkg/generics" "github.com/fatih/color" "github.com/go-logr/logr" "github.com/pkg/errors" @@ -461,32 +463,32 @@ func GetEventObject(e *corev1.Event) string { } // ComponentConfigSpecs returns configSpecs used by the component. -func ComponentConfigSpecs(clusterName string, namespace string, cli dynamic.Interface, componentName string, reloadTpl bool) ([]kbappsv1alpha1.ComponentConfigSpec, error) { - var ( - clusterObj = kbappsv1.Cluster{} - clusterDefObj = kbappsv1.ClusterDefinition{} - ) - - clusterKey := client.ObjectKey{ - Namespace: namespace, - Name: clusterName, - } - if err := GetResourceObjectFromGVR(types.ClusterGVR(), clusterKey, cli, &clusterObj); err != nil { - return nil, err - } - clusterDefKey := client.ObjectKey{ - Namespace: "", - Name: clusterObj.Spec.ClusterDef, - } - if err := GetResourceObjectFromGVR(types.ClusterDefGVR(), clusterDefKey, cli, &clusterDefObj); err != nil { - return nil, err - } - compDef, err := GetComponentDefByCompName(cli, &clusterObj, componentName) - if err != nil { - return nil, err - } - return GetValidConfigSpecs(reloadTpl, ToV1ComponentConfigSpecs(compDef.Spec.Configs)) -} +// func ComponentConfigSpecs(clusterName string, namespace string, cli dynamic.Interface, componentName string, reloadTpl bool) ([]kbappsv1alpha1.ComponentConfigSpec, error) { +// var ( +// clusterObj = kbappsv1.Cluster{} +// clusterDefObj = kbappsv1.ClusterDefinition{} +// ) +// +// clusterKey := client.ObjectKey{ +// Namespace: namespace, +// Name: clusterName, +// } +// if err := GetResourceObjectFromGVR(types.ClusterGVR(), clusterKey, cli, &clusterObj); err != nil { +// return nil, err +// } +// clusterDefKey := client.ObjectKey{ +// Namespace: "", +// Name: clusterObj.Spec.ClusterDef, +// } +// if err := GetResourceObjectFromGVR(types.ClusterDefGVR(), clusterDefKey, cli, &clusterDefObj); err != nil { +// return nil, err +// } +// compDef, err := GetComponentDefByCompName(cli, &clusterObj, componentName) +// if err != nil { +// return nil, err +// } +// return GetValidConfigSpecs(reloadTpl, ToV1ComponentConfigSpecs(compDef.Spec.Configs)) +// } func GetComponentDefByName(dynamic dynamic.Interface, name string) (*kbappsv1.ComponentDefinition, error) { componentDef := &kbappsv1.ComponentDefinition{} @@ -547,33 +549,33 @@ func GetConfigSpecsFromComponentName(cli dynamic.Interface, namespace, clusterNa return GetValidConfigSpecs(reloadTpl, configSpecs) } -func ToV1ComponentConfigSpec(configSpec kbappsv1.ComponentConfigSpec) kbappsv1alpha1.ComponentConfigSpec { - config := kbappsv1alpha1.ComponentConfigSpec{ - ComponentTemplateSpec: kbappsv1alpha1.ComponentTemplateSpec{ - Name: configSpec.Name, - TemplateRef: configSpec.TemplateRef, - Namespace: configSpec.Namespace, - VolumeName: configSpec.VolumeName, - DefaultMode: configSpec.DefaultMode, - }, - Keys: configSpec.Keys, - ConfigConstraintRef: configSpec.ConfigConstraintRef, - InjectEnvTo: configSpec.InjectEnvTo, - AsSecret: configSpec.AsSecret, - } - for i := range configSpec.ReRenderResourceTypes { - config.ReRenderResourceTypes = append(config.ReRenderResourceTypes, kbappsv1alpha1.RerenderResourceType(configSpec.ReRenderResourceTypes[i])) - } - return config -} - -func ToV1ComponentConfigSpecs(configSpecs []kbappsv1.ComponentConfigSpec) []kbappsv1alpha1.ComponentConfigSpec { - var configs []kbappsv1alpha1.ComponentConfigSpec - for i := range configSpecs { - configs = append(configs, ToV1ComponentConfigSpec(configSpecs[i])) - } - return configs -} +// func ToV1ComponentConfigSpec(configSpec kbappsv1.ComponentConfigSpec) kbappsv1alpha1.ComponentConfigSpec { +// config := kbappsv1alpha1.ComponentConfigSpec{ +// ComponentTemplateSpec: kbappsv1alpha1.ComponentTemplateSpec{ +// Name: configSpec.Name, +// TemplateRef: configSpec.TemplateRef, +// Namespace: configSpec.Namespace, +// VolumeName: configSpec.VolumeName, +// DefaultMode: configSpec.DefaultMode, +// }, +// Keys: configSpec.Keys, +// ConfigConstraintRef: configSpec.ConfigConstraintRef, +// InjectEnvTo: configSpec.InjectEnvTo, +// AsSecret: configSpec.AsSecret, +// } +// for i := range configSpec.ReRenderResourceTypes { +// config.ReRenderResourceTypes = append(config.ReRenderResourceTypes, kbappsv1alpha1.RerenderResourceType(configSpec.ReRenderResourceTypes[i])) +// } +// return config +// } +// +// func ToV1ComponentConfigSpecs(configSpecs []kbappsv1.ComponentConfigSpec) []kbappsv1alpha1.ComponentConfigSpec { +// var configs []kbappsv1alpha1.ComponentConfigSpec +// for i := range configSpecs { +// configs = append(configs, ToV1ComponentConfigSpec(configSpecs[i])) +// } +// return configs +// } // GetK8SClientObject gets the client object of k8s, // obj must be a struct pointer so that obj can be updated with the response. @@ -690,24 +692,47 @@ func IsSupportReconfigureParams(tpl kbappsv1alpha1.ComponentConfigSpec, values m return true, nil } -func ValidateParametersModified(tpl *kbappsv1alpha1.ComponentConfigSpec, parameters sets.Set[string], cli dynamic.Interface) (err error) { - cc := kbappsv1beta1.ConfigConstraint{} - ccKey := client.ObjectKey{ - Namespace: "", - Name: tpl.ConfigConstraintRef, +func ValidateParametersModified(classifyParameters map[string]map[string]*parametersv1alpha1.ParametersInFile, pds []*parametersv1alpha1.ParametersDefinition) (err error) { + validator := func(index int, parameters sets.Set[string]) error { + if index < 0 || len(pds[index].Spec.ImmutableParameters) == 0 { + return nil + } + immuSet := sets.New(pds[index].Spec.ImmutableParameters...) + uniqueParameters := immuSet.Intersection(parameters) + if uniqueParameters.Len() == 0 { + return nil + } + return core.MakeError("parameter[%v] is immutable, cannot be modified!", cfgutil.ToSet(uniqueParameters).AsSlice()) } - if err = GetResourceObjectFromGVR(types.ConfigConstraintGVR(), ccKey, cli, &cc); err != nil { - return + + for _, tplParams := range classifyParameters { + for file, params := range tplParams { + match := func(pd *parametersv1alpha1.ParametersDefinition) bool { + return pd.Spec.FileName == file + } + index := generics.FindFirstFunc(pds, match) + if err := validator(index, sets.KeySet(params.Parameters)); err != nil { + return err + } + } } - return ValidateParametersModified2(parameters, cc.Spec) + return nil } -func ValidateParametersModified2(parameters sets.Set[string], cc kbappsv1beta1.ConfigConstraintSpec) error { - if len(cc.ImmutableParameters) == 0 { +func ValidateParametersModified2(parameters sets.Set[string], pds []*parametersv1alpha1.ParametersDefinition, file string) error { + var ret *parametersv1alpha1.ParametersDefinition + for _, pd := range pds { + if pd.Spec.FileName == file { + ret = pd + break + } + } + + if ret == nil || len(ret.Spec.ImmutableParameters) == 0 { return nil } - immutableParameters := sets.New(cc.ImmutableParameters...) + immutableParameters := sets.New(ret.Spec.ImmutableParameters...) uniqueParameters := immutableParameters.Intersection(parameters) if uniqueParameters.Len() == 0 { return nil From 70fb0cba95a59602f13f95eb17681da703050dc0 Mon Sep 17 00:00:00 2001 From: sophon Date: Mon, 24 Mar 2025 10:16:30 +0800 Subject: [PATCH 2/6] rmeove useless code --- pkg/cmd/cluster/config_util.go | 11 --- pkg/cmd/cluster/config_wrapper.go | 95 +------------------- pkg/cmd/cluster/errors.go | 24 +---- pkg/util/util.go | 143 ------------------------------ 4 files changed, 2 insertions(+), 271 deletions(-) diff --git a/pkg/cmd/cluster/config_util.go b/pkg/cmd/cluster/config_util.go index 2b0e075dc..aa46f3489 100644 --- a/pkg/cmd/cluster/config_util.go +++ b/pkg/cmd/cluster/config_util.go @@ -38,7 +38,6 @@ import ( "k8s.io/kubectl/pkg/cmd/util/editor" "sigs.k8s.io/controller-runtime/pkg/client" - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util" @@ -272,16 +271,6 @@ func getComponentNames(cluster *appsv1.Cluster) []string { return components } -func findTplByName(tpls []appsv1alpha1.ComponentConfigSpec, tplName string) *appsv1alpha1.ComponentConfigSpec { - for i := range tpls { - tpl := &tpls[i] - if tpl.Name == tplName { - return tpl - } - } - return nil -} - func resolveConfigTemplate(rctx *ReconfigureContext, dynamic dynamic.Interface) (map[string]*corev1.ConfigMap, error) { tpls := generics.Map(rctx.ConfigRender.Spec.Configs, func(parameter parametersv1alpha1.ComponentConfigDescription) string { return parameter.TemplateName diff --git a/pkg/cmd/cluster/config_wrapper.go b/pkg/cmd/cluster/config_wrapper.go index bd72194cd..9ea438b7e 100644 --- a/pkg/cmd/cluster/config_wrapper.go +++ b/pkg/cmd/cluster/config_wrapper.go @@ -58,8 +58,6 @@ type ReconfigureWrapper struct { // autofill field configSpecName string configFileKey string - - configTemplateSpec kbappsv1.ComponentTemplateSpec } func (w *ReconfigureWrapper) ConfigSpecName() string { @@ -90,98 +88,6 @@ func (w *ReconfigureWrapper) ConfigFile() string { return "" } -// AutoFillRequiredParam auto fills required param. -// func (w *ReconfigureWrapper) AutoFillRequiredParam() error { -// if err := w.fillConfigSpec(); err != nil { -// return err -// } -// return w.fillConfigFile() -// } - -// ValidateRequiredParam validates required param. -// func (w *ReconfigureWrapper) ValidateRequiredParam(forceReplace bool) error { -// // step1: check existence of component. -// if w.Spec.GetComponentByName(w.componentName) == nil { -// return makeComponentNotExistErr(w.clusterName, w.componentName) -// } -// -// // step2: check existence of configmap -// cmObj := corev1.ConfigMap{} -// cmKey := client.ObjectKey{ -// Name: core.GetComponentCfgName(w.clusterName, w.componentName, w.configSpecName), -// Namespace: w.Namespace, -// } -// if err := util.GetResourceObjectFromGVR(types.ConfigmapGVR(), cmKey, w.Dynamic, &cmObj); err != nil { -// return err -// } -// -// // step3: check existence of config file -// if _, ok := cmObj.Data[w.configFileKey]; !ok { -// return makeNotFoundConfigFileErr(w.configFileKey, w.configSpecName, cfgutil.ToSet(cmObj.Data).AsSlice()) -// } -// -// if !forceReplace && !util.IsSupportConfigFileReconfigure(w.configTemplateSpec, w.configFileKey) { -// return makeNotSupportConfigFileUpdateErr(w.configFileKey, w.configTemplateSpec) -// } -// return nil -// } - -func (w *ReconfigureWrapper) fillConfigSpec() error { - var rctx = w.rctx - - if rctx.ConfigRender == nil || len(rctx.ConfigRender.Spec.Configs) == 0 { - return makeNotFoundTemplateErr(w.Name, rctx.CompName) - } - - var configs []parametersv1alpha1.ComponentConfigDescription - if w.configSpecName != "" { - configs = intctrlutil.GetComponentConfigDescriptions(&rctx.ConfigRender.Spec, w.configSpecName) - if len(configs) == 0 { - return makeConfigSpecNotExistErr(w.Name, rctx.CompName, w.configSpecName) - } - } - return nil -} - -// func (w *ReconfigureWrapper) fillConfigFile() error { -// if w.configFileKey != "" { -// return nil -// } -// -// if w.configTemplateSpec.TemplateRef == "" { -// return makeNotFoundTemplateErr(w.clusterName, w.componentName) -// } -// -// cmObj := corev1.ConfigMap{} -// cmKey := client.ObjectKey{ -// Name: core.GetComponentCfgName(w.clusterName, w.componentName, w.configSpecName), -// Namespace: w.Namespace, -// } -// if err := util.GetResourceObjectFromGVR(types.ConfigmapGVR(), cmKey, w.Dynamic, &cmObj); err != nil { -// return err -// } -// if len(cmObj.Data) == 0 { -// return core.MakeError("not supported reconfiguring because there is no config file.") -// } -// -// keys := w.filterForReconfiguring(cmObj.Data) -// if len(keys) == 1 { -// w.configFileKey = keys[0] -// return nil -// } -// return core.MakeError(multiConfigFileErrorMessage) -// } - -// func (w *ReconfigureWrapper) filterForReconfiguring(data map[string]string) []string { -// keys := make([]string, 0, len(data)) -// for configFileKey := range data { -// if util.IsSupportConfigFileReconfigure(w.configTemplateSpec, configFileKey) { -// keys = append(keys, configFileKey) -// } -// } -// return keys -// } - func GetClientFromOptions(factory cmdutil.Factory) (*versioned.Clientset, error) { config, err := factory.ToRESTConfig() if err != nil { @@ -289,6 +195,7 @@ func resolveComponentDefObj(ctx context.Context, client *versioned.Clientset, cl sharding = true if shardingSpec.ShardingDef != "" { cmpd, err = resolveShardingCmpd(shardingSpec.ShardingDef) + return } cmpd, err = resolveCmpd(shardingSpec.Template.ComponentDef) diff --git a/pkg/cmd/cluster/errors.go b/pkg/cmd/cluster/errors.go index d7c947be4..cf9d65f3c 100644 --- a/pkg/cmd/cluster/errors.go +++ b/pkg/cmd/cluster/errors.go @@ -20,28 +20,18 @@ along with this program. If not, see . package cluster import ( - appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" ) var ( - clusterNotExistErrMessage = "cluster[name=%s] does not exist. Please check that is spelled correctly." componentNotExistErrMessage = "cluster[name=%s] does not have component[name=%s]. Please check that --component is spelled correctly." missingClusterArgErrMassage = "cluster name should be specified, using --help." missingUpdatedParametersErrMessage = "missing updated parameters, using --help." - multiComponentsErrorMessage = "when multi components exist, specify a component, using --components" - multiConfigTemplateErrorMessage = "when multi config templates exist, specify a config template, using --config-spec" - multiConfigFileErrorMessage = "when multi config files exist, specify a config file, using --config-file" - - notFoundValidConfigTemplateErrorMessage = "cannot find valid config templates for component[name=%s] in the cluster[name=%s]" - notFoundConfigSpecErrorMessage = "cannot find config spec[%s] for component[name=%s] in the cluster[name=%s]" - notFoundConfigFileErrorMessage = "cannot find config file[name=%s] in the configspec[name=%s], all configfiles: %v" - notSupportFileUpdateErrorMessage = "not supported file[%s] for updating, current supported files: %v" + notFoundConfigFileErrorMessage = "cannot find config file[name=%s] in the configspec[name=%s], all configfiles: %v" - noConfigConstraintPrompt = "cannot find configConstraint for template[%s]" notConfigSchemaPrompt = "The config template[%s] is not defined in schema and parameter explanation info cannot be generated." cue2openAPISchemaFailedPrompt = "The cue schema may not satisfy the conversion constraints of openAPISchema and parameter explanation info cannot be generated." restartConfirmPrompt = "The parameter change incurs a cluster restart, which brings the cluster down for a while. Enter to continue...\n, " @@ -49,10 +39,6 @@ var ( confirmApplyReconfigurePrompt = "Are you sure you want to apply these changes?\n" ) -func makeClusterNotExistErr(clusterName string) error { - return cfgcore.MakeError(clusterNotExistErrMessage, clusterName) -} - func makeComponentNotExistErr(clusterName, component string) error { return cfgcore.MakeError(componentNotExistErrMessage, clusterName, component) } @@ -61,18 +47,10 @@ func makeConfigSpecNotExistErr(clusterName, component, configSpec string) error return cfgcore.MakeError(notFoundConfigSpecErrorMessage, configSpec, component, clusterName) } -func makeNotFoundTemplateErr(clusterName, component string) error { - return cfgcore.MakeError(notFoundValidConfigTemplateErrorMessage, component, clusterName) -} - func makeNotFoundConfigFileErr(configFile, configSpec string, all []string) error { return cfgcore.MakeError(notFoundConfigFileErrorMessage, configFile, configSpec, all) } -func makeNotSupportConfigFileUpdateErr(configFile string, configSpec appsv1alpha1.ComponentConfigSpec) error { - return cfgcore.MakeError(notSupportFileUpdateErrorMessage, configFile, configSpec.Keys) -} - func makeMissingClusterNameErr() error { return cfgcore.MakeError(missingClusterArgErrMassage) } diff --git a/pkg/util/util.go b/pkg/util/util.go index ae4862df3..60c8a0d12 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -462,34 +462,6 @@ func GetEventObject(e *corev1.Event) string { return fmt.Sprintf("%s/%s", kind, e.InvolvedObject.Name) } -// ComponentConfigSpecs returns configSpecs used by the component. -// func ComponentConfigSpecs(clusterName string, namespace string, cli dynamic.Interface, componentName string, reloadTpl bool) ([]kbappsv1alpha1.ComponentConfigSpec, error) { -// var ( -// clusterObj = kbappsv1.Cluster{} -// clusterDefObj = kbappsv1.ClusterDefinition{} -// ) -// -// clusterKey := client.ObjectKey{ -// Namespace: namespace, -// Name: clusterName, -// } -// if err := GetResourceObjectFromGVR(types.ClusterGVR(), clusterKey, cli, &clusterObj); err != nil { -// return nil, err -// } -// clusterDefKey := client.ObjectKey{ -// Namespace: "", -// Name: clusterObj.Spec.ClusterDef, -// } -// if err := GetResourceObjectFromGVR(types.ClusterDefGVR(), clusterDefKey, cli, &clusterDefObj); err != nil { -// return nil, err -// } -// compDef, err := GetComponentDefByCompName(cli, &clusterObj, componentName) -// if err != nil { -// return nil, err -// } -// return GetValidConfigSpecs(reloadTpl, ToV1ComponentConfigSpecs(compDef.Spec.Configs)) -// } - func GetComponentDefByName(dynamic dynamic.Interface, name string) (*kbappsv1.ComponentDefinition, error) { componentDef := &kbappsv1.ComponentDefinition{} if err := GetK8SClientObject(dynamic, componentDef, types.CompDefGVR(), "", name); err != nil { @@ -513,70 +485,6 @@ func GetComponentDefByCompName(cli dynamic.Interface, clusterObj *kbappsv1.Clust return GetComponentDefByName(cli, compDefName) } -func GetValidConfigSpecs(reloadTpl bool, configSpecs []kbappsv1alpha1.ComponentConfigSpec) ([]kbappsv1alpha1.ComponentConfigSpec, error) { - if !reloadTpl || len(configSpecs) == 1 { - return configSpecs, nil - } - - validConfigSpecs := make([]kbappsv1alpha1.ComponentConfigSpec, 0, len(configSpecs)) - for _, configSpec := range configSpecs { - if configSpec.ConfigConstraintRef != "" && configSpec.TemplateRef != "" { - validConfigSpecs = append(validConfigSpecs, configSpec) - } - } - return validConfigSpecs, nil -} - -func GetConfigSpecsFromComponentName(cli dynamic.Interface, namespace, clusterName, componentName string, reloadTpl bool) ([]kbappsv1alpha1.ComponentConfigSpec, error) { - configKey := client.ObjectKey{ - Namespace: namespace, - Name: core.GenerateComponentConfigurationName(clusterName, componentName), - } - config := kbappsv1alpha1.Configuration{} - if err := GetResourceObjectFromGVR(types.ConfigurationGVR(), configKey, cli, &config); err != nil { - return nil, err - } - if len(config.Spec.ConfigItemDetails) == 0 { - return nil, nil - } - - configSpecs := make([]kbappsv1alpha1.ComponentConfigSpec, 0, len(config.Spec.ConfigItemDetails)) - for _, item := range config.Spec.ConfigItemDetails { - if item.ConfigSpec != nil { - configSpecs = append(configSpecs, *item.ConfigSpec) - } - } - return GetValidConfigSpecs(reloadTpl, configSpecs) -} - -// func ToV1ComponentConfigSpec(configSpec kbappsv1.ComponentConfigSpec) kbappsv1alpha1.ComponentConfigSpec { -// config := kbappsv1alpha1.ComponentConfigSpec{ -// ComponentTemplateSpec: kbappsv1alpha1.ComponentTemplateSpec{ -// Name: configSpec.Name, -// TemplateRef: configSpec.TemplateRef, -// Namespace: configSpec.Namespace, -// VolumeName: configSpec.VolumeName, -// DefaultMode: configSpec.DefaultMode, -// }, -// Keys: configSpec.Keys, -// ConfigConstraintRef: configSpec.ConfigConstraintRef, -// InjectEnvTo: configSpec.InjectEnvTo, -// AsSecret: configSpec.AsSecret, -// } -// for i := range configSpec.ReRenderResourceTypes { -// config.ReRenderResourceTypes = append(config.ReRenderResourceTypes, kbappsv1alpha1.RerenderResourceType(configSpec.ReRenderResourceTypes[i])) -// } -// return config -// } -// -// func ToV1ComponentConfigSpecs(configSpecs []kbappsv1.ComponentConfigSpec) []kbappsv1alpha1.ComponentConfigSpec { -// var configs []kbappsv1alpha1.ComponentConfigSpec -// for i := range configSpecs { -// configs = append(configs, ToV1ComponentConfigSpec(configSpecs[i])) -// } -// return configs -// } - // GetK8SClientObject gets the client object of k8s, // obj must be a struct pointer so that obj can be updated with the response. func GetK8SClientObject(dynamic dynamic.Interface, @@ -603,57 +511,6 @@ func GetResourceObjectFromGVR(gvr schema.GroupVersionResource, key client.Object return apiruntime.DefaultUnstructuredConverter.FromUnstructured(unstructuredObj.Object, k8sObj) } -// GetComponentsFromResource returns name of component. -func GetComponentsFromResource(namespace, clusterName string, componentSpecs []kbappsv1.ClusterComponentSpec, cli dynamic.Interface) ([]string, error) { - componentNames := make([]string, 0, len(componentSpecs)) - for _, component := range componentSpecs { - configKey := client.ObjectKey{ - Namespace: namespace, - Name: core.GenerateComponentConfigurationName(clusterName, component.Name), - } - config := kbappsv1alpha1.Configuration{} - if err := GetResourceObjectFromGVR(types.ConfigurationGVR(), configKey, cli, &config); err != nil { - return nil, err - } - if len(config.Spec.ConfigItemDetails) == 0 { - continue - } - - if enableReconfiguring(&config.Spec) { - componentNames = append(componentNames, component.Name) - } - } - return componentNames, nil -} - -func IsSupportConfigFileReconfigure(configTemplateSpec kbappsv1alpha1.ComponentConfigSpec, configFileKey string) bool { - if len(configTemplateSpec.Keys) == 0 { - return true - } - for _, keySelector := range configTemplateSpec.Keys { - if keySelector == configFileKey { - return true - } - } - return false -} - -func enableReconfiguring(component *kbappsv1alpha1.ConfigurationSpec) bool { - if component == nil { - return false - } - for _, item := range component.ConfigItemDetails { - if item.ConfigSpec == nil { - continue - } - tpl := item.ConfigSpec - if len(tpl.ConfigConstraintRef) > 0 && len(tpl.TemplateRef) > 0 { - return true - } - } - return false -} - // IsSupportReconfigureParams checks whether all updated parameters belong to config template parameters. func IsSupportReconfigureParams(tpl kbappsv1alpha1.ComponentConfigSpec, values map[string]*string, cli dynamic.Interface) (bool, error) { var ( From 856118e2363191cd9913971cd580d954b8de41b2 Mon Sep 17 00:00:00 2001 From: sophon Date: Mon, 24 Mar 2025 15:54:46 +0800 Subject: [PATCH 3/6] chore: remove containers_image_openpgp tag --- Makefile | 2 +- docker/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 2e98e078f..e60b2c47c 100644 --- a/Makefile +++ b/Makefile @@ -49,7 +49,7 @@ endif export GOPROXY # build tags -BUILD_TAGS="containers_image_openpgp" +BUILD_TAGS="" TAG_LATEST ?= false diff --git a/docker/Dockerfile b/docker/Dockerfile index 2aee1458c..573c16210 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -39,7 +39,7 @@ RUN --mount=type=cache,target=/go/pkg/mod \ RUN --mount=type=bind,target=. \ --mount=type=cache,target=/root/.cache/go-build \ --mount=type=cache,target=/go/pkg/mod \ - CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags="${LD_FLAGS}" -tags="containers_image_openpgp" -a -o /out/kbcli cmd/cli/main.go + CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags="${LD_FLAGS}" -a -o /out/kbcli cmd/cli/main.go # Use alpine with tag 20230329 is corresponding to "edge" tag (latest release to date is 3.18) as of 20230625 FROM docker.io/alpine:edge as dist From 9fad8e509ab437ce40715040187ca5c3db1e807e Mon Sep 17 00:00:00 2001 From: sophon Date: Mon, 24 Mar 2025 17:34:08 +0800 Subject: [PATCH 4/6] fix ut --- Makefile | 16 ++++----- go.mod | 11 +++--- go.sum | 22 ++++++------ pkg/cmd/cluster/config_ops.go | 9 ++++- pkg/cmd/cluster/config_ops_test.go | 46 ++++++++----------------- pkg/cmd/cluster/config_resource.go | 7 ++-- pkg/cmd/cluster/config_wrapper.go | 27 +++++---------- pkg/cmd/cluster/logs_test.go | 12 +++++-- pkg/testing/fake.go | 55 ++++++++++++++++++++++++++++++ pkg/types/types.go | 4 +++ 10 files changed, 126 insertions(+), 83 deletions(-) diff --git a/Makefile b/Makefile index e60b2c47c..05f5f9e60 100644 --- a/Makefile +++ b/Makefile @@ -48,10 +48,6 @@ GOPROXY := https://proxy.golang.org endif export GOPROXY -# build tags -BUILD_TAGS="" - - TAG_LATEST ?= false BUILDX_ENABLED ?= "" ifeq ($(BUILDX_ENABLED), "") @@ -104,7 +100,7 @@ fmt: ## Run go fmt against code. .PHONY: vet vet: ## Run go vet against code. - GOOS=$(GOOS) $(GO) vet -tags $(BUILD_TAGS) -mod=mod ./... + GOOS=$(GOOS) $(GO) vet -mod=mod ./... .PHONY: cue-fmt cue-fmt: cuetool ## Run cue fmt against code. @@ -124,7 +120,7 @@ golangci-lint: golangci generate ## Run golangci-lint against code. .PHONY: staticcheck staticcheck: staticchecktool generate ## Run staticcheck against code. - $(STATICCHECK) -tags $(BUILD_TAGS) ./... + $(STATICCHECK) ./... .PHONY: build-checks build-checks: generate fmt vet goimports lint-fast ## Run build checks. @@ -143,11 +139,11 @@ TEST_PACKAGES ?= ./pkg/... ./cmd/... OUTPUT_COVERAGE=-coverprofile cover.out .PHONY: test test: generate ## Run operator controller tests with current $KUBECONFIG context. if existing k8s cluster is k3d or minikube, specify EXISTING_CLUSTER_TYPE. - $(GO) test -tags $(BUILD_TAGS) -p 1 $(TEST_PACKAGES) $(OUTPUT_COVERAGE) + $(GO) test -p 1 $(TEST_PACKAGES) $(OUTPUT_COVERAGE) .PHONY: test-fast test-fast: - $(GO) test -tags $(BUILD_TAGS) -short $(TEST_PACKAGES) $(OUTPUT_COVERAGE) + $(GO) test -short $(TEST_PACKAGES) $(OUTPUT_COVERAGE) .PHONY: cover-report cover-report: ## Generate cover.html from cover.out @@ -179,7 +175,7 @@ CLI_LD_FLAGS ="-s -w \ -X github.com/apecloud/kbcli/version.DefaultKubeBlocksVersion=$(VERSION)" bin/kbcli.%: ## Cross build bin/kbcli.$(OS).$(ARCH). - GOOS=$(word 2,$(subst ., ,$@)) GOARCH=$(word 3,$(subst ., ,$@)) $(GO) build -tags $(BUILD_TAGS) -ldflags=${CLI_LD_FLAGS} -o $@ cmd/cli/main.go + GOOS=$(word 2,$(subst ., ,$@)) GOARCH=$(word 3,$(subst ., ,$@)) $(GO) build -ldflags=${CLI_LD_FLAGS} -o $@ cmd/cli/main.go .PHONY: fetch-addons fetch-addons: ## update addon submodule @@ -249,7 +245,7 @@ clean-kbcli: ## Clean bin/kbcli*. .PHONY: kbcli-doc kbcli-doc: ## generate CLI command reference manual. - $(GO) run -tags $(BUILD_TAGS) ./hack/docgen/cli/main.go ./docs/user_docs/cli + $(GO) run ./hack/docgen/cli/main.go ./docs/user_docs/cli .PHONY: install-docker-buildx install-docker-buildx: ## Create `docker buildx` builder. diff --git a/go.mod b/go.mod index 33ced0326..ea30b5505 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/Masterminds/semver/v3 v3.3.0 github.com/NimbleMarkets/ntcharts v0.1.2 github.com/apecloud/dbctl v0.0.0-20240827084000-68a1980b1a46 - github.com/apecloud/kubeblocks v1.0.0-beta.33 + github.com/apecloud/kubeblocks v1.0.0-beta.35 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/briandowns/spinner v1.23.0 github.com/chaos-mesh/chaos-mesh/api v0.0.0-20230912020346-a5d89c1c90ad @@ -118,8 +118,8 @@ require ( github.com/cloudflare/circl v1.3.7 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect github.com/containerd/cgroups/v3 v3.0.3 // indirect - github.com/containerd/containerd v1.7.19 // indirect - github.com/containerd/errdefs v0.1.0 // indirect + github.com/containerd/containerd v1.7.27 // indirect + github.com/containerd/errdefs v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/containers/image/v5 v5.32.2 // indirect @@ -250,7 +250,8 @@ require ( github.com/moby/spdystream v0.4.0 // indirect github.com/moby/sys/mountinfo v0.7.2 // indirect github.com/moby/sys/sequential v0.5.0 // indirect - github.com/moby/sys/user v0.2.0 // indirect + github.com/moby/sys/user v0.3.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -355,7 +356,7 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiserver v0.31.1 // indirect - k8s.io/component-helpers v0.29.2 // indirect + k8s.io/component-helpers v0.29.14 // indirect oras.land/oras-go v1.2.5 // indirect periph.io/x/host/v3 v3.8.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect diff --git a/go.sum b/go.sum index ffacc6c8a..3f503b695 100644 --- a/go.sum +++ b/go.sum @@ -677,8 +677,8 @@ github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4x github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/apecloud/dbctl v0.0.0-20240827084000-68a1980b1a46 h1:+Jcc7IjDGxPgIfIkGX2Q5Yxj35U65zgcfjh0B9rDhjo= github.com/apecloud/dbctl v0.0.0-20240827084000-68a1980b1a46/go.mod h1:eksJtZ7z1nVcVLqDzAdcN5EfpHwXllIAvHZEks2zWys= -github.com/apecloud/kubeblocks v1.0.0-beta.33 h1:A6lWmz/yZEP7s1lP3TXGfyPTixO9BAPJ+6TGwdqiKbk= -github.com/apecloud/kubeblocks v1.0.0-beta.33/go.mod h1:b656nTyvHhwRwOuwNpOPG87Q0Lba3ygGRWoSOacPt5o= +github.com/apecloud/kubeblocks v1.0.0-beta.35 h1:S+Zd3Bzo72lfo/VY3dct1wPHC+rHYVFz1bnHqy79mlo= +github.com/apecloud/kubeblocks v1.0.0-beta.35/go.mod h1:Mk5xRLm2MpxoTNZKEdDcrIY3I1EpokQBU3Q9Zwse8MI= github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -790,12 +790,12 @@ github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEa github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= -github.com/containerd/containerd v1.7.19 h1:/xQ4XRJ0tamDkdzrrBAUy/LE5nCcxFKdBm4EcPrSMEE= -github.com/containerd/containerd v1.7.19/go.mod h1:h4FtNYUUMB4Phr6v+xG89RYKj9XccvbNSCKjdufCrkc= -github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= -github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= -github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= -github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0= +github.com/containerd/containerd v1.7.27 h1:yFyEyojddO3MIGVER2xJLWoCIn+Up4GaHFquP7hsFII= +github.com/containerd/containerd v1.7.27/go.mod h1:xZmPnl75Vc+BLGt4MIfu6bp+fy03gdHAn9bz+FreFR0= +github.com/containerd/continuity v0.4.4 h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34PlCpMKII= +github.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= +github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= +github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= @@ -1405,8 +1405,10 @@ github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9Kou github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= -github.com/moby/sys/user v0.2.0 h1:OnpapJsRp25vkhw8TFG6OLJODNh/3rEwRWtJ3kakwRM= -github.com/moby/sys/user v0.2.0/go.mod h1:RYstrcWOJpVh+6qzUqp2bU3eaRpdiQeKGlKitaH0PM8= +github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= +github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= diff --git a/pkg/cmd/cluster/config_ops.go b/pkg/cmd/cluster/config_ops.go index 34656d01c..9f3b938a4 100644 --- a/pkg/cmd/cluster/config_ops.go +++ b/pkg/cmd/cluster/config_ops.go @@ -26,6 +26,7 @@ import ( opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" + "github.com/apecloud/kubeblocks/pkg/client/clientset/versioned" cfgcm "github.com/apecloud/kubeblocks/pkg/configuration/config_manager" "github.com/apecloud/kubeblocks/pkg/configuration/core" configctrl "github.com/apecloud/kubeblocks/pkg/controller/configuration" @@ -55,6 +56,8 @@ type configOpsOptions struct { ComponentName string LocalFilePath string `json:"localFilePath"` Parameters []string `json:"parameters"` + + clientSet versioned.Interface } var ( @@ -83,7 +86,11 @@ func (o *configOpsOptions) Complete() error { } } - wrapper, err := newConfigWrapper(o.CreateOptions, o.ComponentName, o.CfgTemplateName, o.CfgFile, o.KeyValues) + client := o.clientSet + if client == nil { + client = GetClientFromOptionsOrDie(o.Factory) + } + wrapper, err := newConfigWrapper(client, o.Namespace, o.Name, o.ComponentName, o.CfgTemplateName, o.CfgFile, o.KeyValues) if err != nil { return err } diff --git a/pkg/cmd/cluster/config_ops_test.go b/pkg/cmd/cluster/config_ops_test.go index ff70119cb..9e18ab35d 100644 --- a/pkg/cmd/cluster/config_ops_test.go +++ b/pkg/cmd/cluster/config_ops_test.go @@ -33,7 +33,7 @@ import ( cmdtesting "k8s.io/kubectl/pkg/cmd/testing" kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + kbfakeclient "github.com/apecloud/kubeblocks/pkg/client/clientset/versioned/fake" cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core" testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" @@ -71,39 +71,17 @@ var _ = Describe("reconfigure test", func() { It("check params for reconfiguring operations", func() { const ( - ns = "default" - clusterName = "test-cluster" - statefulCompDefName = "replicasets" - statefulCompName = "mysql" - configSpecName = "mysql-config-tpl" - configVolumeName = "mysql-config" + ns = "default" + clusterName = "test-cluster" + statefulCompName = "mysql" + configSpecName = "mysql-config-tpl" ) By("Create configmap and config constraint obj") - configmap := testapps.NewCustomizedObj("resources/mysql-config-template.yaml", &corev1.ConfigMap{}, testapps.WithNamespace(ns)) - constraint := testapps.NewCustomizedObj("resources/mysql-config-constraint.yaml", - &appsv1beta1.ConfigConstraint{}) + configmap := testapps.NewCustomizedObj("resources/mysql-config-template.yaml", &corev1.ConfigMap{}, testapps.WithNamespace(ns), testapps.WithName(testing.FakeMysqlTemplateName)) componentConfig := testapps.NewConfigMap(ns, cfgcore.GetComponentCfgName(clusterName, statefulCompName, configSpecName), testapps.SetConfigMapData("my.cnf", "")) - By("Create a configuration obj") - // configObj := builder.NewConfigurationBuilder(ns, cfgcore.GenerateComponentConfigurationName(clusterName, statefulCompName)). - // ClusterRef(clusterName). - // Component(statefulCompName). - // AddConfigurationItem(kbappsv1.ComponentConfigSpec{ - // ComponentTemplateSpec: kbappsv1.ComponentTemplateSpec{ - // Name: configSpecName, - // TemplateRef: configmap.Name, - // Namespace: ns, - // VolumeName: configVolumeName, - // }, - // ConfigConstraintRef: constraint.Name, - // }). - // GetObject() - By("creating a cluster") - clusterObj := testapps.NewClusterFactory(ns, clusterName, ""). - AddComponent(statefulCompName, statefulCompDefName).GetObject() - - objs := []runtime.Object{configmap, constraint, clusterObj, componentConfig} - ttf, ops := NewFakeOperationsOptions(ns, clusterObj.Name, objs...) + objs := []runtime.Object{configmap, componentConfig} + ttf, ops := NewFakeOperationsOptions(ns, clusterName, objs...) o := &configOpsOptions{ // nil cannot be set to a map struct in CueLang, so init the map of KeyValues. OperationsOptions: &OperationsOptions{ @@ -112,10 +90,16 @@ var _ = Describe("reconfigure test", func() { } o.KeyValues = make(map[string]*string) o.HasPatch = true + o.clientSet = kbfakeclient.NewSimpleClientset( + testing.FakeCluster(clusterName, ns), + testing.FakeCompDef(), + testing.FakeParameterDefinition(), + testing.FakeParameterConfigRenderer(), + ) defer ttf.Cleanup() By("validate reconfiguring parameters") - o.ComponentNames = []string{statefulCompName} + o.ComponentNames = []string{testing.ComponentName} _, err := o.parseUpdatedParams() Expect(err.Error()).To(ContainSubstring(missingUpdatedParametersErrMessage)) o.Parameters = []string{"abcd"} diff --git a/pkg/cmd/cluster/config_resource.go b/pkg/cmd/cluster/config_resource.go index 660bed0b0..aebd3472f 100644 --- a/pkg/cmd/cluster/config_resource.go +++ b/pkg/cmd/cluster/config_resource.go @@ -35,7 +35,7 @@ type ConfigObjectsWrapper struct { rctxMap map[string]*ReconfigureContext } -func GetCluster(clientSet *versioned.Clientset, ns, clusterName string) (*appsv1.Cluster, error) { +func GetCluster(clientSet versioned.Interface, ns, clusterName string) (*appsv1.Cluster, error) { clusterObj, err := clientSet.AppsV1().Clusters(ns).Get(context.TODO(), clusterName, metav1.GetOptions{}) if err != nil { return nil, err @@ -44,10 +44,7 @@ func GetCluster(clientSet *versioned.Clientset, ns, clusterName string) (*appsv1 } func New(clusterName string, namespace string, options *describeOpsOptions, components ...string) (*ConfigObjectsWrapper, error) { - clientSet, err := GetClientFromOptions(options.factory) - if err != nil { - return nil, err - } + clientSet := GetClientFromOptionsOrDie(options.factory) clusterObj, err := GetCluster(clientSet, namespace, clusterName) if err != nil { return nil, err diff --git a/pkg/cmd/cluster/config_wrapper.go b/pkg/cmd/cluster/config_wrapper.go index 9ea438b7e..f17f2535e 100644 --- a/pkg/cmd/cluster/config_wrapper.go +++ b/pkg/cmd/cluster/config_wrapper.go @@ -31,12 +31,10 @@ import ( "github.com/apecloud/kubeblocks/pkg/generics" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" cmdutil "k8s.io/kubectl/pkg/cmd/util" - - "github.com/apecloud/kbcli/pkg/action" ) type ReconfigureContext struct { - Client *versioned.Clientset + Client versioned.Interface Context context.Context Cluster *kbappsv1.Cluster @@ -49,8 +47,6 @@ type ReconfigureContext struct { } type ReconfigureWrapper struct { - action.CreateOptions - rctx *ReconfigureContext updatedParams map[string]*string @@ -88,22 +84,16 @@ func (w *ReconfigureWrapper) ConfigFile() string { return "" } -func GetClientFromOptions(factory cmdutil.Factory) (*versioned.Clientset, error) { +func GetClientFromOptionsOrDie(factory cmdutil.Factory) versioned.Interface { config, err := factory.ToRESTConfig() if err != nil { - return nil, err + panic(err) } - - return versioned.NewForConfigOrDie(config), err + return versioned.NewForConfigOrDie(config) } -func newConfigWrapper(baseOptions action.CreateOptions, componentName, templateName, fileName string, params map[string]*string) (*ReconfigureWrapper, error) { - cli, err := GetClientFromOptions(baseOptions.Factory) - if err != nil { - return nil, err - } - - rctx, err := generateReconfigureContext(context.TODO(), cli, baseOptions.Name, componentName, baseOptions.Namespace) +func newConfigWrapper(clientSet versioned.Interface, ns, clusterName, componentName, templateName, fileName string, params map[string]*string) (*ReconfigureWrapper, error) { + rctx, err := generateReconfigureContext(context.TODO(), clientSet, clusterName, componentName, ns) if err != nil { return nil, err } @@ -112,7 +102,6 @@ func newConfigWrapper(baseOptions action.CreateOptions, componentName, templateN } return &ReconfigureWrapper{ - CreateOptions: baseOptions, rctx: rctx, configSpecName: templateName, configFileKey: fileName, @@ -120,7 +109,7 @@ func newConfigWrapper(baseOptions action.CreateOptions, componentName, templateN }, nil } -func generateReconfigureContext(ctx context.Context, clientSet *versioned.Clientset, clusterName, componentName, ns string) (*ReconfigureContext, error) { +func generateReconfigureContext(ctx context.Context, clientSet versioned.Interface, clusterName, componentName, ns string) (*ReconfigureContext, error) { defaultCompName := func(clusterSpec kbappsv1.ClusterSpec) string { switch { case len(clusterSpec.ComponentSpecs) != 0: @@ -159,7 +148,7 @@ func generateReconfigureContext(ctx context.Context, clientSet *versioned.Client return rctx, nil } -func resolveComponentDefObj(ctx context.Context, client *versioned.Clientset, clusterObj *kbappsv1.Cluster, componentName string) (sharding bool, cmpd *kbappsv1.ComponentDefinition, err error) { +func resolveComponentDefObj(ctx context.Context, client versioned.Interface, clusterObj *kbappsv1.Cluster, componentName string) (sharding bool, cmpd *kbappsv1.ComponentDefinition, err error) { resolveCmpd := func(cmpdName string) (*kbappsv1.ComponentDefinition, error) { if cmpdName == "" { return nil, errors.New("the referenced ComponentDefinition is empty") diff --git a/pkg/cmd/cluster/logs_test.go b/pkg/cmd/cluster/logs_test.go index 3673b233a..29320e13f 100644 --- a/pkg/cmd/cluster/logs_test.go +++ b/pkg/cmd/cluster/logs_test.go @@ -154,9 +154,17 @@ var _ = Describe("logs", func() { compDefName := "component-type" compName := "component-name" compDef := testapps.NewComponentDefinitionFactory(compDefName). - AddLogConfig("slow", "/log/mysql/*slow.log"). - AddLogConfig("error", "/log/mysql/*.err"). Get() + compDef.Spec.LogConfigs = []kbappsv1.LogConfig{ + { + Name: "slow", + FilePathPattern: "/log/mysql/*slow.log", + }, + { + Name: "error", + FilePathPattern: "/log/mysql/*err", + }, + } pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", diff --git a/pkg/testing/fake.go b/pkg/testing/fake.go index 29368bd47..a78b8b479 100644 --- a/pkg/testing/fake.go +++ b/pkg/testing/fake.go @@ -24,6 +24,7 @@ import ( "time" appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1" "github.com/apecloud/kubeblocks/pkg/controller/instanceset" chaosmeshv1alpha1 "github.com/chaos-mesh/chaos-mesh/api/v1alpha1" snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" @@ -74,6 +75,9 @@ const ( accountName = "root" + fakeConfigTemplateName = "mysql-config" + FakeMysqlTemplateName = "mysql-config-tpl" + IsDefault = true ) @@ -352,6 +356,14 @@ func FakeCompDef() *kbappsv1.ComponentDefinition { Reconfigure: &defaultAction, AccountProvision: &defaultAction, }, + Configs: []kbappsv1.ComponentTemplateSpec{ + { + Name: fakeConfigTemplateName, + TemplateRef: FakeMysqlTemplateName, + Namespace: "default", + VolumeName: "for_test", + }, + }, } return compDef } @@ -362,6 +374,49 @@ func FakeActionSet() *dpv1alpha1.ActionSet { return as } +func FakeParameterDefinition() *parametersv1alpha1.ParametersDefinition { + pd := ¶metersv1alpha1.ParametersDefinition{ + TypeMeta: metav1.TypeMeta{ + APIVersion: fmt.Sprintf("%s/%s", types.ParametersAPIGroup, types.ParametersAPIVersion), + Kind: types.KindParametersDef, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pd", + }, + Spec: parametersv1alpha1.ParametersDefinitionSpec{ + FileName: "my.cnf", + }, + } + return pd +} + +func FakeParameterConfigRenderer() *parametersv1alpha1.ParamConfigRenderer { + pcr := ¶metersv1alpha1.ParamConfigRenderer{ + TypeMeta: metav1.TypeMeta{ + APIVersion: fmt.Sprintf("%s/%s", types.ParametersAPIGroup, types.ParametersAPIVersion), + Kind: types.KindParameterConfigRender, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pcr", + Namespace: Namespace, + }, + Spec: parametersv1alpha1.ParamConfigRendererSpec{ + ComponentDef: CompDefName, + ParametersDefs: []string{"test-pd"}, + Configs: []parametersv1alpha1.ComponentConfigDescription{ + { + Name: "my.cnf", + TemplateName: fakeConfigTemplateName, + FileFormatConfig: ¶metersv1alpha1.FileFormatConfig{ + Format: parametersv1alpha1.Ini, + }, + }, + }, + }, + } + return pcr +} + func FakeBackupPolicy(backupPolicyName, clusterName string) *dpv1alpha1.BackupPolicy { template := &dpv1alpha1.BackupPolicy{ TypeMeta: metav1.TypeMeta{ diff --git a/pkg/types/types.go b/pkg/types/types.go index 1ca6813b8..76fa959cb 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -131,6 +131,8 @@ const ( KindDeployment = "Deployment" KindConfigMap = "ConfigMap" KindCronJob = "CronJob" + KindParametersDef = "ParametersDefinition" + KindParameterConfigRender = "ParameterConfigRender" ) // K8S rbac API group @@ -176,6 +178,8 @@ const ( ResourceBackupRepos = "backuprepos" ResourceBackupSchedules = "backupschedules" ResourceBackupTemplates = "backuppolicytemplates" + ParametersAPIGroup = "parameters.kubeblocks.io" + ParametersAPIVersion = "v1alpha1" ) // Extensions API group From 1e64ef4b7d18c705c5a5648e3ec3e144c99c1806 Mon Sep 17 00:00:00 2001 From: sophon Date: Mon, 24 Mar 2025 17:51:58 +0800 Subject: [PATCH 5/6] fix lint --- pkg/cmd/cluster/config_wrapper.go | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/pkg/cmd/cluster/config_wrapper.go b/pkg/cmd/cluster/config_wrapper.go index f17f2535e..67765f86c 100644 --- a/pkg/cmd/cluster/config_wrapper.go +++ b/pkg/cmd/cluster/config_wrapper.go @@ -42,7 +42,6 @@ type ReconfigureContext struct { ConfigRender *parametersv1alpha1.ParamConfigRenderer ParametersDefs []*parametersv1alpha1.ParametersDefinition - Sharding bool CompName string } @@ -129,13 +128,12 @@ func generateReconfigureContext(ctx context.Context, clientSet versioned.Interfa componentName = defaultCompName(clusterObj.Spec) } - sharding, cmpd, err := resolveComponentDefObj(ctx, clientSet, clusterObj, componentName) + cmpd, err := resolveComponentDefObj(ctx, clientSet, clusterObj, componentName) if err != nil { return nil, err } rctx := &ReconfigureContext{ Context: ctx, - Sharding: sharding, Cmpd: cmpd, Cluster: clusterObj, Client: clientSet, @@ -148,7 +146,7 @@ func generateReconfigureContext(ctx context.Context, clientSet versioned.Interfa return rctx, nil } -func resolveComponentDefObj(ctx context.Context, client versioned.Interface, clusterObj *kbappsv1.Cluster, componentName string) (sharding bool, cmpd *kbappsv1.ComponentDefinition, err error) { +func resolveComponentDefObj(ctx context.Context, client versioned.Interface, clusterObj *kbappsv1.Cluster, componentName string) (*kbappsv1.ComponentDefinition, error) { resolveCmpd := func(cmpdName string) (*kbappsv1.ComponentDefinition, error) { if cmpdName == "" { return nil, errors.New("the referenced ComponentDefinition is empty") @@ -172,23 +170,17 @@ func resolveComponentDefObj(ctx context.Context, client versioned.Interface, clu compSpec := clusterObj.Spec.GetComponentByName(componentName) if compSpec != nil { - cmpd, err = resolveCmpd(compSpec.ComponentDef) - return + return resolveCmpd(compSpec.ComponentDef) } + shardingSpec := clusterObj.Spec.GetShardingByName(componentName) if shardingSpec == nil { - err = makeComponentNotExistErr(clusterObj.Name, componentName) - return + return nil, makeComponentNotExistErr(clusterObj.Name, componentName) } - - sharding = true if shardingSpec.ShardingDef != "" { - cmpd, err = resolveShardingCmpd(shardingSpec.ShardingDef) - return + return resolveShardingCmpd(shardingSpec.ShardingDef) } - - cmpd, err = resolveCmpd(shardingSpec.Template.ComponentDef) - return + return resolveCmpd(shardingSpec.Template.ComponentDef) } func resolveCmpdParametersDefs(rctx *ReconfigureContext) error { From 1be7fcb26aea7e66d03c32d14ecb1aa2e868faa5 Mon Sep 17 00:00:00 2001 From: sophon-zt Date: Mon, 24 Mar 2025 09:56:34 +0000 Subject: [PATCH 6/6] chore: auto update cli doc changes --- docs/user_docs/cli/cli.md | 1 - docs/user_docs/cli/kbcli_cluster.md | 1 - docs/user_docs/cli/kbcli_cluster_describe-config.md | 1 - 3 files changed, 3 deletions(-) diff --git a/docs/user_docs/cli/cli.md b/docs/user_docs/cli/cli.md index 77e3efc2c..80392637d 100644 --- a/docs/user_docs/cli/cli.md +++ b/docs/user_docs/cli/cli.md @@ -52,7 +52,6 @@ Cluster command. * [kbcli cluster describe-config](kbcli_cluster_describe-config.md) - Show details of a specific reconfiguring. * [kbcli cluster describe-ops](kbcli_cluster_describe-ops.md) - Show details of a specific OpsRequest. * [kbcli cluster describe-restore](kbcli_cluster_describe-restore.md) - Describe a restore -* [kbcli cluster diff-config](kbcli_cluster_diff-config.md) - Show the difference in parameters between the two submitted OpsRequest. * [kbcli cluster edit-backup-policy](kbcli_cluster_edit-backup-policy.md) - Edit backup policy * [kbcli cluster edit-config](kbcli_cluster_edit-config.md) - Edit the config file of the component. * [kbcli cluster explain-config](kbcli_cluster_explain-config.md) - List the constraint for supported configuration params. diff --git a/docs/user_docs/cli/kbcli_cluster.md b/docs/user_docs/cli/kbcli_cluster.md index a89b49262..217f15cfa 100644 --- a/docs/user_docs/cli/kbcli_cluster.md +++ b/docs/user_docs/cli/kbcli_cluster.md @@ -53,7 +53,6 @@ Cluster command. * [kbcli cluster describe-config](kbcli_cluster_describe-config.md) - Show details of a specific reconfiguring. * [kbcli cluster describe-ops](kbcli_cluster_describe-ops.md) - Show details of a specific OpsRequest. * [kbcli cluster describe-restore](kbcli_cluster_describe-restore.md) - Describe a restore -* [kbcli cluster diff-config](kbcli_cluster_diff-config.md) - Show the difference in parameters between the two submitted OpsRequest. * [kbcli cluster edit-backup-policy](kbcli_cluster_edit-backup-policy.md) - Edit backup policy * [kbcli cluster edit-config](kbcli_cluster_edit-config.md) - Edit the config file of the component. * [kbcli cluster explain-config](kbcli_cluster_explain-config.md) - List the constraint for supported configuration params. diff --git a/docs/user_docs/cli/kbcli_cluster_describe-config.md b/docs/user_docs/cli/kbcli_cluster_describe-config.md index a73bb87d4..8c9727483 100644 --- a/docs/user_docs/cli/kbcli_cluster_describe-config.md +++ b/docs/user_docs/cli/kbcli_cluster_describe-config.md @@ -31,7 +31,6 @@ kbcli cluster describe-config [flags] --config-file strings Specify the name of the configuration file to be describe (e.g. for mysql: --config-file=my.cnf). If unset, all files. --config-specs strings Specify the name of the configuration template to describe. (e.g. for apecloud-mysql: --config-specs=mysql-3node-tpl) -h, --help help for describe-config - --show-detail If true, the content of the files specified by config-file will be printed. ``` ### Options inherited from parent commands