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 := ¶meterSchema{ 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 }