github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/cluster/config_util.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package cluster
    21  
    22  import (
    23  	"bytes"
    24  	"encoding/json"
    25  	"fmt"
    26  	"io"
    27  	"path/filepath"
    28  	"sort"
    29  	"strings"
    30  
    31  	"github.com/spf13/cast"
    32  	corev1 "k8s.io/api/core/v1"
    33  	apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    34  	"k8s.io/kubectl/pkg/cmd/util/editor"
    35  	"sigs.k8s.io/controller-runtime/pkg/client"
    36  
    37  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    38  	"github.com/1aal/kubeblocks/pkg/cli/create"
    39  	"github.com/1aal/kubeblocks/pkg/cli/printer"
    40  	"github.com/1aal/kubeblocks/pkg/cli/types"
    41  	"github.com/1aal/kubeblocks/pkg/cli/util"
    42  	cfgcore "github.com/1aal/kubeblocks/pkg/configuration/core"
    43  	cfgutil "github.com/1aal/kubeblocks/pkg/configuration/util"
    44  )
    45  
    46  type configEditContext struct {
    47  	create.CreateOptions
    48  
    49  	clusterName    string
    50  	componentName  string
    51  	configSpecName string
    52  	configKey      string
    53  
    54  	original string
    55  	edited   string
    56  }
    57  
    58  type parameterSchema struct {
    59  	name        string
    60  	valueType   string
    61  	miniNum     string
    62  	maxiNum     string
    63  	enum        []string
    64  	description string
    65  	scope       string
    66  	dynamic     bool
    67  }
    68  
    69  func (c *configEditContext) getOriginal() string {
    70  	return c.original
    71  }
    72  
    73  func (c *configEditContext) getEdited() string {
    74  	return c.edited
    75  }
    76  
    77  func (c *configEditContext) prepare() error {
    78  	cmObj := corev1.ConfigMap{}
    79  	cmKey := client.ObjectKey{
    80  		Name:      cfgcore.GetComponentCfgName(c.clusterName, c.componentName, c.configSpecName),
    81  		Namespace: c.Namespace,
    82  	}
    83  	if err := util.GetResourceObjectFromGVR(types.ConfigmapGVR(), cmKey, c.Dynamic, &cmObj); err != nil {
    84  		return err
    85  	}
    86  
    87  	val, ok := cmObj.Data[c.configKey]
    88  	if !ok {
    89  		return makeNotFoundConfigFileErr(c.configKey, c.configSpecName, cfgutil.ToSet(cmObj.Data).AsSlice())
    90  	}
    91  
    92  	c.original = val
    93  	return nil
    94  }
    95  
    96  func (c *configEditContext) editConfig(editor editor.Editor, reader io.Reader) error {
    97  	if reader == nil {
    98  		reader = bytes.NewBufferString(c.original)
    99  	}
   100  	edited, _, err := editor.LaunchTempFile(fmt.Sprintf("%s-edit-", filepath.Base(c.configKey)), "", reader)
   101  	if err != nil {
   102  		return err
   103  	}
   104  
   105  	c.edited = string(edited)
   106  	return nil
   107  }
   108  
   109  func newConfigContext(baseOptions create.CreateOptions, clusterName, componentName, configSpec, file string) *configEditContext {
   110  	return &configEditContext{
   111  		CreateOptions:  baseOptions,
   112  		clusterName:    clusterName,
   113  		componentName:  componentName,
   114  		configSpecName: configSpec,
   115  		configKey:      file,
   116  	}
   117  }
   118  
   119  func fromKeyValuesToMap(params []cfgcore.VisualizedParam, file string) map[string]*string {
   120  	result := make(map[string]*string)
   121  	for _, param := range params {
   122  		if param.Key != file {
   123  			continue
   124  		}
   125  		for _, kv := range param.Parameters {
   126  			result[kv.Key] = kv.Value
   127  		}
   128  	}
   129  	return result
   130  }
   131  
   132  func (pt *parameterSchema) enumFormatter(maxFieldLength int) string {
   133  	if len(pt.enum) == 0 {
   134  		return ""
   135  	}
   136  	v := strings.Join(pt.enum, ",")
   137  	if maxFieldLength > 0 && len(v) > maxFieldLength {
   138  		v = v[:maxFieldLength] + "..."
   139  	}
   140  	return v
   141  }
   142  
   143  func (pt *parameterSchema) rangeFormatter() string {
   144  	const (
   145  		r          = "-"
   146  		rangeBegin = "["
   147  		rangeEnd   = "]"
   148  	)
   149  
   150  	if len(pt.maxiNum) == 0 && len(pt.miniNum) == 0 {
   151  		return ""
   152  	}
   153  
   154  	v := rangeBegin
   155  	if len(pt.miniNum) != 0 {
   156  		v += pt.miniNum
   157  	}
   158  	if len(pt.maxiNum) != 0 {
   159  		v += r
   160  		v += pt.maxiNum
   161  	} else if len(v) != 0 {
   162  		v += r
   163  	}
   164  	v += rangeEnd
   165  	return v
   166  }
   167  
   168  func getAllowedValues(pt *parameterSchema, maxFieldLength int) string {
   169  	if len(pt.enum) != 0 {
   170  		return pt.enumFormatter(maxFieldLength)
   171  	}
   172  	return pt.rangeFormatter()
   173  }
   174  
   175  func printSingleParameterSchema(pt *parameterSchema) {
   176  	printer.PrintTitle("Configure Constraint")
   177  	// print column "PARAMETER NAME", "RANGE", "ENUM", "SCOPE", "TYPE", "DESCRIPTION"
   178  	printer.PrintPairStringToLine("Parameter Name", pt.name)
   179  	printer.PrintPairStringToLine("Allowed Values", getAllowedValues(pt, -1))
   180  	printer.PrintPairStringToLine("Scope", pt.scope)
   181  	printer.PrintPairStringToLine("Dynamic", cast.ToString(pt.dynamic))
   182  	printer.PrintPairStringToLine("Type", pt.valueType)
   183  	printer.PrintPairStringToLine("Description", pt.description)
   184  }
   185  
   186  // printConfigParameterSchema prints the conditions of resource.
   187  func printConfigParameterSchema(paramTemplates []*parameterSchema, out io.Writer, maxFieldLength int) {
   188  	if len(paramTemplates) == 0 {
   189  		return
   190  	}
   191  
   192  	sort.SliceStable(paramTemplates, func(i, j int) bool {
   193  		x1 := paramTemplates[i]
   194  		x2 := paramTemplates[j]
   195  		return strings.Compare(x1.name, x2.name) < 0
   196  	})
   197  
   198  	tbl := printer.NewTablePrinter(out)
   199  	tbl.SetStyle(printer.TerminalStyle)
   200  	printer.PrintTitle("Parameter Explain")
   201  	tbl.SetHeader("PARAMETER NAME", "ALLOWED VALUES", "SCOPE", "DYNAMIC", "TYPE", "DESCRIPTION")
   202  	for _, pt := range paramTemplates {
   203  		tbl.AddRow(pt.name, getAllowedValues(pt, maxFieldLength), pt.scope, cast.ToString(pt.dynamic), pt.valueType, pt.description)
   204  	}
   205  	tbl.Print()
   206  }
   207  
   208  func generateParameterSchema(paramName string, property apiext.JSONSchemaProps) (*parameterSchema, error) {
   209  	toString := func(v interface{}) (string, error) {
   210  		b, err := json.Marshal(v)
   211  		if err != nil {
   212  			return "", err
   213  		}
   214  		return string(b), nil
   215  	}
   216  	pt := &parameterSchema{
   217  		name:        paramName,
   218  		valueType:   property.Type,
   219  		description: strings.TrimSpace(property.Description),
   220  	}
   221  	if property.Minimum != nil {
   222  		b, err := toString(property.Minimum)
   223  		if err != nil {
   224  			return nil, err
   225  		}
   226  		pt.miniNum = b
   227  	}
   228  	if property.Format != "" {
   229  		pt.valueType = property.Format
   230  	}
   231  	if property.Maximum != nil {
   232  		b, err := toString(property.Maximum)
   233  		if err != nil {
   234  			return nil, err
   235  		}
   236  		pt.maxiNum = b
   237  	}
   238  	if property.Enum != nil {
   239  		pt.enum = make([]string, len(property.Enum))
   240  		for i, v := range property.Enum {
   241  			b, err := toString(v)
   242  			if err != nil {
   243  				return nil, err
   244  			}
   245  			pt.enum[i] = b
   246  		}
   247  	}
   248  	return pt, nil
   249  }
   250  
   251  func getComponentNames(cluster *appsv1alpha1.Cluster) []string {
   252  	var components []string
   253  	for _, component := range cluster.Spec.ComponentSpecs {
   254  		components = append(components, component.Name)
   255  	}
   256  	return components
   257  }
   258  
   259  func findTplByName(tpls []appsv1alpha1.ComponentConfigSpec, tplName string) *appsv1alpha1.ComponentConfigSpec {
   260  	for i := range tpls {
   261  		tpl := &tpls[i]
   262  		if tpl.Name == tplName {
   263  			return tpl
   264  		}
   265  	}
   266  	return nil
   267  }