github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/cmd/reloader/tools/main.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 main 21 22 import ( 23 "bufio" 24 "bytes" 25 "flag" 26 "fmt" 27 "io" 28 "os" 29 "reflect" 30 "regexp" 31 "strconv" 32 "strings" 33 34 cfgcore "github.com/1aal/kubeblocks/pkg/configuration/core" 35 ) 36 37 const ( 38 FileNotExist = -1 39 FileReadError = -2 40 41 RecordFieldCount = 8 42 RecordMinField = 7 43 44 NameField = 0 45 DefaultValueField = 1 46 ValueRestrictField = 2 47 ImmutableField = 3 48 ValueTypeField = 4 49 ChangeTypeField = 5 50 DocField = 6 51 ) 52 53 var ( 54 prefixString = "" 55 filePath = "" 56 typeName = "MyParameter" 57 ignoreStringDefault = true 58 booleanPromotion = false 59 ) 60 61 type ValueType string 62 63 const ( 64 BooleanType = "boolean" 65 IntegerType = "integer" 66 FloatType = "float" 67 StringType = "string" 68 ListType = "list" // for string 69 ) 70 71 type ValueParser func(s string) (interface{}, error) 72 73 func EmptyParser(s string) (interface{}, error) { 74 return s, nil 75 } 76 77 var numberRegex = regexp.MustCompile(`^\d+$`) 78 79 var ValueTypeParserMap = map[ValueType]ValueParser{ 80 BooleanType: func(s string) (interface{}, error) { 81 if booleanPromotion && numberRegex.MatchString(s) { 82 return nil, cfgcore.MakeError("boolean parser failed") 83 } 84 return strconv.ParseBool(s) 85 }, 86 IntegerType: func(s string) (interface{}, error) { 87 if v, err := strconv.ParseInt(s, 10, 64); err == nil { 88 return v, nil 89 } 90 return strconv.ParseUint(s, 10, 64) 91 }, 92 FloatType: func(s string) (interface{}, error) { 93 return strconv.ParseFloat(s, 64) 94 }, 95 StringType: EmptyParser, 96 ListType: EmptyParser, 97 } 98 99 func main() { 100 // The source file format is one line per parameter, the fields are separated by tabs, and the fields are as follows: 101 // parameter name | default value | value restriction | is immutable(true/false) | value type(boolean/integer/string) | change type(static/dynamic) | description 102 // file format example: 103 // default_authentication_plugin\tmysql_native_password\tmysql_native_password, sha256_password, caching_sha2_password\tfalse\string\tstatic\tThe default authentication plugin 104 105 flag.StringVar(&filePath, "file-path", "", "The source file path for generating cue template.") 106 flag.StringVar(&prefixString, "output-prefix", prefixString, "prefix, default: \"\"") 107 flag.StringVar(&typeName, "type-name", typeName, "cue parameter type name.") 108 flag.BoolVar(&ignoreStringDefault, "ignore-string-default", ignoreStringDefault, "ignore string default. ") 109 flag.BoolVar(&booleanPromotion, "boolean-promotion", booleanPromotion, "enable using OFF or ON. ") 110 flag.Parse() 111 112 f, err := os.Open(filePath) 113 if err != nil { 114 fmt.Printf("open file[%s] failed. error: %v", filePath, err) 115 os.Exit(FileNotExist) 116 } 117 118 writer := os.Stdout 119 scanner := bufio.NewScanner(f) 120 wrapOutputTypeDefineBegin(typeName, writer) 121 for scanner.Scan() { 122 if err := scanner.Err(); err != nil { 123 fmt.Printf("readline failed. error: %v", err) 124 os.Exit(FileReadError) 125 } 126 fields := strings.SplitN(scanner.Text(), "\t", RecordFieldCount) 127 if len(fields) < RecordMinField { 128 continue 129 } 130 wrapOutputCueLang(ConstructParameterType(fields), writer) 131 } 132 wrapOutputTypeDefineEnd(writer) 133 } 134 135 func wrapOutputTypeDefineEnd(writer io.Writer) int { 136 r, _ := writer.Write([]byte(fmt.Sprintf("\n %s...\n%s}", prefixString, prefixString))) 137 return r 138 } 139 140 func wrapOutputTypeDefineBegin(typeName string, writer io.Writer) int { 141 r, _ := writer.Write([]byte(fmt.Sprintf("%s#%s: {\n\n", prefixString, typeName))) 142 return r 143 } 144 145 func wrapOutputCueLang(parameter *ParameterType, writer io.Writer) { 146 if !validateParameter(parameter) { 147 return 148 } 149 150 wrapper := CueWrapper{ 151 writer: writer, 152 ParameterType: parameter, 153 } 154 155 wrapper.output() 156 } 157 158 func validateParameter(parameter *ParameterType) bool { 159 return parameter != nil && 160 parameter.Name != "" && 161 parameter.Type != "" 162 } 163 164 func ConstructParameterType(fields []string) *ParameterType { 165 // type promotion 166 types := []ValueType{BooleanType, IntegerType, StringType} 167 param := ParameterType{ 168 Name: fields[NameField], 169 Type: ValueType(fields[ValueTypeField]), 170 DefaultValue: strings.TrimSpace(fields[DefaultValueField]), 171 IsStatic: true, 172 Immutable: false, 173 } 174 175 if len(fields) > RecordMinField { 176 param.Document = fields[DocField] 177 } 178 if r, err := strconv.ParseBool(fields[ImmutableField]); err == nil { 179 param.Immutable = r 180 } 181 if fields[ChangeTypeField] == "dynamic" { 182 param.IsStatic = false 183 } 184 if param.Type == ListType { 185 param.Type = StringType 186 } 187 checkAndUpdateDefaultValue(¶m) 188 189 if param.Type != BooleanType { 190 pr, _ := parseParameterRestrict(fields[ValueRestrictField], param.Type) 191 param.ParameterRestrict = pr 192 return ¶m 193 } 194 195 for _, vt := range types { 196 pr, err := parseParameterRestrict(fields[ValueRestrictField], vt) 197 if err != nil { 198 continue 199 } 200 if vt == IntegerType && booleanPromotion && pr.isEnum { 201 fields[ValueRestrictField] += ", OFF, ON" 202 continue 203 } 204 param.Type = vt 205 param.ParameterRestrict = pr 206 break 207 } 208 return ¶m 209 } 210 211 func checkAndUpdateDefaultValue(param *ParameterType) { 212 var ( 213 defaultValue = param.DefaultValue 214 valueType = param.Type 215 ) 216 217 formatString := func(v interface{}) string { 218 return fmt.Sprintf("%v", v) 219 } 220 221 if defaultValue == "" { 222 return 223 } 224 if defaultValue[0] == '{' { 225 param.DefaultValue = "" 226 } 227 228 switch valueType { 229 case BooleanType: 230 checkAndUpdateBoolDefaultValue(param, formatString) 231 case StringType: 232 if ignoreStringDefault { 233 param.DefaultValue = "" 234 } 235 case IntegerType, FloatType: 236 if v, err := ValueTypeParserMap[param.Type](param.DefaultValue); err != nil || formatString(v) != defaultValue { 237 param.DefaultValue = "" 238 } 239 } 240 } 241 242 func checkAndUpdateBoolDefaultValue(param *ParameterType, formatString func(v interface{}) string) { 243 if booleanPromotion { 244 return 245 } 246 247 v, err := ValueTypeParserMap[BooleanType](param.DefaultValue) 248 if err != nil { 249 param.DefaultValue = "" 250 return 251 } 252 param.DefaultValue = formatString(v) 253 } 254 255 type ParameterRestrict struct { 256 isEnum bool 257 258 Min interface{} 259 Max interface{} 260 261 EnumList []interface{} 262 } 263 264 type ParameterType struct { 265 Name string 266 Type ValueType 267 DefaultValue string 268 IsStatic bool 269 270 Immutable bool 271 ParameterRestrict *ParameterRestrict 272 Document string 273 } 274 275 func generateRestrictParam(buffer *bytes.Buffer, restrict *ParameterRestrict, valueType ValueType) { 276 buffer.WriteString(" & ") 277 if restrict.isEnum { 278 for i := 0; i < len(restrict.EnumList); i++ { 279 if i > 0 { 280 buffer.WriteString(" | ") 281 } 282 generateElemValue(buffer, restrict.EnumList[i], valueType) 283 } 284 } else { 285 buffer.WriteString(">= ") 286 generateElemValue(buffer, restrict.Min, valueType) 287 buffer.WriteString(" & <= ") 288 generateElemValue(buffer, restrict.Max, valueType) 289 } 290 } 291 292 func generateElemValue(buffer *bytes.Buffer, value interface{}, valueType ValueType) { 293 if valueType == StringType { 294 buffer.WriteString("\"") 295 } 296 buffer.WriteString(fmt.Sprintf("%v", value)) 297 if valueType == StringType { 298 buffer.WriteString("\"") 299 } 300 } 301 302 func parseValue(s string, valueType ValueType) (interface{}, error) { 303 v, err := ValueTypeParserMap[valueType](s) 304 if err != nil { 305 return nil, cfgcore.MakeError("parse type[%s] value[%s] failed!", valueType, s) 306 } 307 return v, nil 308 } 309 310 func parseParameterRestrict(s string, valueType ValueType) (*ParameterRestrict, error) { 311 var ( 312 IntegerRangeRegex = regexp.MustCompile(`([\+\-]?\d+)-([\+\-]?\d+)`) 313 // support format: 0-1.79769e+308 314 FloatRangeRegex = regexp.MustCompile(`([\+\-]?\d+(\.\d*(e[\+\-]\d+))?)-([\+\-]?\d+(\.\d*(e[\+\-]\d+))?)`) 315 316 pr *ParameterRestrict 317 err error 318 ) 319 320 setValueHelper := func(rv reflect.Value, s string, valueType ValueType) error { 321 if rv.Kind() != reflect.Pointer || rv.IsNil() { 322 return cfgcore.MakeError("invalid return type") 323 } 324 325 value, err := parseValue(s, valueType) 326 if err != nil { 327 return err 328 } 329 reflect.Indirect(rv).Set(reflect.Indirect(reflect.ValueOf(value))) 330 return nil 331 } 332 integerTypeHandle := func(s string) (*ParameterRestrict, error) { 333 r := IntegerRangeRegex.FindStringSubmatch(s) 334 if len(r) == 0 { 335 return nil, nil 336 } 337 t := &ParameterRestrict{isEnum: false} 338 if err := setValueHelper(reflect.ValueOf(&t.Min), r[1], valueType); err != nil { 339 return nil, err 340 } 341 if err := setValueHelper(reflect.ValueOf(&t.Max), r[2], valueType); err != nil { 342 return nil, err 343 } 344 return t, nil 345 } 346 floatTypeHandle := func(s string) (*ParameterRestrict, error) { 347 r := FloatRangeRegex.FindStringSubmatch(s) 348 if len(r) == 0 { 349 return nil, nil 350 } 351 t := &ParameterRestrict{isEnum: false} 352 if err := setValueHelper(reflect.ValueOf(&t.Min), r[1], valueType); err != nil { 353 return nil, err 354 } 355 if err := setValueHelper(reflect.ValueOf(&t.Max), r[4], valueType); err != nil { 356 return nil, err 357 } 358 return t, nil 359 } 360 361 if s == "" { 362 return nil, nil 363 } 364 365 switch valueType { 366 case IntegerType: 367 pr, err = integerTypeHandle(s) 368 case FloatType: 369 pr, err = floatTypeHandle(s) 370 } 371 372 if err != nil { 373 return nil, err 374 } 375 if pr != nil { 376 return pr, nil 377 } 378 379 return parseListParameter(s, valueType) 380 } 381 382 func parseListParameter(s string, valueType ValueType) (*ParameterRestrict, error) { 383 values := strings.Split(s, ",") 384 if len(values) == 0 { 385 return nil, nil 386 } 387 388 p := &ParameterRestrict{isEnum: true} 389 for _, v := range values { 390 v = strings.TrimSpace(v) 391 if len(v) == 0 { 392 continue 393 } 394 if typeValue, err := ValueTypeParserMap[valueType](v); err != nil { 395 return nil, cfgcore.WrapError(err, "parse failed: [%s] [%s] [%s]\n", s, v, valueType) 396 } else { 397 AddRestrictValue(p, typeValue) 398 } 399 } 400 if len(p.EnumList) == 0 { 401 return nil, nil 402 } 403 return p, nil 404 } 405 406 func AddRestrictValue(p *ParameterRestrict, value interface{}) { 407 if p.EnumList == nil { 408 p.EnumList = make([]interface{}, 0) 409 } 410 411 p.EnumList = append(p.EnumList, value) 412 } 413 414 type CueWrapper struct { 415 writer io.Writer 416 *ParameterType 417 } 418 419 func (w *CueWrapper) output() int { 420 var buffer bytes.Buffer 421 w.generateCueDocument(&buffer) 422 w.generateCueTypeParameter(&buffer) 423 w.generateCueRestrict(&buffer) 424 w.generateCueDefaultValue(&buffer) 425 426 _ = buffer.WriteByte('\n') 427 _ = buffer.WriteByte('\n') 428 b, _ := w.writer.Write(buffer.Bytes()) 429 return b 430 } 431 432 func (w *CueWrapper) generateCueDefaultValue(buffer *bytes.Buffer) { 433 if w.DefaultValue == "" { 434 return 435 } 436 buffer.WriteString(" | *") 437 generateElemValue(buffer, w.DefaultValue, w.Type) 438 } 439 440 func (w *CueWrapper) generateCueRestrict(buffer *bytes.Buffer) { 441 if w.ParameterRestrict != nil { 442 generateRestrictParam(buffer, w.ParameterRestrict, w.Type) 443 } 444 } 445 446 func (w *CueWrapper) generateCueTypeParameter(buffer *bytes.Buffer) { 447 buffer.WriteString(prefixString + " ") 448 if strings.ContainsAny(w.Name, "-.") { 449 buffer.WriteByte('"') 450 buffer.WriteString(w.Name) 451 buffer.WriteByte('"') 452 } else { 453 buffer.WriteString(w.Name) 454 } 455 456 if w.DefaultValue == "" { 457 buffer.WriteByte('?') 458 } 459 buffer.WriteString(": ") 460 switch w.Type { 461 case IntegerType: 462 buffer.WriteString("int") 463 case BooleanType: 464 buffer.WriteString("bool") 465 default: 466 buffer.WriteString(string(w.Type)) 467 } 468 } 469 470 func (w *CueWrapper) generateCueDocument(buffer *bytes.Buffer) { 471 if w.Document != "" { 472 buffer.WriteString(prefixString + " ") 473 buffer.WriteString("// ") 474 buffer.WriteString(w.Document) 475 buffer.WriteByte('\n') 476 } 477 }