Files
warewulf/internal/pkg/node/constructors.go
Christian Goll 5bd4fd4712 use yaml/v3 and don't export Nodes
introduced wwbool and don't export
Nodes and NodeConfs. This requires new
explict Yaml (un)marshaling as the standard
marshaller won't touch these fields

Signed-off-by: Christian Goll <cgoll@suse.com>
2024-10-17 15:30:54 -04:00

322 lines
8.0 KiB
Go

package node
import (
"os"
"path"
"reflect"
"sort"
"dario.cat/mergo"
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
"gopkg.in/yaml.v3"
)
var (
ConfigFile string
)
/*
Creates a new nodeDb object from the on-disk configuration
*/
func New() (NodeYaml, error) {
conf := warewulfconf.Get()
if ConfigFile == "" {
ConfigFile = path.Join(conf.Paths.Sysconfdir, "warewulf/nodes.conf")
}
wwlog.Verbose("Opening node configuration file: %s", ConfigFile)
data, err := os.ReadFile(ConfigFile)
if err != nil {
return NodeYaml{}, err
}
return Parse(data)
}
// Parse constructs a new nodeDb object from an input YAML
// document. Passes any errors return from yaml.Unmarshal. Returns an
// error if any parsed value is not of a valid type for the given
// parameter.
func Parse(data []byte) (nodeList NodeYaml, err error) {
wwlog.Debug("Unmarshaling the node configuration")
err = yaml.Unmarshal(data, &nodeList)
if err != nil {
return nodeList, err
}
wwlog.Debug("Checking nodes for types")
if nodeList.nodes == nil {
nodeList.nodes = map[string]*NodeConf{}
}
if nodeList.nodeProfiles == nil {
nodeList.nodeProfiles = map[string]*ProfileConf{}
}
wwlog.Debug("returning node object")
return nodeList, nil
}
/*
Get a node with its merged in nodes
*/
func (config *NodeYaml) GetNode(id string) (node NodeConf, err error) {
if _, ok := config.nodes[id]; !ok {
return node, ErrNotFound
}
node = *config.nodes[id]
for _, p := range cleanList(config.nodes[id].Profiles) {
includedProfile, err := config.GetProfile(p)
if err != nil {
return node, err
}
err = mergo.Merge(&node.ProfileConf, includedProfile, mergo.WithAppendSlice)
if err != nil {
return node, err
}
}
// err = mergo.Merge(&node, config.nodes[id], mergo.WithOverride, mergo.WithoutDereference)
// err = mergo.Merge(&node, config.nodes[id], mergo.WithOverride)
// err = mergo.Merge(&node, config.nodes[id])
if err != nil {
return node, err
}
/*
node = EmptyNode()
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
dec := gob.NewDecoder(&buf)
includedProfile, err := config.GetProfile(p)
appendStringSlices(&node, &includedProfile)
if err != nil {
return node, err
}
err = enc.Encode(includedProfile)
if err != nil {
return node, err
}
err = dec.Decode(&node)
if err != nil {
return node, err
}
wwlog.Debug("merged in profile: %s", p)
}
var bufNode bytes.Buffer
encNode := gob.NewEncoder(&bufNode)
decNode := gob.NewDecoder(&bufNode)
appendStringSlices(&node, &config.nodes[id].ProfileConf)
err = encNode.Encode(config.nodes[id])
if err != nil {
return node, err
}
err = decNode.Decode(&node)
if err != nil {
return node, err
}
*/
// finally set no exported values
node.id = id
node.valid = true
if netdev, ok := node.NetDevs[node.PrimaryNetDev]; ok {
netdev.primary = true
} else {
keys := make([]string, 0)
for k := range node.NetDevs {
keys = append(keys, k)
}
sort.Strings(keys)
if len(keys) > 0 {
wwlog.Debug("%s: no primary defined, sanitizing to: %s", id, keys[0])
node.NetDevs[keys[0]].primary = true
node.PrimaryNetDev = keys[0]
}
}
wwlog.Debug("constructed node: %s", id)
return
}
/*
Return the node with the id string without the merged in nodes, return ErrNotFound
otherwise
*/
func (config *NodeYaml) GetNodeOnly(id string) (node NodeConf, err error) {
node = EmptyNode()
if found, ok := config.nodes[id]; ok {
return *found, nil
}
return node, ErrNotFound
}
/*
Return pointer to the node with the id string without the merged in nodes, return ErrMotFound
otherwise
*/
func (config *NodeYaml) GetNodeOnlyPtr(id string) (*NodeConf, error) {
node := EmptyNode()
if found, ok := config.nodes[id]; ok {
return found, nil
}
return &node, ErrNotFound
}
/*
Get the profile with id, return ErrNotFound otherwise
*/
func (config *NodeYaml) GetProfile(id string) (profile ProfileConf, err error) {
if found, ok := config.nodeProfiles[id]; ok {
found.id = id
return *found, nil
}
return profile, ErrNotFound
}
/*
Get the profile with id, return ErrNotFound otherwise
*/
func (config *NodeYaml) GetProfilePtr(id string) (profile *ProfileConf, err error) {
if found, ok := config.nodeProfiles[id]; ok {
found.id = id
return found, nil
}
return profile, ErrNotFound
}
/*
Get the nodes from the loaded configuration. This function also merges
the nodes with the given nodes.
*/
func (config *NodeYaml) FindAllNodes(nodes ...string) (nodeList []NodeConf, err error) {
if len(nodes) == 0 {
for n := range config.nodes {
nodes = append(nodes, n)
}
}
wwlog.Debug("Finding nodes: %s", nodes)
for _, nodeId := range nodes {
node, err := config.GetNode(nodeId)
if err != nil {
return nodeList, err
}
nodeList = append(nodeList, node)
}
sort.Slice(nodeList, func(i, j int) bool {
if nodeList[i].ClusterName < nodeList[j].ClusterName {
return true
} else if nodeList[i].ClusterName == nodeList[j].ClusterName {
if nodeList[i].id < nodeList[j].id {
return true
}
}
return false
})
return nodeList, nil
}
/*
Return all nodes as ProfileConf
*/
func (config *NodeYaml) FindAllProfiles(nodes ...string) (profileList []ProfileConf, err error) {
if len(nodes) == 0 {
for n := range config.nodeProfiles {
nodes = append(nodes, n)
}
}
wwlog.Debug("Finding nodes: %s", nodes)
for _, profileId := range nodes {
node, err := config.GetProfile(profileId)
if err != nil {
return profileList, err
}
profileList = append(profileList, node)
}
sort.Slice(profileList, func(i, j int) bool {
if profileList[i].ClusterName < profileList[j].ClusterName {
return true
} else if profileList[i].ClusterName == profileList[j].ClusterName {
if profileList[i].id < profileList[j].id {
return true
}
}
return false
})
return profileList, nil
}
/*
Return the names of all available nodes
*/
func (config *NodeYaml) ListAllNodes() []string {
nodeList := make([]string, len(config.nodes))
for name := range config.nodes {
nodeList = append(nodeList, name)
}
return nodeList
}
/*
Return the names of all available nodes
*/
func (config *NodeYaml) ListAllProfiles() []string {
var nodeList []string
for name := range config.nodeProfiles {
nodeList = append(nodeList, name)
}
return nodeList
}
/*
FindDiscoverableNode returns the first discoverable node and an
interface to associate with the discovered interface. If the node has
a primary interface, it is returned; otherwise, the first interface
without a hardware address is returned.
If no unconfigured node is found, an error is returned.
*/
func (config *NodeYaml) FindDiscoverableNode() (NodeConf, string, error) {
nodes, _ := config.FindAllNodes()
for _, node := range nodes {
if !node.Discoverable {
continue
}
if _, ok := node.NetDevs[node.PrimaryNetDev]; ok {
return node, node.PrimaryNetDev, nil
}
for netdev, dev := range node.NetDevs {
if dev.Hwaddr != "" {
return node, netdev, nil
}
}
}
return EmptyNode(), "", ErrNoUnconfigured
}
func appendStringSlices(src, dst any) {
//srcType := reflect.TypeOf(src)
srcVal := reflect.ValueOf(src)
dstType := reflect.TypeOf(dst)
dstVal := reflect.ValueOf(dst)
for i := 0; i < dstType.Elem().NumField(); i++ {
// wwlog.Debug("dstType.Name: %s", dstType.Elem().Field(i).Name)
srcValField := srcVal.Elem().FieldByName(dstType.Elem().Field(i).Name)
if !srcValField.IsZero() {
if srcValField.Type() == reflect.TypeOf([]string{}) {
// wwlog.Debug("dstType.Name: %s", dstType.Elem().Field(i).Name)
if srcValField.Len() > 0 {
// wwlog.Debug("srcValField.Len(): %d", srcValField.Len())
for _, elem := range srcValField.Interface().([]string) {
dstVal.Elem().Field(i).Set(reflect.Append(dstVal.Elem().Field(i), reflect.ValueOf(elem)))
wwlog.Debug("elem: %s", elem)
}
lst := dstVal.Elem().Field(i).Addr().Interface().(*[]string)
*lst = cleanList(*lst)
}
}
} else if srcValField.Type().Kind() == reflect.Ptr {
appendStringSlices(srcValField, dstVal.Elem().Field(i).Interface())
}
}
}