From 6ab857686a2790cb94d1d34f44c47d13204cc6a7 Mon Sep 17 00:00:00 2001 From: HT0403 Date: Mon, 29 Apr 2024 20:13:38 +0800 Subject: [PATCH] These changes can be used as an version to enhance keadm tool to achieve full parameter configuration by using --set Signed-off-by: HT0403 --- keadm/cmd/keadm/app/cmd/common/types.go | 1 + keadm/cmd/keadm/app/cmd/edge/join_others.go | 8 + keadm/cmd/keadm/app/cmd/edge/join_windows.go | 9 + keadm/cmd/keadm/app/cmd/util/set.go | 498 +++++++++++++++++++ keadm/cmd/keadm/app/cmd/util/set_test.go | 464 +++++++++++++++++ 5 files changed, 980 insertions(+) create mode 100644 keadm/cmd/keadm/app/cmd/util/set.go create mode 100644 keadm/cmd/keadm/app/cmd/util/set_test.go diff --git a/keadm/cmd/keadm/app/cmd/common/types.go b/keadm/cmd/keadm/app/cmd/common/types.go index 20bb2024200..ec9e6da852f 100644 --- a/keadm/cmd/keadm/app/cmd/common/types.go +++ b/keadm/cmd/keadm/app/cmd/common/types.go @@ -93,6 +93,7 @@ type JoinOptions struct { CertPort string CGroupDriver string Labels []string + Sets string // WithMQTT ... // Deprecated: the mqtt broker is alreay managed by the DaemonSet in the cloud diff --git a/keadm/cmd/keadm/app/cmd/edge/join_others.go b/keadm/cmd/keadm/app/cmd/edge/join_others.go index 9ad778c714c..493b2a5a93c 100644 --- a/keadm/cmd/keadm/app/cmd/edge/join_others.go +++ b/keadm/cmd/keadm/app/cmd/edge/join_others.go @@ -92,6 +92,9 @@ func AddJoinOtherFlags(cmd *cobra.Command, joinOptions *common.JoinOptions) { cmd.Flags().StringVar(&joinOptions.HubProtocol, common.HubProtocol, joinOptions.HubProtocol, `Use this key to decide which communication protocol the edge node adopts.`) + + cmd.Flags().StringVar(&joinOptions.Sets, common.FlagNameSet, joinOptions.Sets, + `Set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)`) } func createEdgeConfigFiles(opt *common.JoinOptions) error { @@ -165,6 +168,11 @@ func createEdgeConfigFiles(opt *common.JoinOptions) error { if len(opt.Labels) > 0 { edgeCoreConfig.Modules.Edged.NodeLabels = setEdgedNodeLabels(opt) } + if len(opt.Sets) > 0 { + if err := util.ParseSet(edgeCoreConfig, opt.Sets); err != nil { + return err + } + } if errs := validation.ValidateEdgeCoreConfiguration(edgeCoreConfig); len(errs) > 0 { return errors.New(pkgutil.SpliceErrors(errs.ToAggregate().Errors())) diff --git a/keadm/cmd/keadm/app/cmd/edge/join_windows.go b/keadm/cmd/keadm/app/cmd/edge/join_windows.go index fdcff3d6984..054e0ffe46b 100644 --- a/keadm/cmd/keadm/app/cmd/edge/join_windows.go +++ b/keadm/cmd/keadm/app/cmd/edge/join_windows.go @@ -80,6 +80,9 @@ func AddJoinOtherFlags(cmd *cobra.Command, joinOptions *common.JoinOptions) { cmd.Flags().StringVar(&joinOptions.HubProtocol, common.HubProtocol, joinOptions.HubProtocol, `Use this key to decide which communication protocol the edge node adopts.`) + + cmd.Flags().StringVar(&joinOptions.Sets, common.FlagNameSet, joinOptions.Sets, + `Set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)`) } func createEdgeConfigFiles(opt *common.JoinOptions) error { @@ -153,6 +156,12 @@ func createEdgeConfigFiles(opt *common.JoinOptions) error { edgeCoreConfig.Modules.Edged.NodeLabels = setEdgedNodeLabels(opt) } + if len(opt.Sets) > 0 { + if err := util.ParseSet(edgeCoreConfig, opt.Sets); err != nil { + return err + } + } + if errs := validation.ValidateEdgeCoreConfiguration(edgeCoreConfig); len(errs) > 0 { return errors.New(pkgutil.SpliceErrors(errs.ToAggregate().Errors())) } diff --git a/keadm/cmd/keadm/app/cmd/util/set.go b/keadm/cmd/keadm/app/cmd/util/set.go new file mode 100644 index 00000000000..82c55dc8c0e --- /dev/null +++ b/keadm/cmd/keadm/app/cmd/util/set.go @@ -0,0 +1,498 @@ +/* +Copyright 2024 The KubeEdge Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "strings" + "unicode" +) + +// ParseSetByCommma splits a set line according to the comma. +// +// A set line is of the form "hello, world" or {a, b, c} +func parseSetByComma(set string) []string { + var vals []string + var buffer strings.Builder + var inQuotes, inBraces bool + + for _, char := range set { + switch { + case char == ',' && !inBraces && !inQuotes: + val := buffer.String() + if val != "" { + vals = append(vals, val) + } + buffer.Reset() + case char == '{' && !inQuotes: + inBraces = true + buffer.WriteRune(char) + case char == '}' && !inQuotes: + inBraces = false + buffer.WriteRune(char) + case char == '"': + inQuotes = !inQuotes + buffer.WriteRune(char) + case !unicode.IsSpace(char) || inQuotes || inBraces: + buffer.WriteRune(char) + } + } + if buffer.Len() > 0 { + vals = append(vals, buffer.String()) + } + + return vals +} + +// ParseSetByEqual splits a set line according to the equal. +// +// A set line is of the form ["name1=value1","name2=value2"] +func parseSetByEqual(set []string) ([]string, []string) { + var names []string + var vals []string + for _, s := range set { + parts := strings.Split(s, "=") + if len(parts) != 2 { + continue + } + names = append(names, parts[0]) + vals = append(vals, parts[1]) + } + return names, vals +} + +// ParseSetValue parses the value in the splited set line. +// The type of value must be interpreted by int, float64, string and array. +func parseSetValue(vals []string) []interface{} { + parsedvals := make([]interface{}, len(vals)) + for i, s := range vals { + parsedvals[i] = parseValue(s) + } + return parsedvals +} + +// ParseValue parses the value and interprets it to int, float64, string and array. +// The representation of {value} will be interpreted by array. +func parseValue(s string) interface{} { + if strings.HasPrefix(s, "{") && strings.HasSuffix(s, "}") { + if strings.Contains(s, ":") { + return parseMap(s) + } + return parseArray(s) + } + + if intValue, err := strconv.Atoi(s); err == nil { + return intValue + } + if floatValue, err := strconv.ParseFloat(s, 64); err == nil { + return floatValue + } + if boolvalue, err := strconv.ParseBool(s); err == nil { + return boolvalue + } + return s +} + +// ParseMap parses the value of map. +func parseMap(s string) interface{} { + s = s[1 : len(s)-1] + keyValuePairs := strings.Split(s, ",") + keyValue := strings.Split(keyValuePairs[0], ":") + myMap := makeMap(reflect.TypeOf(parseValue(keyValue[0])), reflect.TypeOf(parseValue(keyValue[1]))) + for _, pair := range keyValuePairs { + keyValue := strings.Split(pair, ":") + parseKey := parseValue(keyValue[0]) + parseVal := parseValue(keyValue[1]) + setValue(myMap, parseKey, parseVal) + } + return myMap +} + +// use reflect build map +func makeMap(keyType, valueType reflect.Type) interface{} { + mapType := reflect.MapOf(keyType, valueType) + return reflect.MakeMap(mapType).Interface() +} + +// append into map +func setValue(m interface{}, key interface{}, value interface{}) { + v := reflect.ValueOf(m) + k := reflect.ValueOf(key) + vv := reflect.ValueOf(value) + v.SetMapIndex(k, vv) +} + +// ParseArray parses the value of array. +func parseArray(s string) interface{} { + s = s[1 : len(s)-1] + vals := strings.Split(s, ",") + switch parseType(vals[0]) { + case "int": + intArray := make([]int, len(vals)) + for i, v := range vals { + v = strings.TrimSpace(v) + intValue, _ := strconv.Atoi(v) + intArray[i] = intValue + } + return intArray + case "float": + floatArray := make([]float64, len(vals)) + for i, v := range vals { + v = strings.TrimSpace(v) + floatValue, _ := strconv.ParseFloat(v, 64) + floatArray[i] = floatValue + } + return floatArray + default: + stringArray := make([]string, len(vals)) + for i, v := range vals { + stringArray[i] = strings.TrimSpace(v) + } + return stringArray + } +} + +// ParseType parses the type of array and interprets it to int, float64, string. +func parseType(s string) string { + // Check if it's an integer + if _, err := strconv.Atoi(s); err == nil { + return "int" + } + // Check if it's a float + if _, err := strconv.ParseFloat(s, 64); err == nil { + return "float" + } + // Check if it's a bool + if _, err := strconv.ParseBool(s); err == nil { + return "bool" + } + // Otherwise, it's a string + return "string" +} + +// GetNameFormStatus judges the types of names. +// The name has three forms: +// +// 1)name1 = value1, return 0 +// 2)name1[0].variable1 = value1, return 1 +// 3)name1[0] = value1, return 2 +func getNameFormStatus(s string) int { + if !strings.Contains(s, "[") && !strings.Contains(s, "]") { + return 0 + } + closeBracketIndex := strings.Index(s, "]") + if closeBracketIndex != (len(s) - 1) { + return 1 + } + if closeBracketIndex == (len(s) - 1) { + return 2 + } + return -1 +} + +// SetCommonValue modifies the new value of the name in the config represented by struct. +// The type of new value may be int, float, string, splic. +// The name is represented by name1.name2.(...).nameM. +func setCommonValue(structPtr interface{}, fieldPath string, value interface{}) error { + structVal := reflect.ValueOf(structPtr).Elem() + + path := strings.Split(fieldPath, ".") + var fieldVal reflect.Value + fieldVal = structVal + for _, p := range path { + fieldVal = getFieldValue(fieldVal, p) + if !fieldVal.IsValid() { + return fmt.Errorf("%s: No such field: %s in Config", fieldPath, p) + } + } + + val := reflect.ValueOf(value) + + if fieldVal.Type() != val.Type() { + return fmt.Errorf("%s: Provided value type %s does not match field type %s", fieldPath, val.Type(), fieldVal.Type()) + } + fieldVal.Set(val) + + return nil +} + +// GetFieldValue gets the fieldname of the struct. +func getFieldValue(v reflect.Value, fieldName string) reflect.Value { + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + + f := v.FieldByName(fieldName) + if !f.IsValid() { + return reflect.Value{} + } + return f +} + +// ParseAndSetArrayValue combines the function of SetArrayValue and ParseFieldPath1. +func parseAndSetArrayValue(structPtr interface{}, fieldPath string, newValue interface{}) error { + path, index, _ := parseFieldPath1(fieldPath) + err := setArrayValue(structPtr, path, index, newValue) + return err +} + +// SetArrayValue modifies the new value of the name in the config represented by struct. +// The type of new value may be int, float, string. +// The name is represented by name1.name2.(...).nameM[N]. +func setArrayValue(structPtr interface{}, fieldPath string, index int, newValue interface{}) error { + v := reflect.ValueOf(structPtr).Elem() + + pathParts := strings.Split(fieldPath, ".") + var fieldVal reflect.Value + fieldVal = v + for _, part := range pathParts { + fieldVal = getFieldValue(fieldVal, part) + if !fieldVal.IsValid() { + return fmt.Errorf("%s: No such field: %s in Config", fieldPath, part) + } + } + if fieldVal.Kind() != reflect.Array && fieldVal.Kind() != reflect.Slice && fieldVal.Kind() != reflect.Map { + return fmt.Errorf("%s is not an array,slice and map", pathParts[len(pathParts)-1]) + } + // If it is an array or slice, you need to check whether the index is within the range + if index < 0 || index >= fieldVal.Len() { + return fmt.Errorf("index out of range for %s", pathParts[len(pathParts)-1]) + } + + //Extend array or slice length + if index >= fieldVal.Len() { + newSlice := reflect.MakeSlice(fieldVal.Type(), index+1, index+1) + reflect.Copy(newSlice, fieldVal) + fieldVal.Set(newSlice) + } + //Set new value + elem := reflect.ValueOf(newValue) + if elem.Type() != fieldVal.Type().Elem() { + return fmt.Errorf("type mismatch for field %s", pathParts[len(pathParts)-1]) + } + fieldVal.Index(index).Set(elem) + return nil +} + +// ParseFieldPath1 parses the names in form of "name1.name2.(...).nameM[N]". +// path : name1.name2.(...).nameM, index : N +func parseFieldPath1(fieldPath string) (string, int, error) { + parts := strings.Split(fieldPath, "[") + if len(parts) != 2 { + return "", -1, errors.New("invalid field path") + } + + path := parts[0] + indexStr := strings.TrimSuffix(parts[1], "]") + index, err := strconv.Atoi(indexStr) + if err != nil { + return "", -1, fmt.Errorf("invalid index: %v", err) + } + + return path, index, errors.New("") +} + +// SetVariableValue modifies the new value of the name in the config represented by struct. +// The type of new value may be int, float, string. +// The name is represented by name1.name2.(...).nameM[N].variable1. +func setVariableValue(obj interface{}, fieldPath string, value interface{}) error { + objValue := reflect.ValueOf(obj) + if objValue.Kind() != reflect.Ptr { + return errors.New("obj must be a pointer") + } + objValue = objValue.Elem() + fieldNames := parseFieldPath(fieldPath) + + for i, fieldName := range fieldNames[:len(fieldNames)-1] { + switch objValue.Kind() { + case reflect.Struct: + objValue = objValue.FieldByName(fieldName) + case reflect.Slice: + index, err := getIndex(fieldName) + if err != nil { + return err + } + if index < 0 || index >= objValue.Len() { + return fmt.Errorf("index out of range for field %s", fieldName) + } + objValue = objValue.Index(index) + case reflect.Ptr: + if objValue.IsNil() { + objValue.Set(reflect.New(objValue.Type().Elem())) + } + objValue = objValue.Elem() + default: + return fmt.Errorf("field %s is neither a struct nor a slice nor a pointer", fieldName) + } + if objValue.Kind() == reflect.Ptr && objValue.Elem().Kind() == reflect.Struct { + objValue = objValue.Elem() + } + if i == len(fieldNames)-2 && objValue.Kind() != reflect.Struct { + return fmt.Errorf("field %s is not a struct", fieldNames[i]) + } + } + + targetFieldName := fieldNames[len(fieldNames)-1] + if objValue.Kind() == reflect.Ptr { + if objValue.IsNil() { + objValue.Set(reflect.New(objValue.Type().Elem())) + } + objValue = objValue.Elem() + } + targetFieldValue := objValue.FieldByName(targetFieldName) + if !targetFieldValue.IsValid() { + return fmt.Errorf("field %s not found", targetFieldName) + } + if !targetFieldValue.CanSet() { + return fmt.Errorf("field %s cannot be set", targetFieldName) + } + + valueToSet := reflect.ValueOf(value) + if !valueToSet.Type().AssignableTo(targetFieldValue.Type()) { + return fmt.Errorf("value type %s is not assignable to field %s type %s", valueToSet.Type(), targetFieldName, targetFieldValue.Type()) + } + + targetFieldValue.Set(valueToSet) + + return nil +} + +// Split fieldPath by "." and "[" to separate fields and indexes +func parseFieldPath(fieldPath string) []string { + fields := strings.FieldsFunc(fieldPath, func(r rune) bool { + return r == '.' || r == '[' || r == ']' + }) + + // Remove empty fields and trim brackets from indexes + var cleanedFields []string + for _, f := range fields { + if f != "" { + cleanedFields = append(cleanedFields, strings.Trim(f, "[]")) + } + } + + return cleanedFields +} + +func getIndex(field string) (int, error) { + index, err := strconv.Atoi(field) + if err != nil { + return -1, fmt.Errorf("invalid index: %s", field) + } + return index, nil +} + +func findFieldByTag(obj interface{}, k int, tagName []string, fieldNames []string) { + if k == len(tagName) { + return + } + v := reflect.ValueOf(obj) + t := v.Type() + if v.Kind() == reflect.Ptr { + v = v.Elem() + t = t.Elem() + } + + if isFirstLetterUpper(tagName[k]) { + fieldNames[k] = tagName[k] + findFieldByTag(v.FieldByName(tagName[k]).Interface(), k+1, tagName, fieldNames) + } + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + tagValue := field.Tag.Get("json") + if !isFirstLetterUpper(tagName[k]) && tagValue == "" && i >= t.NumField() { + upperTagName := upperFirstLetter(tagName[k]) + fieldNames[k] = upperTagName + findFieldByTag(v.FieldByName(upperTagName).Interface(), k+1, tagName, fieldNames) + } + tagVals := strings.Split(tagValue, ",") + if tagVals[0] == tagName[k] { + fieldNames[k] = field.Name + findFieldByTag(v.Field(i).Interface(), k+1, tagName, fieldNames) + } + } +} + +func upperFirstLetter(s string) string { + if len(s) > 0 { + firstChar := []rune(s)[0] + upperFirstChar := unicode.ToUpper(firstChar) + return string(upperFirstChar) + s[1:] + } + return "" +} + +// Determine whether the first letter is capitalized +func isFirstLetterUpper(s string) bool { + if s == "" { + return false + } + r := rune(s[0]) + return unicode.IsUpper(r) +} + +func parseTag(cfg interface{}, name string) string { + names := strings.Split(name, ".") + var parseName string + if isFirstLetterUpper(names[0]) { + parseName = name + } else { + fieldNames := make([]string, len(names)) + findFieldByTag(cfg, 0, names, fieldNames) + for i, fieldName := range fieldNames { + if i == len(fieldNames)-1 { + parseName = parseName + fieldName + } else { + parseName = parseName + fieldName + "." + } + } + } + return parseName +} + +func ParseSet(cfg interface{}, set string) error { + sets := parseSetByComma(set) + names, vals := parseSetByEqual(sets) + parseVals := parseSetValue(vals) + for i, name := range names { + status := getNameFormStatus(name) + parseTagName := parseTag(cfg, name) + switch status { + //name1.nam2=val + case 0: + if err := setCommonValue(cfg, parseTagName, parseVals[i]); err != nil { + return err + } + //name1.nam[1].var=val + case 1: + if err := setVariableValue(cfg, parseTagName, parseVals[i]); err != nil { + return err + } + //name[0]=val + case 2: + if err := parseAndSetArrayValue(cfg, parseTagName, parseVals[i]); err != nil { + return err + } + default: + return errors.New("The field is not support") + } + } + return nil +} diff --git a/keadm/cmd/keadm/app/cmd/util/set_test.go b/keadm/cmd/keadm/app/cmd/util/set_test.go new file mode 100644 index 00000000000..9ae26dee9f7 --- /dev/null +++ b/keadm/cmd/keadm/app/cmd/util/set_test.go @@ -0,0 +1,464 @@ +/* +Copyright 2024 The KubeEdge Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "fmt" + "reflect" + "testing" + + "github.com/kubeedge/kubeedge/pkg/apis/componentconfig/edgecore/v1alpha2" +) + +func TestParseSetByComma(t *testing.T) { + testCases := []struct { + set string + expectVals []string + }{ + // Test case 1: Normal input + { + set: "value1,value2,value3", + expectVals: []string{"value1", "value2", "value3"}, + }, + // Test case 2: Input with spaces + { + set: "value1=1, value2[1].var=2 , value3.var=helo", + expectVals: []string{"value1=1", "value2[1].var=2", "value3.var=helo"}, + }, + // Test case 3: Input with quotes + { + set: `"value1","value2","value3"`, + expectVals: []string{`"value1"`, `"value2"`, `"value3"`}, + }, + // Test case 4: Input with braces + { + set: "{value1},{value2},{value3}", + expectVals: []string{"{value1}", "{value2}", "{value3}"}, + }, + // Test case 5: Mixed input + { + set: `value1,{"value2","value3"},value4`, + expectVals: []string{"value1", `{"value2","value3"}`, "value4"}, + }, + // Test case 6: Set with empty values + { + set: ",value1,,value2,", + expectVals: []string{"value1", "value2"}, + }, + } + + for _, tc := range testCases { + got := parseSetByComma(tc.set) + if !reflect.DeepEqual(got, tc.expectVals) { + t.Errorf("ParseSetByComma(%q) = %v, expect %v", tc.set, got, tc.expectVals) + } + } +} +func TestParseSetByEqual(t *testing.T) { + testCases := []struct { + set []string + expectNames []string + expectVals []string + }{ + { + set: []string{"name1=value1", "name2=value2", "name3=value3"}, + expectNames: []string{"name1", "name2", "name3"}, + expectVals: []string{"value1", "value2", "value3"}, + }, + { + set: []string{"name1value1", "name2=value2", "name3=value3"}, + expectNames: []string{"name2", "name3"}, + expectVals: []string{"value2", "value3"}, + }, + } + for _, tc := range testCases { + names, vals := parseSetByEqual(tc.set) + if !reflect.DeepEqual(names, tc.expectNames) || !reflect.DeepEqual(vals, tc.expectVals) { + t.Errorf("Failed for input %v. Expected (%v,%v),got(%v,%v)", tc.set, tc.expectNames, tc.expectVals, names, vals) + } + } +} + +func TestParseSetValue(t *testing.T) { + testCases := []struct { + vals []string + expectParse []interface{} + }{ + // Test case 1: Normal input + { + vals: []string{"123", "true", "3.14", "hello"}, + expectParse: []interface{}{123, true, 3.14, "hello"}, + }, + // Test case 2: Empty input + { + vals: []string{}, + expectParse: []interface{}{}, + }, + // Test case 3: Input with mixed types + { + vals: []string{"123", "true", "3.14", "hello", "false", "42"}, + expectParse: []interface{}{123, true, 3.14, "hello", false, 42}, + }, + } + + for _, tc := range testCases { + got := parseSetValue(tc.vals) + if !reflect.DeepEqual(got, tc.expectParse) { + t.Errorf("ParseSetValue(%q) = %v, want %v", tc.vals, got, tc.expectParse) + } + } +} +func TestParseValue(t *testing.T) { + testCases := []struct { + s string + expectResult interface{} + }{ + // Test case 1: Integer input + { + s: "123", + expectResult: 123, + }, + // Test case 2: Float input + { + s: "3.14", + expectResult: 3.14, + }, + // Test case 3: String input + { + s: "hello", + expectResult: "hello", + }, + // Test case 4: Array input + { + s: "{1, 2, 3}", + expectResult: []int{1, 2, 3}, + }, + // Test case 5: Empty input + { + s: "", + expectResult: "", + }, + // Test case 6: Bool true input + { + s: "true", + expectResult: true, + }, + // Test case 7: Bool false input + { + s: "false", + expectResult: false, + }, + } + + for _, tc := range testCases { + result := parseValue(tc.s) + if !reflect.DeepEqual(result, tc.expectResult) { + t.Errorf("Failed for input %s. Expected %v, got %v", tc.s, tc.expectResult, result) + } + } +} + +func TestParseArray(t *testing.T) { + testCases := []struct { + s string + expectResult interface{} + }{ + // Test case 1: Integer array input + { + s: "{1, 2, 3}", + expectResult: []int{1, 2, 3}, + }, + // Test case 2: Float array input + { + s: "{3.14, 2.718, 1.618}", + expectResult: []float64{3.14, 2.718, 1.618}, + }, + // Test case 3: String array input + { + s: `{"apple", "banana", "cherry"}`, + expectResult: []string{`"apple"`, `"banana"`, `"cherry"`}, + }, + } + + for _, tc := range testCases { + got := parseArray(tc.s) + if !reflect.DeepEqual(got, tc.expectResult) { + t.Errorf("ParseArray(%q) = %v, want %v", tc.s, got, tc.expectResult) + } + } +} +func TestParseType(t *testing.T) { + testCases := []struct { + s string + expectResult string + }{ + { + s: "123", + expectResult: "int", + }, + { + s: "3.14", + expectResult: "float", + }, + { + s: "hello", + expectResult: "string", + }, + { + s: "true", + expectResult: "bool", + }, + { + s: "false", + expectResult: "bool", + }, + } + for _, tc := range testCases { + result := parseType(tc.s) + if result != tc.expectResult { + t.Errorf("Failed for input %s. Expected %s,got %s", tc.s, tc.expectResult, result) + } + } +} + +func TestGetNameFormStatus(t *testing.T) { + testCases := []struct { + s string + expectResult int + }{ + // Test case 1: String without brackets + { + s: "name1", + expectResult: 0, + }, + // Test case 2 + { + s: "name1[0].variable1", + expectResult: 1, + }, + // Test case 3 + { + s: "name1[0]", + expectResult: 2, + }, + // Test case 4 + { + s: "name1[0].variable1.value2", + expectResult: 1, + }, + } + + for _, tc := range testCases { + got := getNameFormStatus(tc.s) + if got != tc.expectResult { + t.Errorf("GetNameFormStatus(%q) = %d, want %d", tc.s, got, tc.expectResult) + } + } +} + +func TestSetCommonValue(t *testing.T) { + type Config struct { + Name string + Value int + } + testStruct := Config{Name: "initial", Value: 42} + + err := setCommonValue(&testStruct, "Name", "updated") + if err != nil { + t.Errorf("Failed to set value:%v", err) + } + if testStruct.Name != "updated" { + t.Errorf("Failed to set string value.Expected 'updated',got '%s'", testStruct.Name) + } + + err = setCommonValue(&testStruct, "Value", 100) + if err != nil { + t.Errorf("Failed to set value: %v", err) + } + if testStruct.Value != 100 { + t.Errorf("Failed to set int value. Expected 100, got %d", testStruct.Value) + } + + err = setCommonValue(&testStruct, "Name", 123) + if err == nil { + t.Error("Expected an error for setting incorrect value type, but got nil") + } + + err = setCommonValue(&testStruct, "NonExistentField", "value") + if err == nil { + t.Error("Expected an error for setting value to non-existent field, but got nil") + } +} +func TestParseAndSetArrayValue(t *testing.T) { + type config struct { + ArrayField [3]string + } + testStruct := config{ArrayField: [3]string{"1", "2", "3"}} + newVal := "10" + err := parseAndSetArrayValue(&testStruct, "ArrayField[1]", newVal) + if err != nil { + t.Errorf("Failed to set array value: %v", err) + } + expectedArray := [3]string{"1", "10", "3"} + if !reflect.DeepEqual(testStruct.ArrayField, expectedArray) { + t.Errorf("Failed to set array value. Expected %v, got %v", expectedArray, testStruct.ArrayField) + } + err = parseAndSetArrayValue(&testStruct, "ArrayField[10]", "10") + if err == nil { + t.Error("Expected an error for setting value to non-existent index, but got nil") + } +} + +func TestSetArrayValue(t *testing.T) { + type Config struct { + ArrayField [3]string + } + // Initialize a test struct + testStruct := Config{ArrayField: [3]string{"1", "2", "3"}} + + // Test case 1: Set array value + err := setArrayValue(&testStruct, "ArrayField", 1, "10") + if err != nil { + t.Errorf("Failed to set array value: %v", err) + } + expectedArray := [3]string{"1", "10", "3"} + if !reflect.DeepEqual(testStruct.ArrayField, expectedArray) { + t.Errorf("Failed to set array value. Expected %v, got %v", expectedArray, testStruct.ArrayField) + } + + // Test case 2: Set value to non-existent index + err = setArrayValue(&testStruct, "ArrayField", 10, 5) + if err == nil { + t.Error("Expected an error for setting value to non-existent index, but got nil") + } + + // Test case 3: Set value with incorrect type + err = setArrayValue(&testStruct, "ArrayField", 1, 1) + if err == nil { + t.Error("Expected an error for setting incorrect value type, but got nil") + } +} + +func TestParseFieldPath1(t *testing.T) { + testCases := []struct { + fieldPath string + expectedPath string + expectedIndex int + expectedError string + }{ + // Test case 1: Valid field path with index + { + fieldPath: "ArrayField[1]", + expectedPath: "ArrayField", + expectedIndex: 1, + expectedError: "", + }, + // Test case 2: Invalid field path (no index) + { + fieldPath: "ArrayField", + expectedPath: "", + expectedIndex: -1, + expectedError: "invalid field path", + }, + } + + for _, tc := range testCases { + path, index, err := parseFieldPath1(tc.fieldPath) + // Check if the output matches the expected output + if path != tc.expectedPath || index != tc.expectedIndex || fmt.Sprintf("%v", err) != tc.expectedError { + t.Errorf("Failed for field path %s. Expected (%s, %d, %s), got (%s, %d, %v)", tc.fieldPath, tc.expectedPath, tc.expectedIndex, tc.expectedError, path, index, err) + } + } +} + +type Config struct { + Name1 Name1Config +} + +type Name1Config struct { + Name2 []Name2Config +} + +type Name2Config struct { + Variable1 int + Variable2 float64 + Variable3 string +} + +func TestSetVariableValue(t *testing.T) { + config := &Config{ + Name1: Name1Config{ + Name2: []Name2Config{ + {Variable1: 10, Variable2: 3.14, Variable3: "hello"}, + {Variable1: 20, Variable2: 6.28, Variable3: "world"}, + }, + }, + } + + err := setVariableValue(config, "Name1.Name2[0].Variable1", 100) + if err != nil { + t.Errorf("Error updating field: %v", err) + } + if config.Name1.Name2[0].Variable1 != 100 { + t.Errorf("Variable1 not updated properly") + } + + err = setVariableValue(config, "Name1.Name2[1].Variable4", "new value") + if err == nil { + t.Error("Expected error for updating non-existent field, but got nil") + } + + err = setVariableValue(config, "Name1.Name2[2].Variable1", 200) + if err == nil { + t.Error("Expected error for updating out of range index, but got nil") + } +} + +func TestSetVariableValue_TypeMismatch(t *testing.T) { + config := &Config{ + Name1: Name1Config{ + Name2: []Name2Config{ + {Variable1: 10, Variable2: 3.14, Variable3: "hello"}, + {Variable1: 20, Variable2: 6.28, Variable3: "world"}, + }, + }, + } + + err := setVariableValue(config, "Name1.Name2[0].Variable1", "string value") + if err == nil { + t.Error("Expected error for type mismatch, but got nil") + } +} + +func TestSetVariableValue_EmptySlice(t *testing.T) { + config := &Config{ + Name1: Name1Config{}, + } + + err := setVariableValue(config, "Name1.Name2[0].Variable1", 100) + if err == nil { + t.Error("Expected error for updating empty slice, but got nil") + } +} + +func TestEdgeCoreConfig(t *testing.T) { + cfg := v1alpha2.NewDefaultEdgeCoreConfig() + if err := ParseSet(cfg, `database.AliasName=test,database.driverName=mysql,modules.dbTest.enable=true,Modules.Edged.TailoredKubeletFlag.HostnameOverride=hy,Modules.MetaManager.MetaServer.ServiceAccountIssuers={ht,jl},featureGates={"alpha":true,"ht":false}`); err != nil { + t.Fatal(err) + } + t.Log(cfg.DataBase.AliasName, cfg.DataBase.DriverName, cfg.Modules.DBTest.Enable, cfg.Modules.Edged.TailoredKubeletFlag.HostnameOverride, cfg.Modules.MetaManager.MetaServer.ServiceAccountIssuers, cfg.FeatureGates) +}