Explicit field names

Fields now use the value of a `name` tag on a struct to determine the display
name for a given field. This improves output for boolean-pointer fields, which
otherwise have a P suffix and helper methods to retain backwards-compatibility.

Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
Jonathon Anderson
2025-09-05 12:16:48 -06:00
parent 28b040326e
commit 92375f4ebb
3 changed files with 60 additions and 12 deletions

View File

@@ -94,7 +94,7 @@ Holds the disks of a node
*/
type Disk struct {
id string `yaml:"-" json:"-"`
WipeTableP *bool `yaml:"wipe_table,omitempty" json:"wipe_table,omitempty" lopt:"diskwipe" comment:"whether or not the partition tables shall be wiped"`
WipeTableP *bool `yaml:"wipe_table,omitempty" json:"wipe_table,omitempty" lopt:"diskwipe" comment:"whether or not the partition tables shall be wiped" name:"WipeTable"`
Partitions map[string]*Partition `yaml:"partitions,omitempty" json:"partitions,omitempty"`
}
@@ -109,9 +109,9 @@ type Partition struct {
StartMiB string `yaml:"start_mib,omitempty" json:"start_mib,omitempty" comment:"the start of the partition" type:"uint"`
TypeGuid string `yaml:"type_guid,omitempty" json:"type_guid,omitempty" lopt:"parttype" comment:"Set the partition type GUID"`
Guid string `yaml:"guid,omitempty" json:"guid,omitempty" comment:"the GPT unique partition GUID"`
WipePartitionEntryP *bool `yaml:"wipe_partition_entry,omitempty" json:"wipe_partition_entry,omitempty" comment:"if true, Ignition will clobber an existing partition if it does not match the config"`
ShouldExistP *bool `yaml:"should_exist,omitempty" json:"should_exist,omitempty" lopt:"partcreate" comment:"Create partition if it does not exist"`
ResizeP *bool `yaml:"resize,omitempty" json:"resize,omitempty" comment:"whether or not the existing partition should be resize"`
WipePartitionEntryP *bool `yaml:"wipe_partition_entry,omitempty" json:"wipe_partition_entry,omitempty" comment:"if true, Ignition will clobber an existing partition if it does not match the config" name:"WipePartitionEntry"`
ShouldExistP *bool `yaml:"should_exist,omitempty" json:"should_exist,omitempty" lopt:"partcreate" comment:"Create partition if it does not exist" name:"ShouldExist"`
ResizeP *bool `yaml:"resize,omitempty" json:"resize,omitempty" comment:"whether or not the existing partition should be resize" name:"Resize"`
}
/*
@@ -121,7 +121,7 @@ type FileSystem struct {
id string `yaml:"-" json:"-"`
Format string `yaml:"format,omitempty" json:"format,omitempty" lopt:"fsformat" comment:"format of the file system"`
Path string `yaml:"path,omitempty" json:"path,omitempty" lopt:"fspath" comment:"the mount point of the file system"`
WipeFileSystemP *bool `yaml:"wipe_filesystem,omitempty" json:"wipe_filesystem,omitempty" lopt:"fswipe" comment:"wipe file system at boot"`
WipeFileSystemP *bool `yaml:"wipe_filesystem,omitempty" json:"wipe_filesystem,omitempty" lopt:"fswipe" comment:"wipe file system at boot" name:"WipeFileSystem"`
Label string `yaml:"label,omitempty" json:"label,omitempty" comment:"the label of the filesystem"`
Uuid string `yaml:"uuid,omitempty" json:"uuid,omitempty" comment:"the uuid of the filesystem"`
Options []string `yaml:"options,omitempty" json:"options,omitempty" comment:"any additional options to be passed to the format-specific mkfs utility"`

View File

@@ -129,7 +129,9 @@ func getNestedFieldValue(obj interface{}, name string) (value reflect.Value, err
err = fmt.Errorf("no value: %v", name)
return
}
value = value.FieldByName(fieldName)
// Find the actual field name if fieldName might be a display name
actualFieldName := findActualFieldName(value.Type(), fieldName)
value = value.FieldByName(actualFieldName)
if key != "" {
value = value.MapIndex(reflect.ValueOf(key))
if !value.IsValid() {
@@ -213,6 +215,29 @@ func listFields(obj interface{}) (fields []string) {
return listReflectedFields(reflect.TypeOf(obj), reflect.ValueOf(obj), "")
}
// getFieldDisplayName returns the display name for a struct field.
// If the field has a "name" tag, it returns that value. Otherwise, it returns the field's actual name.
func getFieldDisplayName(field reflect.StructField) string {
if name := field.Tag.Get("name"); name != "" {
return name
}
return field.Name
}
// findActualFieldName finds the actual struct field name given a display name.
// If no field matches the display name, it returns the original name (assuming it's already the actual field name).
func findActualFieldName(structType reflect.Type, displayName string) string {
for _, field := range reflect.VisibleFields(structType) {
if !field.IsExported() || field.Anonymous {
continue
}
if getFieldDisplayName(field) == displayName {
return field.Name
}
}
return displayName // fallback to original name
}
// listReflectedFields recursively traverses the structure defined by reflect.Type and reflect.Value
// to discover field paths. It supports struct fields, pointer fields, and map fields (with keys).
// Fields are returned as their dotted paths. For map fields, keys are included as "[key]" segments.
@@ -225,6 +250,7 @@ func listReflectedFields(t reflect.Type, v reflect.Value, prefix string) (fields
}
fieldType := field.Type
fieldValue := reflect.Value{}
displayName := getFieldDisplayName(field)
if v.IsValid() {
fieldValue = v.FieldByName(field.Name)
}
@@ -233,7 +259,7 @@ func listReflectedFields(t reflect.Type, v reflect.Value, prefix string) (fields
fieldValue = fieldValue.Elem()
}
if fieldType.Kind() == reflect.Struct {
fields = append(fields, listReflectedFields(fieldType, fieldValue, fmt.Sprintf("%v%v.", prefix, field.Name))...)
fields = append(fields, listReflectedFields(fieldType, fieldValue, fmt.Sprintf("%v%v.", prefix, displayName))...)
} else if fieldType.Kind() == reflect.Map {
if !fieldValue.IsValid() {
continue
@@ -250,13 +276,13 @@ func listReflectedFields(t reflect.Type, v reflect.Value, prefix string) (fields
}
}
if elementType.Kind() == reflect.Struct {
fields = append(fields, listReflectedFields(elementType, elementValue, fmt.Sprintf("%v%v[%v].", prefix, field.Name, key.String()))...)
fields = append(fields, listReflectedFields(elementType, elementValue, fmt.Sprintf("%v%v[%v].", prefix, displayName, key.String()))...)
} else {
fields = append(fields, fmt.Sprintf("%v%v[%v]", prefix, field.Name, key.String()))
fields = append(fields, fmt.Sprintf("%v%v[%v]", prefix, displayName, key.String()))
}
}
} else {
fields = append(fields, prefix+field.Name)
fields = append(fields, prefix+displayName)
}
}
return

View File

@@ -103,7 +103,7 @@ nodes:
field: "Resources[fstab]",
jsonValue: `[{"file":"/home","freq":0,"mntops":"defaults","passno":0,"spec":"warewulf:/home","vfstype":"nfs"}]`,
},
"disk partition": {
"disk partition (with name tag)": {
nodesConf: `
nodes:
n1:
@@ -113,7 +113,7 @@ nodes:
rootfs:
resize: false`,
node: "n1",
field: "Disks[/dev/vda].Partitions[rootfs].ResizeP",
field: "Disks[/dev/vda].Partitions[rootfs].Resize",
value: "false",
},
}
@@ -256,6 +256,28 @@ func Test_listFields(t *testing.T) {
"Resources[resource]",
},
},
"disk with name tags": {
object: Disk{
Partitions: map[string]*Partition{
"root": {
WipePartitionEntryP: new(bool),
ShouldExistP: new(bool),
ResizeP: new(bool),
},
},
},
fields: []string{
"WipeTable",
"Partitions[root].Number",
"Partitions[root].SizeMiB",
"Partitions[root].StartMiB",
"Partitions[root].TypeGuid",
"Partitions[root].Guid",
"Partitions[root].WipePartitionEntry",
"Partitions[root].ShouldExist",
"Partitions[root].Resize",
},
},
}
for name, tt := range tests {