Merge pull request #1647 from anderbubble/enhanced-field-formatting

Enhancements for node.Field formatting
This commit is contained in:
Jonathon Anderson
2025-01-23 21:51:54 -07:00
committed by GitHub
3 changed files with 106 additions and 28 deletions

View File

@@ -1,8 +1,8 @@
package node package node
import ( import (
"encoding/json"
"fmt" "fmt"
"net"
"reflect" "reflect"
"regexp" "regexp"
"sort" "sort"
@@ -223,33 +223,51 @@ func listReflectedFields(t reflect.Type, v reflect.Value, prefix string) (fields
return return
} }
// valueStr converts a reflect.Value into a string. For slices, it joins all elements into a comma-separated // valueStr converts a reflect.Value into a string.
// 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.
func valueStr(value reflect.Value) (output string) { func valueStr(value reflect.Value) (output string) {
if !value.IsValid() { if !value.IsValid() {
return "" return ""
} }
if value.Kind() == reflect.Pointer { if value.Kind() == reflect.Pointer {
if value.IsZero() { if value.IsZero() {
return "" return ""
} }
value = value.Elem() value = value.Elem()
} }
if value.Type() == reflect.TypeOf(net.IP{}) {
if !value.IsZero() { switch value.Kind() {
output = fmt.Sprintf("%s", value) 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 var sliceStrs []string
for i := 0; i < value.Len(); i++ { for i := 0; i < value.Len(); i++ {
sliceStrs = append(sliceStrs, fmt.Sprintf("%v", value.Index(i))) sliceStrs = append(sliceStrs, fmt.Sprintf("%v", value.Index(i)))
} }
output = strings.Join(sliceStrs, ",") return strings.Join(sliceStrs, ",")
} else {
output = fmt.Sprintf("%s", value)
} }
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 // sortValues sorts a slice of reflect.Values. Currently, it only supports sorting string values and

View File

@@ -14,6 +14,7 @@ func Test_getNestedFieldString(t *testing.T) {
node string node string
field string field string
value string value string
jsonValue string
}{ }{
"comment (simple)": { "comment (simple)": {
nodesConf: ` nodesConf: `
@@ -67,6 +68,53 @@ nodes:
field: "NetDevs[default].Tags[tag]", field: "NetDevs[default].Tags[tag]",
value: "n1 netdev 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 { for name, tt := range tests {
@@ -80,7 +128,12 @@ nodes:
node := registry.Nodes[tt.node] node := registry.Nodes[tt.node]
value, err := getNestedFieldString(node, tt.field) value, err := getNestedFieldString(node, tt.field)
assert.NoError(t, err) 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)
}
}) })
} }
} }

View File

@@ -194,15 +194,16 @@ nodeprofiles:
func Test_MergeNode(t *testing.T) { func Test_MergeNode(t *testing.T) {
var tests = map[string]struct { var tests = map[string]struct {
nodesConf string nodesConf string
node string node string
field string field string
source string source string
value string value string
nodes []string nodes []string
fields []string fields []string
sources []string sources []string
values []string values []string
jsonValues []string
}{ }{
"node comment": { "node comment": {
nodesConf: ` nodesConf: `
@@ -779,10 +780,10 @@ nodes:
file: /opt file: /opt
vfstype: nfs vfstype: nfs
`, `,
nodes: []string{"n1"}, nodes: []string{"n1"},
fields: []string{"Resources[fstab]"}, fields: []string{"Resources[fstab]"},
sources: []string{"p1,n1"}, sources: []string{"p1,n1"},
values: []string{"[map[file:/home spec:warewulf:/home vfstype:nfs] map[file:/opt spec:warewulf:/opt vfstype:nfs]]"}, 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]) _, fields, _ := registry.MergeNode(tt.nodes[i])
value, valueErr := getNestedFieldString(nodes[i], tt.fields[i]) value, valueErr := getNestedFieldString(nodes[i], tt.fields[i])
assert.NoError(t, valueErr) assert.NoError(t, valueErr)
assert.Equal(t, tt.values[i], value) if len(tt.values) > i {
assert.Equal(t, tt.values[i], fields.Value(tt.fields[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])) assert.Equal(t, tt.sources[i], fields.Source(tt.fields[i]))
} }
}) })