diff --git a/internal/pkg/node/fields.go b/internal/pkg/node/fields.go index 7fc43601..6b32664b 100644 --- a/internal/pkg/node/fields.go +++ b/internal/pkg/node/fields.go @@ -1,8 +1,8 @@ package node import ( + "encoding/json" "fmt" - "net" "reflect" "regexp" "sort" @@ -223,33 +223,51 @@ func listReflectedFields(t reflect.Type, v reflect.Value, prefix string) (fields return } -// valueStr converts a reflect.Value into a string. For slices, it joins all elements into a comma-separated -// string. For pointers, it returns an empty string if the pointer is nil. For other kinds, it returns the -// formatted string representation of the value. +// valueStr converts a reflect.Value into a string. func valueStr(value reflect.Value) (output string) { if !value.IsValid() { return "" } + if value.Kind() == reflect.Pointer { if value.IsZero() { return "" } value = value.Elem() } - if value.Type() == reflect.TypeOf(net.IP{}) { - if !value.IsZero() { - output = fmt.Sprintf("%s", value) + + switch value.Kind() { + case reflect.Ptr, reflect.Interface, reflect.Map, reflect.Slice: + if value.IsNil() { + return "" } - } else if value.Kind() == reflect.Slice { + } + + stringerType := reflect.TypeOf((*fmt.Stringer)(nil)).Elem() + if value.Type().Implements(stringerType) { + return fmt.Sprintf("%s", value) + } + + if value.Type() == reflect.TypeOf([]string{}) { var sliceStrs []string for i := 0; i < value.Len(); i++ { sliceStrs = append(sliceStrs, fmt.Sprintf("%v", value.Index(i))) } - output = strings.Join(sliceStrs, ",") - } else { - output = fmt.Sprintf("%s", value) + return strings.Join(sliceStrs, ",") } - return output + + switch value.Kind() { + case reflect.String, reflect.Int: + return fmt.Sprintf("%s", value) + case reflect.Bool: + return fmt.Sprintf("%t", value.Bool()) + } + + if jsonBytes, err := json.Marshal(value.Interface()); err == nil { + return string(jsonBytes) + } + + return fmt.Sprintf("%s", value) } // sortValues sorts a slice of reflect.Values. Currently, it only supports sorting string values and diff --git a/internal/pkg/node/fields_test.go b/internal/pkg/node/fields_test.go index 95e980db..b8b59127 100644 --- a/internal/pkg/node/fields_test.go +++ b/internal/pkg/node/fields_test.go @@ -14,6 +14,7 @@ func Test_getNestedFieldString(t *testing.T) { node string field string value string + jsonValue string }{ "comment (simple)": { nodesConf: ` @@ -67,6 +68,53 @@ nodes: field: "NetDevs[default].Tags[tag]", value: "n1 netdev tag", }, + "boolean value (true)": { + nodesConf: ` +nodes: + n1: + discoverable: true`, + node: "n1", + field: "Discoverable", + value: "true", + }, + "boolean value (false)": { + nodesConf: ` +nodes: + n1: + discoverable: false`, + node: "n1", + field: "Discoverable", + value: "false", + }, + "fstab resource": { + nodesConf: ` +nodes: + n1: + resources: + fstab: + - file: /home + freq: 0 + mntops: defaults + passno: 0 + spec: warewulf:/home + vfstype: nfs`, + node: "n1", + field: "Resources[fstab]", + jsonValue: `[{"file":"/home","freq":0,"mntops":"defaults","passno":0,"spec":"warewulf:/home","vfstype":"nfs"}]`, + }, + "disk partition": { + nodesConf: ` +nodes: + n1: + disks: + /dev/vda: + partitions: + rootfs: + resize: false`, + node: "n1", + field: "Disks[/dev/vda].Partitions[rootfs].Resize", + value: "false", + }, } for name, tt := range tests { @@ -80,7 +128,12 @@ nodes: node := registry.Nodes[tt.node] value, err := getNestedFieldString(node, tt.field) assert.NoError(t, err) - assert.Equal(t, tt.value, value) + if tt.value != "" { + assert.Equal(t, tt.value, value) + } + if tt.jsonValue != "" { + assert.JSONEq(t, tt.jsonValue, value) + } }) } } diff --git a/internal/pkg/node/mergo_test.go b/internal/pkg/node/mergo_test.go index 5b27c218..139b0bab 100644 --- a/internal/pkg/node/mergo_test.go +++ b/internal/pkg/node/mergo_test.go @@ -194,15 +194,16 @@ nodeprofiles: func Test_MergeNode(t *testing.T) { var tests = map[string]struct { - nodesConf string - node string - field string - source string - value string - nodes []string - fields []string - sources []string - values []string + nodesConf string + node string + field string + source string + value string + nodes []string + fields []string + sources []string + values []string + jsonValues []string }{ "node comment": { nodesConf: ` @@ -779,10 +780,10 @@ nodes: file: /opt vfstype: nfs `, - nodes: []string{"n1"}, - fields: []string{"Resources[fstab]"}, - sources: []string{"p1,n1"}, - values: []string{"[map[file:/home spec:warewulf:/home vfstype:nfs] map[file:/opt spec:warewulf:/opt vfstype:nfs]]"}, + nodes: []string{"n1"}, + fields: []string{"Resources[fstab]"}, + sources: []string{"p1,n1"}, + jsonValues: []string{`[{"file":"/home","spec":"warewulf:/home","vfstype":"nfs"},{"file":"/opt","spec":"warewulf:/opt","vfstype":"nfs"}]`}, }, } @@ -817,8 +818,14 @@ nodes: _, fields, _ := registry.MergeNode(tt.nodes[i]) value, valueErr := getNestedFieldString(nodes[i], tt.fields[i]) assert.NoError(t, valueErr) - assert.Equal(t, tt.values[i], value) - assert.Equal(t, tt.values[i], fields.Value(tt.fields[i])) + if len(tt.values) > i { + assert.Equal(t, tt.values[i], value) + assert.Equal(t, tt.values[i], fields.Value(tt.fields[i])) + } + if len(tt.jsonValues) > i { + assert.JSONEq(t, tt.jsonValues[i], value) + assert.Equal(t, tt.jsonValues[i], fields.Value(tt.fields[i])) + } assert.Equal(t, tt.sources[i], fields.Source(tt.fields[i])) } })