Refactor gRPC-based node functions

Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
Jonathon Anderson
2026-03-21 21:15:12 -06:00
parent 3c895dd941
commit ec6884a878
15 changed files with 430 additions and 694 deletions

View File

@@ -0,0 +1,88 @@
package node
import (
"reflect"
)
// UpdateFrom copies fields from src to dst, but only those fields whose
// corresponding cobra flag (identified by the "lopt" struct tag) reports
// as changed. This replaces the old pattern of marshaling to YAML (to strip
// zero values via omitempty) and then merging with mergo.
//
// The changed function should typically be cmd.Flags().Changed.
func (dst *Node) UpdateFrom(src *Node, changed func(string) bool) {
recursiveUpdateFrom(reflect.ValueOf(dst).Elem(), reflect.ValueOf(src).Elem(), changed)
}
// UpdateFrom copies fields from src to dst for profiles.
func (dst *Profile) UpdateFrom(src *Profile, changed func(string) bool) {
recursiveUpdateFrom(reflect.ValueOf(dst).Elem(), reflect.ValueOf(src).Elem(), changed)
}
func recursiveUpdateFrom(dst, src reflect.Value, changed func(string) bool) {
srcType := src.Type()
for i := 0; i < src.NumField(); i++ {
field := srcType.Field(i)
srcField := src.Field(i)
dstField := dst.Field(i)
if !field.IsExported() {
continue
}
if lopt := field.Tag.Get("lopt"); lopt != "" && field.Tag.Get("comment") != "" {
// Leaf field with a cobra flag — copy if the flag was changed
if changed(lopt) {
dstField.Set(srcField)
}
} else if field.Anonymous {
// Embedded struct (e.g., Profile in Node)
recursiveUpdateFrom(dstField, srcField, changed)
} else if field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct {
// Pointer-to-struct (e.g., *IpmiConf, *KernelConf)
if srcField.IsNil() {
continue
}
if dstField.IsNil() {
dstField.Set(reflect.New(field.Type.Elem()))
}
recursiveUpdateFrom(dstField.Elem(), srcField.Elem(), changed)
} else if field.Type.Kind() == reflect.Struct {
// Direct struct
recursiveUpdateFrom(dstField, srcField, changed)
} else if field.Type.Kind() == reflect.Map {
// Map fields (e.g., NetDevs, Disks, FileSystems)
switch field.Type.Elem().Kind() {
case reflect.String, reflect.Interface:
// Tags map[string]string — handled separately by tag add/del operations
continue
case reflect.Pointer:
// map[string]*Struct — update entries from src into dst
if srcField.IsNil() {
continue
}
if dstField.IsNil() {
dstField.Set(reflect.MakeMap(field.Type))
}
for _, key := range srcField.MapKeys() {
srcEntry := srcField.MapIndex(key)
if srcEntry.IsNil() {
continue
}
dstEntry := dstField.MapIndex(key)
if !dstEntry.IsValid() || dstEntry.IsNil() {
// Create new entry in dst
dstEntry = reflect.New(field.Type.Elem().Elem())
dstField.SetMapIndex(key, dstEntry)
}
// Map values are not addressable, so we need to work with a copy
tmpDst := reflect.New(field.Type.Elem().Elem()).Elem()
tmpDst.Set(dstEntry.Elem())
recursiveUpdateFrom(tmpDst, srcEntry.Elem(), changed)
dstField.SetMapIndex(key, tmpDst.Addr())
}
}
}
}
}