From 28a7b9fe84e37da81513d363ffbe5345e8403c23 Mon Sep 17 00:00:00 2001 From: Christian Goll Date: Fri, 15 Dec 2023 15:35:26 +0100 Subject: [PATCH] removed NodeInfo and using NodeConf instead This is a significant change in the undelying data model! nodeDb, err := node.New() will result in a structure which contains the on disk values. Only nodeDb.FindAllNodes() or nodeDb.GetNode(id) will give the nodes with its merged in profiles. Signed-off-by: Christian Goll --- CHANGELOG.md | 8 + go.mod | 1 + go.sum | 1 + internal/pkg/api/node/status.go | 94 ++++ internal/pkg/configure/hostfile.go | 7 +- internal/pkg/node/constructors.go | 433 ++++++---------- internal/pkg/node/datastructure.go | 241 +++------ internal/pkg/node/error.go | 6 + internal/pkg/node/flags.go | 283 +++++------ internal/pkg/node/helper.go | 16 + internal/pkg/node/ignition.go | 74 +-- internal/pkg/node/list.go | 68 ++- internal/pkg/node/methods.go | 596 +++++++---------------- internal/pkg/node/modifiers.go | 123 ++--- internal/pkg/node/transformers.go | 397 --------------- internal/pkg/node/util.go | 92 ++-- internal/pkg/oci/defaults.go | 3 +- internal/pkg/overlay/config_test.go | 3 +- internal/pkg/overlay/datastructure.go | 62 ++- internal/pkg/overlay/funcmap.go | 4 +- internal/pkg/overlay/overlay.go | 46 +- internal/pkg/util/util.go | 7 +- internal/pkg/warewulfd/nodedb.go | 63 +-- internal/pkg/warewulfd/provision.go | 116 +++-- internal/pkg/warewulfd/provision_test.go | 2 + internal/pkg/warewulfd/status.go | 8 +- internal/pkg/warewulfd/util.go | 14 +- 27 files changed, 967 insertions(+), 1801 deletions(-) create mode 100644 internal/pkg/api/node/status.go create mode 100644 internal/pkg/node/error.go create mode 100644 internal/pkg/node/helper.go delete mode 100644 internal/pkg/node/transformers.go diff --git a/CHANGELOG.md b/CHANGELOG.md index be186597..1e0133d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -262,6 +262,14 @@ Official v4.5.0 release. ## 4.5.0rc1, 2024-02-08 +### Refactoring + +- removed NodeInfo completely. node.FindAllNodes() will now give back + the list of nodes with its merged in profiles. Also there is now a + node.GetNode(id) command which will give back a node with its merged + in profiles. Changes to this structures will not be reflected to the + database, but must nodeYaml.Nodes or nodeYaml.NodeProfiles + ### Added - Start building packages for Rocky Linux 9. #951 diff --git a/go.mod b/go.mod index df3fd449..087f5021 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,7 @@ require ( ) require ( + dario.cat/mergo v1.0.0 // indirect github.com/AdamKorcz/go-fuzz-headers v0.0.0-20210312213058-32f4d319f0d2 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/BurntSushi/toml v1.3.2 // indirect diff --git a/go.sum b/go.sum index 15580a08..7e52c205 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/AdamKorcz/go-fuzz-headers v0.0.0-20210312213058-32f4d319f0d2 h1:dIxAd7URQa+ovSiQURY3UJu8Q7A2dG7QKTlxOlvDZHI= github.com/AdamKorcz/go-fuzz-headers v0.0.0-20210312213058-32f4d319f0d2/go.mod h1:VPevheIvXETHZT/ddjwarP3POR5p/cnH9Hy5yoFnQjc= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= diff --git a/internal/pkg/api/node/status.go b/internal/pkg/api/node/status.go new file mode 100644 index 00000000..babffc2c --- /dev/null +++ b/internal/pkg/api/node/status.go @@ -0,0 +1,94 @@ +package apinode + +import ( + "encoding/json" + "fmt" + "net/http" + + warewulfconf "github.com/warewulf/warewulf/internal/pkg/config" + + "github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1" + "github.com/warewulf/warewulf/internal/pkg/hostlist" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +// NodeStatus returns the imaging state for nodes. +// This requires warewulfd. +func NodeStatus(nodeNames []string) (nodeStatusResponse *wwapiv1.NodeStatusResponse, err error) { + + // Local structs for translating json from warewulfd. + type nodeStatusInternal struct { + NodeName string `json:"node name"` + Stage string `json:"stage"` + Sent string `json:"sent"` + Ipaddr string `json:"ipaddr"` + Lastseen int64 `json:"last seen"` + } + + // all status is a map with one key (nodes) + // and maps of [nodeName]NodeStatus underneath. + type allStatus struct { + Nodes map[string]*nodeStatusInternal `json:"nodes"` + } + + controller := warewulfconf.Get() + + if controller.Ipaddr == "" { + err = fmt.Errorf("the Warewulf Server IP Address is not properly configured") + wwlog.Error(fmt.Sprintf("%v", err.Error())) + return + } + + statusURL := fmt.Sprintf("http://%s:%d/status", controller.Ipaddr, controller.Warewulf.Port) + wwlog.Verbose("Connecting to: %s", statusURL) + + resp, err := http.Get(statusURL) + if err != nil { + wwlog.Error("Could not connect to Warewulf server: %s", err) + return + } + defer resp.Body.Close() + + decoder := json.NewDecoder(resp.Body) + var wwNodeStatus allStatus + + err = decoder.Decode(&wwNodeStatus) + if err != nil { + wwlog.Error("Could not decode JSON: %s", err) + return + } + + // Translate struct and filter. + nodeStatusResponse = &wwapiv1.NodeStatusResponse{} + + if len(nodeNames) == 0 { + for _, v := range wwNodeStatus.Nodes { + nodeStatusResponse.NodeStatus = append(nodeStatusResponse.NodeStatus, + &wwapiv1.NodeStatus{ + NodeName: v.NodeName, + Stage: v.Stage, + Sent: v.Sent, + Ipaddr: v.Ipaddr, + Lastseen: v.Lastseen, + }) + } + } else { + nodeList := hostlist.Expand(nodeNames) + for _, v := range wwNodeStatus.Nodes { + for j := 0; j < len(nodeList); j++ { + if v.NodeName == nodeList[j] { + nodeStatusResponse.NodeStatus = append(nodeStatusResponse.NodeStatus, + &wwapiv1.NodeStatus{ + NodeName: v.NodeName, + Stage: v.Stage, + Sent: v.Sent, + Ipaddr: v.Ipaddr, + Lastseen: v.Lastseen, + }) + break + } + } + } + } + return +} diff --git a/internal/pkg/configure/hostfile.go b/internal/pkg/configure/hostfile.go index c696f6fc..25fa976d 100644 --- a/internal/pkg/configure/hostfile.go +++ b/internal/pkg/configure/hostfile.go @@ -20,10 +20,11 @@ func Hostfile() (err error) { return fmt.Errorf("'the overlay template '/etc/hosts.ww' does not exists in 'host' overlay") } - nodeInfo := node.NewInfo() hostname, _ := os.Hostname() - nodeInfo.Id.Set(hostname) - tstruct := overlay.InitStruct(&nodeInfo) + tstruct, err := overlay.InitStruct(node.NewNode(hostname)) + if err != nil { + return err + } buffer, backupFile, writeFile, err := overlay.RenderTemplateFile( hostTemplate, tstruct) diff --git a/internal/pkg/node/constructors.go b/internal/pkg/node/constructors.go index 7cc575a7..e33817e4 100644 --- a/internal/pkg/node/constructors.go +++ b/internal/pkg/node/constructors.go @@ -1,12 +1,11 @@ package node import ( - "errors" - "fmt" + "bytes" + "encoding/gob" "os" "path" "sort" - "strings" warewulfconf "github.com/warewulf/warewulf/internal/pkg/config" "github.com/warewulf/warewulf/internal/pkg/wwlog" @@ -15,45 +14,9 @@ import ( ) var ( - ConfigFile string - DefaultConfig string + ConfigFile string ) -// used as fallback if DefaultConfig can't be read -var FallBackConf = `--- -defaultnode: - runtime overlay: - - hosts - - ssh.authorized_keys - - syncuser - system overlay: - - wwinit - - wwclient - - fstab - - hostname - - ssh.host_keys - - issue - - resolv - - udev.netname - - systemd.netname - - ifcfg - - NetworkManager - - debian.interfaces - - wicked - - ignition - kernel: - args: quiet crashkernel=no vga=791 net.naming-scheme=v238 - init: /sbin/init - root: initramfs - ipxe template: default - profiles: - - default - network devices: - dummy: - type: ethernet - netmask: 255.255.255.0 - onboot: true` - /* Creates a new nodeDb object from the on-disk configuration */ @@ -62,9 +25,6 @@ func New() (NodeYaml, error) { if ConfigFile == "" { ConfigFile = path.Join(conf.Paths.Sysconfdir, "warewulf/nodes.conf") } - if DefaultConfig == "" { - DefaultConfig = path.Join(conf.Warewulf.DataStore, "warewulf/defaults.conf") - } wwlog.Verbose("Opening node configuration file: %s", ConfigFile) data, err := os.ReadFile(ConfigFile) if err != nil { @@ -78,276 +38,204 @@ func New() (NodeYaml, error) { // error if any parsed value is not of a valid type for the given // parameter. func Parse(data []byte) (NodeYaml, error) { - var ret NodeYaml + var nodeList NodeYaml var err error wwlog.Debug("Unmarshaling the node configuration") - err = yaml.Unmarshal(data, &ret) + err = yaml.Unmarshal(data, &nodeList) if err != nil { - return ret, err + return nodeList, err } - wwlog.Debug("Checking nodes for types") - if ret.Nodes == nil { - ret.Nodes = map[string]*NodeConf{} + wwlog.Debug("Checking profiles for types") + if nodeList.Nodes == nil { + nodeList.Nodes = map[string]*NodeConf{} } - for nodeName, node := range ret.Nodes { - err = node.Check() - if err != nil { - wwlog.Warn("node: %s parsing error: %s", nodeName, err) - return ret, err + /* + for nodeName, node := range nodeList.Nodes { + err = node.Check() + if err != nil { + wwlog.Warn("node: %s parsing error: %s", nodeName, err) + return nodeList, err + } } + */ + if nodeList.NodeProfiles == nil { + nodeList.NodeProfiles = map[string]*ProfileConf{} } - if ret.NodeProfiles == nil { - ret.NodeProfiles = map[string]*NodeConf{} - } - for profileName, profile := range ret.NodeProfiles { - err = profile.Check() - if err != nil { - wwlog.Warn("node: %s parsing error: %s", profileName, err) - return ret, err + /* + for profileName, profile := range nodeList.NodeProfiles { + err = profile.Check() + if err != nil { + wwlog.Warn("node: %s parsing error: %s", profileName, err) + return nodeList, err + } } - } - + */ wwlog.Debug("Returning node object") - return ret, nil + return nodeList, nil } /* -Get all the nodes of a configuration. This function also merges -the nodes with the given profiles and set the default values -for every node +Get a node with its merged in profiles */ -func (config *NodeYaml) FindAllNodes() ([]NodeInfo, error) { - var ret []NodeInfo - var defConf map[string]*NodeConf - wwlog.Verbose("Opening defaults from file failed %s\n", DefaultConfig) - defData, err := os.ReadFile(DefaultConfig) - if err != nil { - wwlog.Verbose("Couldn't read DefaultConfig :%s\n", err) - wwlog.Verbose("Using building defaults") - defData = []byte(FallBackConf) +func (config *NodeYaml) GetNode(id string) (node NodeConf, err error) { + if _, ok := config.Nodes[id]; !ok { + return node, ErrNotFound } - wwlog.Debug("Unmarshalling default config\n") - err = yaml.Unmarshal(defData, &defConf) - if err != nil { - wwlog.Verbose("Couldn't unmarshall defaults from file :%s\n", err) - err = yaml.Unmarshal([]byte(FallBackConf), &defConf) + wwlog.Debug("constructing node: %s", id) + for _, p := range config.Nodes[id].Profiles { + var buf bytes.Buffer + enc := gob.NewEncoder(&buf) + dec := gob.NewDecoder(&buf) + includedProfile, err := config.GetProfile(p) if err != nil { - wwlog.Warn("Could not get any defaults") + return node, err } - } - var defConfNet *NetDevs - if _, ok := defConf["defaultnode"]; ok { - if _, ok := defConf["defaultnode"].NetDevs["dummy"]; ok { - defConfNet = defConf["defaultnode"].NetDevs["dummy"] - } - defConf["defaultnode"].NetDevs = nil - } - - wwlog.Debug("Finding all nodes...") - for nodename, node := range config.Nodes { - var n NodeInfo - - wwlog.Debug("In node loop: %s", nodename) - n.NetDevs = make(map[string]*NetDevEntry) - n.Tags = make(map[string]*Entry) - n.Kernel = new(KernelEntry) - n.Ipmi = new(IpmiEntry) - n.SetDefFrom(defConf["defaultnode"]) - fullname := strings.SplitN(nodename, ".", 2) - if len(fullname) > 1 { - n.ClusterName.SetDefault(fullname[1]) - } - // special handling for profile to get the default one - if len(node.Profiles) == 0 { - n.Profiles.SetSlice([]string{"default"}) - } else { - n.Profiles.SetSlice(node.Profiles) - } - // node explicitly nodename field in NodeConf - n.Id.Set(nodename) - // backward compatibility - for keyname, key := range node.Keys { - node.Tags[keyname] = key - delete(node.Keys, keyname) - } - err = node.Check() + err = enc.Encode(includedProfile) if err != nil { - return nil, fmt.Errorf("node: %s check error: %s", nodename, err) + return node, err } - n.SetFrom(node) - // only now the netdevs start to exist so that default values can be set - for _, netdev := range n.NetDevs { - SetDefFrom(defConfNet, netdev) + err = dec.Decode(&node) + if err != nil { + return node, err } - // backward compatibility - n.Ipmi.Ipaddr.Set(node.IpmiIpaddr) - n.Ipmi.Netmask.Set(node.IpmiNetmask) - n.Ipmi.Port.Set(node.IpmiPort) - n.Ipmi.Gateway.Set(node.IpmiGateway) - n.Ipmi.UserName.Set(node.IpmiUserName) - n.Ipmi.Password.Set(node.IpmiPassword) - n.Ipmi.Interface.Set(node.IpmiInterface) - n.Ipmi.Write.Set(node.IpmiWrite) - n.Kernel.Args.Set(node.KernelArgs) - n.Kernel.Override.Set(node.KernelOverride) - n.Kernel.Override.Set(node.KernelVersion) - // delete deprecated structures so that they do not get unmarshalled - node.IpmiIpaddr = "" - node.IpmiNetmask = "" - node.IpmiGateway = "" - node.IpmiUserName = "" - node.IpmiPassword = "" - node.IpmiInterface = "" - node.IpmiWrite = "" - node.KernelArgs = "" - node.KernelOverride = "" - node.KernelVersion = "" - // Merge Keys into Tags for backwards compatibility - if len(node.Tags) == 0 { - node.Tags = make(map[string]string) - } - - for _, profileName := range n.Profiles.GetSlice() { - if _, ok := config.NodeProfiles[profileName]; !ok { - wwlog.Warn("Profile not found for node '%s': %s", nodename, profileName) - continue - } - // can't call setFrom() as we have to use SetAlt instead of Set for an Entry - wwlog.Verbose("Merging profile into node: %s <- %s", nodename, profileName) - n.SetAltFrom(config.NodeProfiles[profileName], profileName) - } - // set default/primary network is just one network exist - if len(n.NetDevs) >= 1 && !n.PrimaryNetDev.Defined() { - tmpNets := make([]string, 0, len(n.NetDevs)) - for key := range n.NetDevs { - tmpNets = append(tmpNets, key) - } - sort.Strings(tmpNets) - // if a value is present in profile or node, default is not visible - wwlog.Debug("%s setting primary network device: %s", n.Id.Get(), tmpNets[0]) - n.PrimaryNetDev.SetDefault(tmpNets[0]) - } - if dev, ok := n.NetDevs[n.PrimaryNetDev.Get()]; ok { - dev.Primary.SetDefaultB(true) - } - ret = append(ret, n) } + var buf bytes.Buffer + enc := gob.NewEncoder(&buf) + dec := gob.NewDecoder(&buf) + err = enc.Encode(config.Nodes[id]) + if err != nil { + return node, err + } + err = dec.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] + } + } + return +} - sort.Slice(ret, func(i, j int) bool { - if ret[i].ClusterName.Get() < ret[j].ClusterName.Get() { +/* +Get the profile with all the profiles merged in +*/ +func (config *NodeYaml) GetProfile(id string) (profile NodeConf, err error) { + if _, ok := config.NodeProfiles[id]; !ok { + return profile, ErrNotFound + } + var buf bytes.Buffer + enc := gob.NewEncoder(&buf) + dec := gob.NewDecoder(&buf) + // finally merge in the real profile + err = enc.Encode(config.NodeProfiles[id]) + if err != nil { + return profile, err + } + err = dec.Decode(&profile) + if err != nil { + return profile, err + } + // finally set no exported values + profile.id = id + return +} + +/* +Get the profiles from the loaded configuration. This function also merges +the profiles with the given profiles. +*/ +func (config *NodeYaml) FindAllNodes(profiles ...string) (nodeList []NodeConf, err error) { + if len(profiles) == 0 { + for n := range config.Nodes { + profiles = append(profiles, n) + } + } + wwlog.Debug("Finding profiles: %s", profiles) + for _, profileId := range profiles { + node, err := config.GetNode(profileId) + 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 ret[i].ClusterName.Get() == ret[j].ClusterName.Get() { - if ret[i].Id.Get() < ret[j].Id.Get() { + } else if nodeList[i].ClusterName == nodeList[j].ClusterName { + if nodeList[i].id < nodeList[j].id { return true } } return false }) - - return ret, nil + return nodeList, nil } /* Return all profiles as NodeInfo */ -func (config *NodeYaml) FindAllProfiles() ([]NodeInfo, error) { - var ret []NodeInfo - - for name, profile := range config.NodeProfiles { - var p NodeInfo - p.NetDevs = make(map[string]*NetDevEntry) - p.Tags = make(map[string]*Entry) - p.Kernel = new(KernelEntry) - p.Ipmi = new(IpmiEntry) - p.Id.Set(name) - for keyname, key := range profile.Keys { - profile.Tags[keyname] = key - delete(profile.Keys, keyname) +func (config *NodeYaml) FindAllProfiles(profiles ...string) (profileList []NodeConf, err error) { + if len(profiles) == 0 { + for n := range config.NodeProfiles { + profiles = append(profiles, n) } - - p.SetFrom(profile) - p.Ipmi.Ipaddr.Set(profile.IpmiIpaddr) - p.Ipmi.Netmask.Set(profile.IpmiNetmask) - p.Ipmi.Port.Set(profile.IpmiPort) - p.Ipmi.Gateway.Set(profile.IpmiGateway) - p.Ipmi.UserName.Set(profile.IpmiUserName) - p.Ipmi.Password.Set(profile.IpmiPassword) - p.Ipmi.Interface.Set(profile.IpmiInterface) - p.Ipmi.Write.Set(profile.IpmiWrite) - p.Kernel.Args.Set(profile.KernelArgs) - p.Kernel.Override.Set(profile.KernelOverride) - p.Kernel.Override.Set(profile.KernelVersion) - // delete deprecated stuff - profile.IpmiIpaddr = "" - profile.IpmiNetmask = "" - profile.IpmiGateway = "" - profile.IpmiUserName = "" - profile.IpmiPassword = "" - profile.IpmiInterface = "" - profile.IpmiWrite = "" - profile.KernelArgs = "" - profile.KernelOverride = "" - profile.KernelVersion = "" - // Merge Keys into Tags for backwards compatibility - if len(profile.Tags) == 0 { - profile.Tags = make(map[string]string) - } - - ret = append(ret, p) } - sort.Slice(ret, func(i, j int) bool { - if ret[i].ClusterName.Get() < ret[j].ClusterName.Get() { + wwlog.Debug("Finding profiles: %s", profiles) + for _, profileId := range profiles { + 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 ret[i].ClusterName.Get() == ret[j].ClusterName.Get() { - if ret[i].Id.Get() < ret[j].Id.Get() { + } else if profileList[i].ClusterName == profileList[j].ClusterName { + if profileList[i].id < profileList[j].id { return true } } return false }) - return ret, nil + 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 profiles */ func (config *NodeYaml) ListAllProfiles() []string { - var ret []string + var nodeList []string for name := range config.NodeProfiles { - ret = append(ret, name) + nodeList = append(nodeList, name) } - return ret -} - -/* -return a map where the key is the profile id -*/ -func (config *NodeYaml) MapAllProfiles() (retMap map[string]*NodeInfo, err error) { - retMap = make(map[string]*NodeInfo) - profileList, err := config.FindAllProfiles() - if err != nil { - return - } - for _, pr := range profileList { - retMap[pr.Id.Get()] = &pr - } - return -} - -/* -return a map where the key is the node id -*/ -func (config *NodeYaml) MapAllNodes() (retMap map[string]*NodeInfo, err error) { - retMap = make(map[string]*NodeInfo) - nodeList, err := config.FindAllNodes() - if err != nil { - return - } - for _, nd := range nodeList { - retMap[nd.Id.Get()] = &nd - } - return + return nodeList } /* @@ -356,26 +244,25 @@ 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 nodes are found, an error is returned. +If no unconfigured node is found, an error is returned. */ -func (config *NodeYaml) FindDiscoverableNode() (NodeInfo, string, error) { - var ret NodeInfo +func (config *NodeYaml) FindDiscoverableNode() (string, string, error) { nodes, _ := config.FindAllNodes() for _, node := range nodes { - if !node.Discoverable.GetB() { + if !node.Discoverable { continue } - if _, ok := node.NetDevs[node.PrimaryNetDev.Get()]; ok { - return node, node.PrimaryNetDev.Get(), nil + if _, ok := node.NetDevs[node.PrimaryNetDev]; ok { + return node.Id(), node.PrimaryNetDev, nil } for netdev, dev := range node.NetDevs { - if !dev.Hwaddr.Defined() { - return node, netdev, nil + if dev.Hwaddr != "" { + return node.Id(), netdev, nil } } } - return ret, "", errors.New("no unconfigured nodes found") + return "", "", ErrNoUnconfigured } diff --git a/internal/pkg/node/datastructure.go b/internal/pkg/node/datastructure.go index 4907012c..4629ddf8 100644 --- a/internal/pkg/node/datastructure.go +++ b/internal/pkg/node/datastructure.go @@ -1,5 +1,9 @@ package node +import "net" + +const undef string = "UNDEF" + /****** * YAML data representations ******/ @@ -8,7 +12,7 @@ Structure of which goes to disk */ type NodeYaml struct { WWInternal int `yaml:"WW_INTERNAL,omitempty" json:"WW_INTERNAL,omitempty"` - NodeProfiles map[string]*NodeConf + NodeProfiles map[string]*ProfileConf Nodes map[string]*NodeConf } @@ -16,56 +20,47 @@ type NodeYaml struct { NodeConf is the datastructure describing a node and a profile which in disk format. */ type NodeConf struct { - Comment string `yaml:"comment,omitempty" lopt:"comment" comment:"Set arbitrary string comment" json:"comment,omitempty"` - ClusterName string `yaml:"cluster name,omitempty" lopt:"cluster" sopt:"c" comment:"Set cluster group" json:"cluster name,omitempty"` - ContainerName string `yaml:"container name,omitempty" lopt:"container" sopt:"C" comment:"Set container name" json:"container name,omitempty"` - Ipxe string `yaml:"ipxe template,omitempty" lopt:"ipxe" comment:"Set the iPXE template name" json:"ipxe template,omitempty"` - // Deprecated start - // Kernel settings here are deprecated and here for backward compatibility - KernelVersion string `yaml:"kernel version,omitempty" json:"kernel version,omitempty"` - KernelOverride string `yaml:"kernel override,omitempty" json:"kernel override,omitempty"` - KernelArgs string `yaml:"kernel args,omitempty" json:"kernel args,omitempty"` - // Ipmi settings herer are deprecated and here for backward compatibility - IpmiUserName string `yaml:"ipmi username,omitempty" json:"ipmi username,omitempty"` - IpmiPassword string `yaml:"ipmi password,omitempty" json:"ipmi password,omitempty"` - IpmiIpaddr string `yaml:"ipmi ipaddr,omitempty" json:"ipmi ipaddr,omitempty"` - IpmiNetmask string `yaml:"ipmi netmask,omitempty" json:"ipmi netmask,omitempty"` - IpmiPort string `yaml:"ipmi port,omitempty" json:"ipmi port,omitempty"` - IpmiGateway string `yaml:"ipmi gateway,omitempty" json:"ipmi gateway,omitempty"` - IpmiInterface string `yaml:"ipmi interface,omitempty" json:"ipmi interface,omitempty"` - IpmiEscapeChar string `yaml:"ipmi escapechar,omitempty" json:"ipmi escapechar,omitempty"` - IpmiWrite string `yaml:"ipmi write,omitempty" json:"ipmi write,omitempty"` - // Deprecated end - RuntimeOverlay []string `yaml:"runtime overlay,omitempty" lopt:"runtime" sopt:"R" comment:"Set the runtime overlay" json:"runtime overlay,omitempty"` - SystemOverlay []string `yaml:"system overlay,omitempty" lopt:"wwinit" sopt:"O" comment:"Set the system overlay" json:"system overlay,omitempty"` - Kernel *KernelConf `yaml:"kernel,omitempty" json:"kernel,omitempty"` - Ipmi *IpmiConf `yaml:"ipmi,omitempty" json:"ipmi,omitempty"` - Init string `yaml:"init,omitempty" lopt:"init" sopt:"i" comment:"Define the init process to boot the container" json:"init,omitempty"` - Root string `yaml:"root,omitempty" lopt:"root" comment:"Define the rootfs" json:"root,omitempty"` - AssetKey string `yaml:"asset key,omitempty" lopt:"asset" comment:"Set the node's Asset tag (key)" json:"asset key,omitempty"` - Discoverable string `yaml:"discoverable,omitempty" lopt:"discoverable" sopt:"e" comment:"Make discoverable in given network (true/false)" type:"bool" json:"discoverable,omitempty"` - Profiles []string `yaml:"profiles,omitempty" lopt:"profile" sopt:"P" comment:"Set the node's profile members (comma separated)" json:"profiles,omitempty"` - NetDevs map[string]*NetDevs `yaml:"network devices,omitempty" json:"network devices,omitempty"` - Tags map[string]string `yaml:"tags,omitempty" lopt:"tagadd" comment:"base key" json:"tags,omitempty"` - TagsDel []string `yaml:"tagsdel,omitempty" lopt:"tagdel" comment:"remove this tags" json:"tagsdel,omitempty"` // should not go to disk only to wire - Keys map[string]string `yaml:"keys,omitempty" json:"keys,omitempty"` // Reverse compatibility - PrimaryNetDev string `yaml:"primary network,omitempty" lopt:"primarynet" sopt:"p" comment:"Set the primary network interface" json:"primary network,omitempty"` - Disks map[string]*Disk `yaml:"disks,omitempty" json:"disks,omitempty"` - FileSystems map[string]*FileSystem `yaml:"filesystems,omitempty" json:"filesystems,omitempty"` + id string + valid bool // Is set true, if called by the constructor + Discoverable bool `yaml:"discoverable,omitempty" lopt:"discoverable" sopt:"e" comment:"Make discoverable in given network (true/false)"` + AssetKey string `yaml:"asset key,omitempty" lopt:"asset" comment:"Set the node's Asset tag (key)"` + Profiles []string `yaml:"profiles,omitempty" lopt:"profile" sopt:"P" comment:"Set the node's profile members (comma separated)"` + ProfileConf `yaml:"-,inline"` // include all values set in the profile, but inline them in yaml output if these are part of NodeConf +} + +/* +Holds the data which can be set for profiles and nodes. +*/ +type ProfileConf struct { + // exported values + Comment string `yaml:"comment,omitempty" lopt:"comment" comment:"Set arbitrary string comment"` + ClusterName string `yaml:"cluster name,omitempty" lopt:"cluster" sopt:"c" comment:"Set cluster group"` + ContainerName string `yaml:"container name,omitempty" lopt:"container" sopt:"C" comment:"Set container name"` + Ipxe string `yaml:"ipxe template,omitempty" lopt:"ipxe" comment:"Set the iPXE template name"` + RuntimeOverlay []string `yaml:"runtime overlay,omitempty" lopt:"runtime" sopt:"R" comment:"Set the runtime overlay"` + SystemOverlay []string `yaml:"system overlay,omitempty" lopt:"wwinit" sopt:"O" comment:"Set the system overlay"` + Kernel *KernelConf `yaml:"kernel,omitempty"` + Ipmi *IpmiConf `yaml:"ipmi,omitempty"` + Init string `yaml:"init,omitempty" lopt:"init" sopt:"i" comment:"Define the init process to boot the container"` + Root string `yaml:"root,omitempty" lopt:"root" comment:"Define the rootfs" ` + NetDevs map[string]*NetDevs `yaml:"network devices,omitempty"` + Tags map[string]string `yaml:"tags,omitempty"` + PrimaryNetDev string `yaml:"primary network,omitempty" lopt:"primarynet" sopt:"p" comment:"Set the primary network interface"` + Disks map[string]*Disk `yaml:"disks,omitempty"` + FileSystems map[string]*FileSystem `yaml:"filesystems,omitempty"` } type IpmiConf struct { - UserName string `yaml:"username,omitempty" lopt:"ipmiuser" comment:"Set the IPMI username" json:"username,omitempty"` - Password string `yaml:"password,omitempty" lopt:"ipmipass" comment:"Set the IPMI password" json:"password,omitempty"` - Ipaddr string `yaml:"ipaddr,omitempty" lopt:"ipmiaddr" comment:"Set the IPMI IP address" type:"IP" json:"ipaddr,omitempty"` - Netmask string `yaml:"netmask,omitempty" lopt:"ipminetmask" comment:"Set the IPMI netmask" type:"IP" json:"netmask,omitempty"` - Port string `yaml:"port,omitempty" lopt:"ipmiport" comment:"Set the IPMI port" json:"port,omitempty"` - Gateway string `yaml:"gateway,omitempty" lopt:"ipmigateway" comment:"Set the IPMI gateway" type:"IP" json:"gateway,omitempty"` - Interface string `yaml:"interface,omitempty" lopt:"ipmiinterface" comment:"Set the node's IPMI interface (defaults: 'lan')" json:"interface,omitempty"` - EscapeChar string `yaml:"escapechar,omitempty" lopt:"ipmiescapechar" comment:"Set the IPMI escape character (defaults: '~')" json:"escapechar,omitempty"` - Write string `yaml:"write,omitempty" lopt:"ipmiwrite" comment:"Enable the write of impi configuration (true/false)" type:"bool" json:"write,omitempty"` - Tags map[string]string `yaml:"tags,omitempty" lopt:"ipmitagadd" comment:"add ipmitags" json:"tags,omitempty"` - TagsDel []string `yaml:"tagsdel,omitempty" lopt:"ipmitagdel" comment:"remove ipmitags" json:"tagsdel,omitempty"` // should not go to disk only to wire + UserName string `yaml:"username,omitempty" lopt:"ipmiuser" comment:"Set the IPMI username"` + Password string `yaml:"password,omitempty" lopt:"ipmipass" comment:"Set the IPMI password"` + Ipaddr net.IP `yaml:"ipaddr,omitempty" lopt:"ipmiaddr" comment:"Set the IPMI IP address" type:"IP"` + Gateway net.IP `yaml:"gateway,omitempty" lopt:"ipmigateway" comment:"Set the IPMI gateway" type:"IP"` + Netmask net.IPMask `yaml:"netmask,omitempty" lopt:"ipminetmask" comment:"Set the IPMI netmask" type:"IP"` + Port string `yaml:"port,omitempty" lopt:"ipmiport" comment:"Set the IPMI port"` + Interface string `yaml:"interface,omitempty" lopt:"ipmiinterface" comment:"Set the node's IPMI interface (defaults: 'lan')"` + EscapeChar string `yaml:"escapechar,omitempty" lopt:"ipmiescapechar" comment:"Set the IPMI escape character (defaults: '~')"` + Write bool `yaml:"write,omitempty" lopt:"ipmiwrite" comment:"Enable the write of impi configuration (true/false)"` + Tags map[string]string `yaml:"tags,omitempty"` } type KernelConf struct { Version string `yaml:"version,omitempty" json:"version,omitempty"` @@ -75,27 +70,24 @@ type KernelConf struct { type NetDevs struct { Type string `yaml:"type,omitempty" lopt:"type" sopt:"T" comment:"Set device type of given network"` - OnBoot string `yaml:"onboot,omitempty" lopt:"onboot" comment:"Enable/disable network device (true/false)" type:"bool"` + OnBoot bool `yaml:"onboot,omitempty" lopt:"onboot" comment:"Enable/disable network device (true/false)"` Device string `yaml:"device,omitempty" lopt:"netdev" sopt:"N" comment:"Set the device for given network"` Hwaddr string `yaml:"hwaddr,omitempty" lopt:"hwaddr" sopt:"H" comment:"Set the device's HW address for given network" type:"MAC"` - Ipaddr string `yaml:"ipaddr,omitempty" comment:"IPv4 address in given network" sopt:"I" lopt:"ipaddr" type:"IP"` - IpCIDR string `yaml:"ipcidr,omitempty"` - Ipaddr6 string `yaml:"ip6addr,omitempty" lopt:"ipaddr6" comment:"IPv6 address" type:"IP"` - Prefix string `yaml:"prefix,omitempty"` - Netmask string `yaml:"netmask,omitempty" lopt:"netmask" sopt:"M" comment:"Set the networks netmask" type:"IP"` - Gateway string `yaml:"gateway,omitempty" lopt:"gateway" sopt:"G" comment:"Set the node's network device gateway" type:"IP"` + Ipaddr net.IP `yaml:"ipaddr,omitempty" comment:"IPv4 address in given network" sopt:"I" lopt:"ipaddr" type:"IP"` + Ipaddr6 net.IP `yaml:"ip6addr,omitempty" lopt:"ipaddr6" comment:"IPv6 address" type:"IP"` + Prefix net.IP `yaml:"prefix,omitempty"` + Netmask net.IPMask `yaml:"netmask,omitempty" lopt:"netmask" sopt:"M" comment:"Set the networks netmask" type:"IP"` + Gateway net.IP `yaml:"gateway,omitempty" lopt:"gateway" sopt:"G" comment:"Set the node's network device gateway" type:"IP"` MTU string `yaml:"mtu,omitempty" lopt:"mtu" comment:"Set the mtu" type:"uint"` - Primary string `yaml:"primary,omitempty" type:"bool"` - Default string `yaml:"default,omitempty"` /* backward compatibility */ - Tags map[string]string `yaml:"tags,omitempty" lopt:"nettagadd" comment:"network tags"` - TagsDel []string `yaml:"tagsdel,omitempty" lopt:"nettagdel" comment:"delete network tags"` // should not go to disk only to wire + primary bool `yaml:"primary,omitempty"` + Tags map[string]string `yaml:"tags,omitempty"` } /* Holds the disks of a node */ type Disk struct { - WipeTable string `yaml:"wipe_table,omitempty" type:"bool" lopt:"diskwipe" comment:"whether or not the partition tables shall be wiped"` + WipeTable bool `yaml:"wipe_table,omitempty" lopt:"diskwipe" comment:"whether or not the partition tables shall be wiped"` Partitions map[string]*Partition `yaml:"partitions,omitempty"` } @@ -104,14 +96,14 @@ partition definition, the label must be uniq so its used as the key in the Partitions map */ type Partition struct { - Number string `yaml:"number,omitempty" lopt:"partnumber" comment:"set the partition number, if not set next free slot is used"` - SizeMiB string `yaml:"size_mib,omitempty" lopt:"partsize" comment:"set the size of the partition, if not set maximal possible size is used"` - StartMiB string `yaml:"start_mib,omitempty" comment:"the start of the partition"` + Number string `yaml:"number,omitempty" lopt:"partnumber" comment:"set the partition number, if not set next free slot is used" type:"uint"` + SizeMiB string `yaml:"size_mib,omitempty" lopt:"partsize " comment:"set the size of the partition, if not set maximal possible size is used" type:"uint"` + StartMiB string `yaml:"start_mib,omitempty" comment:"the start of the partition" type:"uint"` TypeGuid string `yaml:"type_guid,omitempty" comment:"Linux filesystem data will be used if empty"` Guid string `yaml:"guid,omitempty" comment:"the GPT unique partition GUID"` - WipePartitionEntry string `yaml:"wipe_partition_entry,omitempty" comment:"if true, Ignition will clobber an existing partition if it does not match the config" type:"bool"` - ShouldExist string `yaml:"should_exist,omitempty" lopt:"partcreate" comment:"create partition if not exist" type:"bool"` - Resize string `yaml:"resize,omitempty" comment:" whether or not the existing partition should be resize" type:"bool"` + WipePartitionEntry bool `yaml:"wipe_partition_entry,omitempty" comment:"if true, Ignition will clobber an existing partition if it does not match the config"` + ShouldExist bool `yaml:"should_exist,omitempty" lopt:"partcreate" comment:"create partition if not exist"` + Resize bool `yaml:"resize,omitempty" comment:" whether or not the existing partition should be resize"` } /* @@ -120,122 +112,9 @@ Definition of a filesystem. The device is uniq so its used as key type FileSystem struct { Format string `yaml:"format,omitempty" lopt:"fsformat" comment:"format of the file system"` Path string `yaml:"path,omitempty" lopt:"fspath" comment:"the mount point of the file system"` - WipeFileSystem string `yaml:"wipe_filesystem,omitempty" lopt:"fswipe" comment:"wipe file system at boot" type:"bool"` + WipeFileSystem bool `yaml:"wipe_filesystem,omitempty" lopt:"fswipe" comment:"wipe file system at boot"` Label string `yaml:"label,omitempty" comment:"the label of the filesystem"` Uuid string `yaml:"uuid,omitempty" comment:"the uuid of the filesystem"` Options []string `yaml:"options,omitempty" comment:"any additional options to be passed to the format-specific mkfs utility"` - MountOptions []string `yaml:"mount_options,omitempty" comment:"any special options to be passed to the mount command"` -} - -/****** - * Internal code data representations - ******/ -/* -Holds string values, when accessed via Get, its value -is returned which is the default or if set the value -from the profile or if set the value of the node itself -*/ -type Entry struct { - value []string - altvalue []string - from string - def []string - isSlice bool -} - -/* -NodeInfo is the in memory datastructure, which can containe -a default value, which is overwritten by the overlay from the -overlay (altvalue) which is overwitten by the value of the -node itself, for all values of type Entry. -*/ -type NodeInfo struct { - Id Entry - Comment Entry - ClusterName Entry - ContainerName Entry - Ipxe Entry - Grub Entry - RuntimeOverlay Entry - SystemOverlay Entry - Root Entry - Discoverable Entry - Init Entry //TODO: Finish adding this... - AssetKey Entry - Kernel *KernelEntry - Ipmi *IpmiEntry - Profiles Entry - PrimaryNetDev Entry - NetDevs map[string]*NetDevEntry - Tags map[string]*Entry - Disks map[string]*DiskEntry - FileSystems map[string]*FileSystemEntry -} - -type IpmiEntry struct { - Ipaddr Entry - Netmask Entry - Port Entry - Gateway Entry - UserName Entry - Password Entry - Interface Entry - EscapeChar Entry - Write Entry - Tags map[string]*Entry -} - -type KernelEntry struct { - Override Entry - Args Entry -} - -type NetDevEntry struct { - Type Entry - OnBoot Entry - Device Entry - Hwaddr Entry - Ipaddr Entry - Ipaddr6 Entry - IpCIDR Entry - Prefix Entry - Netmask Entry - Gateway Entry - MTU Entry - Primary Entry - Tags map[string]*Entry -} - -type DiskEntry struct { - WipeTable Entry - Partitions map[string]*PartitionEntry -} - -type PartitionEntry struct { - Number Entry - SizeMiB Entry - StartMiB Entry - TypeGuid Entry - Guid Entry - WipePartitionEntry Entry - ShouldExist Entry - Resize Entry -} - -type FileSystemEntry struct { - Format Entry - Path Entry - WipeFileSystem Entry - Label Entry - Uuid Entry - Options Entry - MountOptions Entry -} - -// string which is printed if no value is set -const NoValue = "--" - -func (e Entry) MarshalText() (buf []byte, err error) { - buf = append(buf, []byte(e.Get())...) - return buf, nil + MountOptions string `yaml:"mount_options,omitempty" comment:"any special options to be passed to the mount command"` } diff --git a/internal/pkg/node/error.go b/internal/pkg/node/error.go new file mode 100644 index 00000000..65fc75cd --- /dev/null +++ b/internal/pkg/node/error.go @@ -0,0 +1,6 @@ +package node + +import "errors" + +var ErrNotFound = errors.New("node/profile not found") +var ErrNoUnconfigured = errors.New("no unconfigured node") diff --git a/internal/pkg/node/flags.go b/internal/pkg/node/flags.go index 0c426997..4b292f5c 100644 --- a/internal/pkg/node/flags.go +++ b/internal/pkg/node/flags.go @@ -1,37 +1,59 @@ package node import ( - "fmt" "net" "reflect" - "strconv" - "strings" "github.com/spf13/cobra" - "github.com/warewulf/warewulf/internal/pkg/util" ) -/* -Create cmd line flags from the NodeConf fields. Returns a []func() where every function -must be called, as the commandline parser returns e.g. netip.IP objects which must be parsedf -back to strings. -*/ -func (nodeConf *NodeConf) CreateFlags(baseCmd *cobra.Command, excludeList []string) (converters []func() error) { - return RecursiveCreateFlags(nodeConf, baseCmd, excludeList) +type NodeConfDel struct { + TagsDel []string `lopt:"tagdel" comment:"add tags"` + IpmiTagsDel []string `lopt:"ipmitagdel" comment:"delete ipmi tags"` + NetTagsDel []string `lopt:"nettagdel" comment:"delete network tags"` + NetDel string `lopt:"netdel" comment:"network to delete"` + DiskDel string `lopt:"diskdel" comment:"delete the disk from the configuration"` + PartDel string `lopt:"partdel" comment:"delete the partition from the configuration"` + FsDel string `lopt:"fsdel" comment:"delete the fs from the configuration"` +} +type NodeConfAdd struct { + TagsAdd map[string]string `lopt:"tagadd" comment:"add tags"` + IpmiTagsAdd map[string]string `lopt:"ipmitagadd" comment:"add ipmi tags"` + NetTagsAdd map[string]string `lopt:"nettagadd" comment:"add network tags"` + Net string `lopt:"netname" comment:"network which is modified"` + DiskName string `lopt:"diskname" comment:"set diskdevice name"` + PartName string `lopt:"partname" comment:"set the partition name so it can be used by a file system"` + FsName string `lopt:"fsname" comment:"set the file system name which must match a partition name"` } -func RecursiveCreateFlags(obj interface{}, baseCmd *cobra.Command, excludeList []string) (converters []func() error) { +/* +Create cmd line flags from the NodeConf fields. Returns a []func() where every function must be called, as the command line parser returns e.g. netip.IP objects which must be parsed +back to strings. +*/ +func (nodeConf *NodeConf) CreateFlags(baseCmd *cobra.Command) { + recursiveCreateFlags(nodeConf, baseCmd) +} + +func (del *NodeConfDel) CreateDelFlags(baseCmd *cobra.Command) { + recursiveCreateFlags(del, baseCmd) + +} +func (add *NodeConfAdd) CreateAddFlags(baseCmd *cobra.Command) { + recursiveCreateFlags(add, baseCmd) + +} + +func recursiveCreateFlags(obj interface{}, baseCmd *cobra.Command) { // now iterate of every field nodeInfoType := reflect.TypeOf(obj) nodeInfoVal := reflect.ValueOf(obj) for i := 0; i < nodeInfoVal.Elem().NumField(); i++ { - if nodeInfoType.Elem().Field(i).Tag.Get("comment") != "" && - !util.InSlice(excludeList, nodeInfoType.Elem().Field(i).Tag.Get("lopt")) { + if nodeInfoType.Elem().Field(i).Tag.Get("comment") != "" { field := nodeInfoVal.Elem().Field(i) - converters = append(converters, createFlags(baseCmd, excludeList, nodeInfoType.Elem().Field(i), &field)...) + createFlags(baseCmd, nodeInfoType.Elem().Field(i), &field) + } else if nodeInfoType.Elem().Field(i).Type.Kind() == reflect.Ptr { - newConv := RecursiveCreateFlags(nodeInfoVal.Elem().Field(i).Interface(), baseCmd, excludeList) - converters = append(converters, newConv...) + recursiveCreateFlags(nodeInfoVal.Elem().Field(i).Interface(), baseCmd) } else if nodeInfoType.Elem().Field(i).Type.Kind() == reflect.Map && nodeInfoType.Elem().Field(i).Type != reflect.TypeOf(map[string]string{}) { @@ -46,174 +68,46 @@ func RecursiveCreateFlags(obj interface{}, baseCmd *cobra.Command, excludeList [ } else { key = nodeInfoVal.Elem().Field(i).MapKeys()[0] } - newConv := RecursiveCreateFlags(nodeInfoVal.Elem().Field(i).MapIndex(key).Interface(), baseCmd, excludeList) - converters = append(converters, newConv...) + recursiveCreateFlags(nodeInfoVal.Elem().Field(i).MapIndex(key).Interface(), baseCmd) + } else if nodeInfoType.Elem().Field(i).Anonymous { + recursiveCreateFlags(nodeInfoVal.Elem().Field(i).Addr().Interface(), baseCmd) } } - return converters } /* Helper function to create the different PersistentFlags() for different types. */ -func createFlags(baseCmd *cobra.Command, excludeList []string, - myType reflect.StructField, myVal *reflect.Value) (converters []func() error) { +func createFlags(baseCmd *cobra.Command, + myType reflect.StructField, myVal *reflect.Value) { if myType.Tag.Get("lopt") != "" { if myType.Type.Kind() == reflect.String { ptr := myVal.Addr().Interface().(*string) - switch myType.Tag.Get("type") { - case "uint": - converters = append(converters, func() error { - if !util.InSlice(GetUnsetVerbs(), *ptr) && *ptr != "" { - _, err := strconv.ParseUint(*ptr, 10, 32) - if err != nil { - return err - } - } - return nil - }) - if myType.Tag.Get("sopt") != "" { - baseCmd.PersistentFlags().StringVarP(ptr, - myType.Tag.Get("lopt"), - myType.Tag.Get("sopt"), - myType.Tag.Get("default"), - myType.Tag.Get("comment")) - } else { - baseCmd.PersistentFlags().StringVar(ptr, - myType.Tag.Get("lopt"), - myType.Tag.Get("default"), - myType.Tag.Get("comment")) - } - case "bool": - /* - Can't use the bool var from pflag as we need the UNSET verbs to be passwd correctly - */ - converters = append(converters, func() error { - if !util.InSlice(GetUnsetVerbs(), *ptr) && *ptr != "" { - if strings.ToLower(*ptr) == "yes" { - *ptr = "true" - return nil - } - if strings.ToLower(*ptr) == "no" { - *ptr = "false" - return nil - } - val, err := strconv.ParseBool(*ptr) - if err != nil { - return fmt.Errorf("commandline option %s needs to be bool", myType.Tag.Get("lopt")) - } - *ptr = strconv.FormatBool(val) - } - return nil - }) - if myType.Tag.Get("sopt") != "" { - baseCmd.PersistentFlags().StringVarP(ptr, - myType.Tag.Get("lopt"), - myType.Tag.Get("sopt"), - "", - myType.Tag.Get("comment")) - } else { - baseCmd.PersistentFlags().StringVar(ptr, - myType.Tag.Get("lopt"), - "", - myType.Tag.Get("comment")) - } - baseCmd.PersistentFlags().Lookup(myType.Tag.Get("lopt")).NoOptDefVal = "true" - case "IP": - converters = append(converters, func() error { - if !util.InSlice(GetUnsetVerbs(), *ptr) && *ptr != "" { - ipval := net.ParseIP(*ptr) - if ipval == nil { - return fmt.Errorf("commandline option %s needs to be an IP address", myType.Tag.Get("lopt")) - } - *ptr = ipval.String() - } - return nil - }) - if myType.Tag.Get("sopt") != "" { - baseCmd.PersistentFlags().StringVarP(ptr, - myType.Tag.Get("lopt"), - myType.Tag.Get("sopt"), - myType.Tag.Get("default"), - myType.Tag.Get("comment")) - } else { - baseCmd.PersistentFlags().StringVar(ptr, - myType.Tag.Get("lopt"), - myType.Tag.Get("default"), - myType.Tag.Get("comment")) - } - case "IPMask": - defaultConv := net.ParseIP(myType.Tag.Get("default")).DefaultMask() - var valueRaw net.IPMask - converters = append(converters, func() error { - if valueRaw != nil { - *ptr = valueRaw.String() - return nil - } else { - return fmt.Errorf("could not parse %s to IP", valueRaw.String()) - } - }) - if myType.Tag.Get("sopt") != "" { - baseCmd.PersistentFlags().IPMaskVarP(&valueRaw, - myType.Tag.Get("lopt"), - myType.Tag.Get("sopt"), - defaultConv, - myType.Tag.Get("comment")) - } else { - baseCmd.PersistentFlags().IPMaskVar(&valueRaw, - myType.Tag.Get("lopt"), - defaultConv, - myType.Tag.Get("comment")) - } - case "MAC": - converters = append(converters, func() error { - if !util.InSlice(GetUnsetVerbs(), *ptr) && *ptr != "" { - myMac, err := net.ParseMAC(*ptr) - if err != nil { - return err - } - *ptr = myMac.String() - } - return nil - }) - if myType.Tag.Get("sopt") != "" { - baseCmd.PersistentFlags().StringVarP(ptr, - myType.Tag.Get("lopt"), - myType.Tag.Get("sopt"), - "", - myType.Tag.Get("comment")) - } else { - baseCmd.PersistentFlags().StringVar(ptr, - myType.Tag.Get("lopt"), - "", - myType.Tag.Get("comment")) - } - default: - if myType.Tag.Get("sopt") != "" { - baseCmd.PersistentFlags().StringVarP(ptr, - myType.Tag.Get("lopt"), - myType.Tag.Get("sopt"), - myType.Tag.Get("default"), - myType.Tag.Get("comment")) - } else { - baseCmd.PersistentFlags().StringVar(ptr, - myType.Tag.Get("lopt"), - myType.Tag.Get("default"), - myType.Tag.Get("comment")) - } + if myType.Tag.Get("sopt") != "" { + baseCmd.PersistentFlags().StringVarP(ptr, + myType.Tag.Get("lopt"), + myType.Tag.Get("sopt"), + myType.Tag.Get("default"), + myType.Tag.Get("comment")) + } else { + baseCmd.PersistentFlags().StringVar(ptr, + myType.Tag.Get("lopt"), + myType.Tag.Get("default"), + myType.Tag.Get("comment")) } + } else if myType.Type == reflect.TypeOf([]string{}) { ptr := myVal.Addr().Interface().(*[]string) if myType.Tag.Get("sopt") != "" { baseCmd.PersistentFlags().StringSliceVarP(ptr, myType.Tag.Get("lopt"), myType.Tag.Get("sopt"), - []string{myType.Tag.Get("default")}, + []string{}, myType.Tag.Get("comment")) - } else if !util.InSlice(excludeList, myType.Tag.Get("lopt")) { + } else { baseCmd.PersistentFlags().StringSliceVar(ptr, myType.Tag.Get("lopt"), - []string{myType.Tag.Get("default")}, + []string{}, myType.Tag.Get("comment")) } @@ -225,14 +119,69 @@ func createFlags(baseCmd *cobra.Command, excludeList []string, myType.Tag.Get("sopt"), map[string]string{}, // empty default! myType.Tag.Get("comment")) - } else if !util.InSlice(excludeList, myType.Tag.Get("lopt")) { + } else { baseCmd.PersistentFlags().StringToStringVar(ptr, myType.Tag.Get("lopt"), map[string]string{}, // empty default! myType.Tag.Get("comment")) + } + } else if myType.Type == reflect.TypeOf(true) { + ptr := myVal.Addr().Interface().(*bool) + if myType.Tag.Get("sopt") != "" { + baseCmd.PersistentFlags().BoolVarP(ptr, + myType.Tag.Get("lopt"), + myType.Tag.Get("sopt"), + false, // empty default! + myType.Tag.Get("comment")) + } else { + baseCmd.PersistentFlags().BoolVar(ptr, + myType.Tag.Get("lopt"), + false, // empty default! + myType.Tag.Get("comment")) + } + } else if myType.Type == reflect.TypeOf(true) { + ptr := myVal.Addr().Interface().(*bool) + if myType.Tag.Get("sopt") != "" { + baseCmd.PersistentFlags().BoolVarP(ptr, + myType.Tag.Get("lopt"), + myType.Tag.Get("sopt"), + false, // empty default! + myType.Tag.Get("comment")) + } else { + baseCmd.PersistentFlags().BoolVar(ptr, + myType.Tag.Get("lopt"), + false, // empty default! + myType.Tag.Get("comment")) + } + } else if myType.Type == reflect.TypeOf(net.IP{}) { + ptr := myVal.Addr().Interface().(*net.IP) + if myType.Tag.Get("sopt") != "" { + baseCmd.PersistentFlags().IPVarP(ptr, + myType.Tag.Get("lopt"), + myType.Tag.Get("sopt"), + net.IP{}, // empty default! + myType.Tag.Get("comment")) + } else { + baseCmd.PersistentFlags().IPVar(ptr, + myType.Tag.Get("lopt"), + net.IP{}, // empty default! + myType.Tag.Get("comment")) + } + } else if myType.Type == reflect.TypeOf(net.IPMask{}) { + ptr := myVal.Addr().Interface().(*net.IPMask) + if myType.Tag.Get("sopt") != "" { + baseCmd.PersistentFlags().IPMaskVarP(ptr, + myType.Tag.Get("lopt"), + myType.Tag.Get("sopt"), + net.IPMask{}, // empty default! + myType.Tag.Get("comment")) + } else { + baseCmd.PersistentFlags().IPMaskVar(ptr, + myType.Tag.Get("lopt"), + net.IPMask{}, // empty default! + myType.Tag.Get("comment")) } } } - return converters } diff --git a/internal/pkg/node/helper.go b/internal/pkg/node/helper.go new file mode 100644 index 00000000..9598c21d --- /dev/null +++ b/internal/pkg/node/helper.go @@ -0,0 +1,16 @@ +package node + +/* +helper functions +*/ + +// string which is printed if no value is set +const NoValue = "--" + +// print NoValue if string is empty +func PrintVal(in string) (out string) { + if in != "" { + return in + } + return NoValue +} diff --git a/internal/pkg/node/ignition.go b/internal/pkg/node/ignition.go index 849c4fbd..4653f437 100644 --- a/internal/pkg/node/ignition.go +++ b/internal/pkg/node/ignition.go @@ -3,6 +3,7 @@ package node import ( "fmt" "sort" + "strconv" types_3_4 "github.com/coreos/ignition/v2/config/v3_2/types" "github.com/coreos/vcontext/path" @@ -12,37 +13,37 @@ import ( /* Create a ignition struct class for ignition */ -func (node *NodeInfo) GetStorage() (stor types_3_4.Storage, err error, rep string) { +func (node *NodeConf) GetStorage() (stor types_3_4.Storage, err error, rep string) { var fileSystems []types_3_4.Filesystem for fsdevice, fs := range node.FileSystems { var mountOptions []types_3_4.MountOption - for _, opt := range fs.MountOptions.GetSlice() { + for _, opt := range fs.MountOptions { mountOptions = append(mountOptions, types_3_4.MountOption(opt)) } var fsOption []types_3_4.FilesystemOption - for _, opt := range fs.Options.GetSlice() { + for _, opt := range fs.Options { fsOption = append(fsOption, types_3_4.FilesystemOption(opt)) } - wipe := fs.WipeFileSystem.GetB() + wipe := fs.WipeFileSystem myFs := types_3_4.Filesystem{ Device: fsdevice, - Path: fs.Path.GetPointer(), + Path: &fs.Path, WipeFilesystem: &wipe, } - if fs.Format.Get() != "" { - myFs.Format = fs.Format.GetPointer() + if fs.Format != "" { + myFs.Format = &fs.Format } - if fs.Label.Get() != "" { - myFs.Label = fs.Label.GetPointer() + if fs.Label != "" { + myFs.Label = &fs.Label } - if fs.MountOptions.Get() != "" { + if fs.MountOptions != "" { myFs.MountOptions = mountOptions } - if fs.Options.Get() != "" { + if len(fs.Options) != 0 { myFs.Options = fsOption } - if fs.Options.Get() != "" { - myFs.UUID = fs.Uuid.GetPointer() + if fs.Uuid != "" { + myFs.UUID = &fs.Uuid } wwlog.Debug("created file system struct: %v", myFs) fileSystems = append(fileSystems, myFs) @@ -54,30 +55,47 @@ func (node *NodeInfo) GetStorage() (stor types_3_4.Storage, err error, rep strin for diskDev, disk := range node.Disks { var partitions []types_3_4.Partition for partlabel, part := range disk.Partitions { - resize := part.Resize.GetB() - shouldExist := part.ShouldExist.GetB() - wipe := part.WipePartitionEntry.GetB() + resize := part.Resize + shouldExist := part.ShouldExist + wipe := part.WipePartitionEntry label := partlabel + var number int + if part.Number != "" { + number, err = strconv.Atoi(part.Number) + if err != nil { + return + } + } myPart := types_3_4.Partition{ Label: &label, - Number: part.Number.GetInt(), + Number: number, ShouldExist: &shouldExist, WipePartitionEntry: &wipe, } - if part.Guid.Get() != "" { - myPart.GUID = part.Guid.GetPointer() + if part.Guid != "" { + myPart.GUID = &part.Guid } - if part.Resize.Get() != "" { + if part.Resize { myPart.Resize = &resize } - if part.SizeMiB.Get() != "" { - myPart.SizeMiB = part.SizeMiB.GetIntPtr() + if part.SizeMiB != "" { + var size int + size, err = strconv.Atoi(part.SizeMiB) + if err != nil { + return + } + myPart.SizeMiB = &size } - if part.StartMiB.Get() != "" { - myPart.StartMiB = part.StartMiB.GetIntPtr() + if part.StartMiB != "" { + var start int + start, err = strconv.Atoi(part.SizeMiB) + if err != nil { + return + } + myPart.StartMiB = &start } - if part.TypeGuid.Get() != "" { - myPart.TypeGUID = part.TypeGuid.GetPointer() + if part.TypeGuid != "" { + myPart.TypeGUID = &part.TypeGuid } partitions = append(partitions, myPart) } @@ -93,7 +111,7 @@ func (node *NodeInfo) GetStorage() (stor types_3_4.Storage, err error, rep strin } return partitions[i].Number < partitions[j].Number }) - wipe := disk.WipeTable.GetB() + wipe := disk.WipeTable disks = append(disks, types_3_4.Disk{ Device: diskDev, Partitions: partitions, @@ -123,7 +141,7 @@ type SimpleIgnitionConfig struct { /* Get a simple config which can be marshalled to json */ -func (node *NodeInfo) GetConfig() (conf SimpleIgnitionConfig, rep string, err error) { +func (node *NodeConf) GetConfig() (conf SimpleIgnitionConfig, rep string, err error) { conf.Storage, err, rep = node.GetStorage() conf.Ignition.Version = "3.1.0" return diff --git a/internal/pkg/node/list.go b/internal/pkg/node/list.go index 4a728ece..1e92346a 100644 --- a/internal/pkg/node/list.go +++ b/internal/pkg/node/list.go @@ -14,66 +14,54 @@ type NodeFields struct { } /* -Get all the info out of NodeInfo. If emptyFields is set true, all fields -are shown not only the ones with effective values +Get all the info out of NodeConf. If emptyFields is set true, all fields are shown not only the ones with effective values */ -func (node *NodeInfo) GetFields(emptyFields bool) (output []NodeFields) { - return recursiveFields(node, emptyFields, "") +func (nodeYml *NodeYaml) GetFields(node NodeConf, emptyFields bool) (output []NodeFields) { + fieldMap := make(map[string]NodeFields) + for _, p := range node.Profiles { + if profile, ok := nodeYml.NodeProfiles[p]; ok { + recursiveFields(profile, emptyFields, "", fieldMap, p) + } + } + recursiveFields(node, emptyFields, "", fieldMap, "") + return output } /* -Internal function which travels through all fields of a NodeInfo and for this +Internal function which travels through all fields of a NodeConf and for this reason needs tb called via interface{} */ -func recursiveFields(obj interface{}, emptyFields bool, prefix string) (output []NodeFields) { +func recursiveFields(obj interface{}, emptyFields bool, prefix string, + fieldMap map[string]NodeFields, source string) { valObj := reflect.ValueOf(obj) typeObj := reflect.TypeOf(obj) for i := 0; i < typeObj.Elem().NumField(); i++ { - if typeObj.Elem().Field(i).Type == reflect.TypeOf(Entry{}) { - myField := valObj.Elem().Field(i).Interface().(Entry) - if emptyFields || myField.Get() != "" { - output = append(output, NodeFields{ + if valObj.Elem().Field(i).IsValid() { + if valObj.Elem().Field(i).String() != "" { + fieldMap[prefix+typeObj.Elem().Field(i).Name] = NodeFields{ Field: prefix + typeObj.Elem().Field(i).Name, - Source: myField.Source(), - Value: myField.Print(), - }) - } - } else if typeObj.Elem().Field(i).Type == reflect.TypeOf(map[string]*Entry{}) { - for key, val := range valObj.Elem().Field(i).Interface().(map[string]*Entry) { - if emptyFields || val.Get() != "" { - output = append(output, NodeFields{ - Field: prefix + typeObj.Elem().Field(i).Name + "[" + key + "]", - Source: val.Source(), - Value: val.Print(), - }) + Source: source, + Value: valObj.Elem().Field(i).String(), + } + } else if emptyFields { + fieldMap[prefix+typeObj.Elem().Field(i).Name] = NodeFields{ + Field: prefix + typeObj.Elem().Field(i).Name + "[]", + Source: source, } - } - if valObj.Elem().Field(i).Len() == 0 && emptyFields { - output = append(output, NodeFields{ - Field: prefix + typeObj.Elem().Field(i).Name + "[]", - }) } } else if typeObj.Elem().Field(i).Type.Kind() == reflect.Map { mapIter := valObj.Elem().Field(i).MapRange() for mapIter.Next() { - nestedOut := recursiveFields(mapIter.Value().Interface(), emptyFields, prefix+typeObj.Elem().Field(i).Name+"["+mapIter.Key().String()+"].") - if len(nestedOut) == 0 { - output = append(output, NodeFields{ - Field: prefix + typeObj.Elem().Field(i).Name + "[" + mapIter.Key().String() + "]", - }) - } else { - output = append(output, nestedOut...) - } + recursiveFields(mapIter.Value().Interface(), + emptyFields, prefix+typeObj.Elem().Field(i).Name+"["+mapIter.Key().String()+"].", fieldMap, source) } if valObj.Elem().Field(i).Len() == 0 && emptyFields { - output = append(output, NodeFields{ + fieldMap[prefix+typeObj.Elem().Field(i).Name] = NodeFields{ Field: prefix + typeObj.Elem().Field(i).Name + "[]", - }) + } } } else if typeObj.Elem().Field(i).Type.Kind() == reflect.Ptr { - nestedOut := recursiveFields(valObj.Elem().Field(i).Interface(), emptyFields, prefix+typeObj.Elem().Field(i).Name+".") - output = append(output, nestedOut...) + recursiveFields(valObj.Elem().Field(i).Interface(), emptyFields, prefix+typeObj.Elem().Field(i).Name+".", fieldMap, source) } } - return } diff --git a/internal/pkg/node/methods.go b/internal/pkg/node/methods.go index e10fb4f9..1a4eeb41 100644 --- a/internal/pkg/node/methods.go +++ b/internal/pkg/node/methods.go @@ -1,23 +1,20 @@ package node import ( - "fmt" + "net" "reflect" "regexp" "sort" - "strconv" "strings" "github.com/warewulf/warewulf/internal/pkg/util" - "github.com/warewulf/warewulf/internal/pkg/wwlog" ) -type sortByName []NodeInfo +type sortByName []NodeConf func (a sortByName) Len() int { return len(a) } func (a sortByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a sortByName) Less(i, j int) bool { return a[i].Id.Print() < a[j].Id.Print() } - +func (a sortByName) Less(i, j int) bool { return a[i].id < a[j].id } func GetUnsetVerbs() []string { return []string{"UNSET", "DELETE", "UNDEF", "undef", "--", "nil", "0.0.0.0"} } @@ -29,18 +26,18 @@ func GetUnsetVerbs() []string { *********/ /* -Filter a given slice of NodeInfo against a given +Filter a given slice of NodeConf against a given regular expression */ -func FilterByName(set []NodeInfo, searchList []string) []NodeInfo { - var ret []NodeInfo - unique := make(map[string]NodeInfo) +func FilterByName(set []NodeConf, searchList []string) []NodeConf { + var ret []NodeConf + unique := make(map[string]NodeConf) if len(searchList) > 0 { for _, search := range searchList { for _, entry := range set { - if match, _ := regexp.MatchString("^"+search+"$", entry.Id.Get()); match { - unique[entry.Id.Get()] = entry + if match, _ := regexp.MatchString("^"+search+"$", entry.id); match { + unique[entry.id] = entry } } } @@ -58,7 +55,7 @@ func FilterByName(set []NodeInfo, searchList []string) []NodeInfo { /* Filter a given map of NodeConf against given regular expression. */ -func FilterMapByName(inputMap map[string]*NodeConf, searchList []string) (retMap map[string]*NodeConf) { +func FilterNodesByName(inputMap map[string]*NodeConf, searchList []string) (retMap map[string]*NodeConf) { retMap = map[string]*NodeConf{} if len(searchList) > 0 { for _, search := range searchList { @@ -72,443 +69,192 @@ func FilterMapByName(inputMap map[string]*NodeConf, searchList []string) (retMap return retMap } -/********** - * - * Sets - * - *********/ - /* -Set value. If argument is 'UNDEF', 'DELETE', -'UNSET" or '--' the value is removed. -N.B. the '--' might never ever happen as '--' -is parsed out by cobra +Filter a given map of NodeConf against given regular expression. */ -func (ent *Entry) Set(val string) { - if val == "" { - return - } - - if util.InSlice(GetUnsetVerbs(), val) { - wwlog.Debug("Removing value for %v", *ent) - ent.value = []string{""} - } else { - ent.value = []string{val} - } -} - -/* -Set bool -*/ -func (ent *Entry) SetB(val bool) { - if val { - ent.value = []string{"true"} - } else { - ent.value = []string{"false"} - } -} - -func (ent *Entry) SetSlice(val []string) { - if len(val) == 0 { - return - } else if len(val) == 1 && val[0] == "" { // check also for an "empty" slice - return - } - ent.isSlice = true - if util.InSlice(GetUnsetVerbs(), val[0]) { - ent.value = []string{} - } else { - ent.value = val - } -} - -/* -Set alternative value -*/ -func (ent *Entry) SetAlt(val string, from string) { - if val == "" { - return - } - ent.altvalue = []string{val} - ent.from = from -} - -/* -Sets alternative bool -*/ -func (ent *Entry) SetAltB(val bool, from string) { - if val { - ent.altvalue = []string{"true"} - ent.from = from - } else { - ent.altvalue = []string{"false"} - ent.from = from - } -} - -/* -Sets alternative slice -*/ -func (ent *Entry) SetAltSlice(val []string, from string) { - if len(val) == 0 { - return - } - ent.isSlice = true - ent.altvalue = append(ent.altvalue, val...) - if ent.from == "" { - ent.from = from - } else { - ent.from = ent.from + "," + from - } -} - -/* -Sets the default value of an entry. -*/ -func (ent *Entry) SetDefault(val string) { - if val == "" { - return - } - ent.def = []string{val} -} - -/* -Set the default entry as slice -*/ -func (ent *Entry) SetDefaultSlice(val []string) { - if len(val) == 0 { - return - } - ent.isSlice = true - ent.def = val -} - -/* -Set default entry as bool -*/ -func (ent *Entry) SetDefaultB(val bool) { - if val { - ent.def = []string{"true"} - } else { - ent.def = []string{"false"} - } -} - -/* -Remove a element from a slice -*/ -func (ent *Entry) SliceRemoveElement(val string) { - util.SliceRemoveElement(ent.value, val) -} - -/********** -* -* Gets -* -*********/ -/* -Gets the the entry of the value in following order -* node value if set -* profile value if set -* default value if set -*/ -func (ent *Entry) Get() string { - if len(ent.value) != 0 { - return ent.value[0] - } - if len(ent.altvalue) != 0 { - return ent.altvalue[0] - } - if len(ent.def) != 0 { - return ent.def[0] - } - return "" -} - -/* -Get the bool value of an entry. -*/ -func (ent *Entry) GetB() bool { - if len(ent.value) > 0 { - return !(strings.ToLower(ent.value[0]) == "false" || - strings.ToLower(ent.value[0]) == "no" || - ent.value[0] == "0") - } else if len(ent.altvalue) > 0 { - return !(strings.ToLower(ent.altvalue[0]) == "false" || - strings.ToLower(ent.altvalue[0]) == "no" || - ent.altvalue[0] == "0") - } else { - return !(len(ent.def) == 0 || - strings.ToLower(ent.def[0]) == "false" || - strings.ToLower(ent.def[0]) == "no" || - ent.def[0] == "0") - } -} - -// returns all negated elemets which are marked with ! as prefix -// from a list -func negList(list []string) (ret []string) { - for _, tok := range list { - if strings.HasPrefix(tok, "~") { - ret = append(ret, tok[1:]) - } - } - return -} - -// clean a list from negated tokens -func cleanList(list []string) (ret []string) { - neg := negList(list) - for _, listTok := range list { - notNegate := true - for _, negTok := range neg { - if listTok == negTok || listTok == "~"+negTok { - notNegate = false +func FilterProfilesByName(inputMap map[string]*ProfileConf, searchList []string) (retMap map[string]*ProfileConf) { + retMap = map[string]*ProfileConf{} + if len(searchList) > 0 { + for _, search := range searchList { + for name, nConf := range inputMap { + if match, _ := regexp.MatchString("^"+search+"$", name); match { + retMap[name] = nConf + } } } - if notNegate { - ret = append(ret, listTok) - } } - return ret + return retMap } /* -Returns a string slice created from a comma separated list of the value. +Creates an NodeConf with the given id. Doesn't add it to the database */ -func (ent *Entry) GetSlice() []string { - retval := append(ent.altvalue, ent.value...) - if len(retval) != 0 { - return cleanList(retval) - } - if len(ent.def) != 0 { - return ent.def - } - return []string{} -} - -/* -Get the real value, not the alternative of default one. -*/ -func (ent *Entry) GetReal() string { - if len(ent.value) == 0 { - return "" - } - return ent.value[0] -} - -/* -Get the real value, not the alternative of default one. -*/ -func (ent *Entry) GetRealSlice() []string { - if len(ent.value) == 0 { - return []string{} - } - return ent.value -} - -/* -true if the entry has set a real value, else false. -*/ -func (ent *Entry) GotReal() bool { - return len(ent.value) != 0 -} - -/* -Get a pointer to the value -*/ -func (ent *Entry) GetPointer() *string { - ret := ent.Get() - return &ret -} - -/* -Try to get a int of a value, 0 if value can't be parsed! -*/ -func (ent *Entry) GetInt() int { - var ret int - if len(ent.value) != 0 { - ret, _ = strconv.Atoi(ent.value[0]) - } else if len(ent.altvalue) != 0 { - ret, _ = strconv.Atoi(ent.altvalue[0]) - } else if len(ent.def) != 0 { - ret, _ = strconv.Atoi(ent.def[0]) - } - return ret -} - -/* -Ptr to int -*/ -func (ent *Entry) GetIntPtr() *int { - ret := ent.GetInt() - return &ret -} - -/********** - * - * Misc - * - *********/ -/* -Gets the the entry of the value in following order -* node value if set -* profile value if set -* default value if set -*/ -func (ent *Entry) Print() string { - if !ent.isSlice { - if len(ent.value) != 0 { - return ent.value[0] - } - if len(ent.altvalue) != 0 { - return ent.altvalue[0] - } - if len(ent.def) != 0 { - return "(" + ent.def[0] + ")" - } - } else { - var ret string - if len(ent.value) != 0 || len(ent.altvalue) != 0 { - combList := append(ent.altvalue, ent.value...) - ret = strings.Join(cleanList(combList), ",") - if len(negList(combList)) > 0 { - ret += " ~{" + strings.Join(negList(combList), ",") + "}" - } - - } - if ret != "" { - return ret - } - if len(ent.def) != 0 { - return "(" + strings.Join(ent.def, ",") + ")" - } - - } - return "--" -} - -/* -Was used for combined stringSlice - -func (ent *Entry) PrintComb() string { - if ent.value != "" && ent.altvalue != "" { - return "[" + ent.value + "," + ent.altvalue + "]" - } - return ent.Print() -} -*/ - -/* -same as GetB() -*/ -func (ent *Entry) PrintB() string { - if len(ent.value) != 0 || len(ent.altvalue) != 0 { - return fmt.Sprintf("%t", ent.GetB()) - } - return fmt.Sprintf("(%t)", ent.GetB()) -} - -/* -Returns SUPERSEDED if value was set per node or -per profile. Else -- is returned. -*/ -func (ent *Entry) Source() string { - if len(ent.value) != 0 && len(ent.altvalue) != 0 { - return "SUPERSEDED" - // return fmt.Sprintf("[%s]", ent.from) - } else if ent.from == "" { - return "--" - } - return ent.from -} - -/* -Check if value was defined. -*/ -func (ent *Entry) Defined() bool { - if len(ent.value) != 0 { - return true - } - if len(ent.altvalue) != 0 { - return true - } - if len(ent.def) != 0 { - return true - } - return false -} - -/* -Create an empty node NodeConf -*/ -func NewConf() (nodeconf NodeConf) { - nodeconf.Ipmi = new(IpmiConf) - nodeconf.Ipmi.Tags = map[string]string{} - nodeconf.Kernel = new(KernelConf) - nodeconf.NetDevs = make(map[string]*NetDevs) - nodeconf.Tags = map[string]string{} +func NewNode(id string) (nodeconf NodeConf) { + nodeconf = EmptyNode() + nodeconf.id = id return nodeconf } /* -Create an empty node NodeInfo +Creates a ProfileConf but doesn't add it to the database. */ -func NewInfo() (nodeInfo NodeInfo) { - nodeInfo.Ipmi = new(IpmiEntry) - nodeInfo.Ipmi.Tags = map[string]*Entry{} - nodeInfo.Kernel = new(KernelEntry) - nodeInfo.NetDevs = make(map[string]*NetDevEntry) - nodeInfo.Tags = make(map[string]*Entry) - return nodeInfo +func EmptyProfile() (profileconf ProfileConf) { + profileconf.Ipmi = new(IpmiConf) + profileconf.Ipmi.Tags = map[string]string{} + profileconf.Kernel = new(KernelConf) + profileconf.NetDevs = make(map[string]*NetDevs) + profileconf.Tags = map[string]string{} + return profileconf } /* -Get a entry by its name +Flattens out a NodeConf, which means if there are no explicit values in *IpmiConf +or *KernelConf, these pointer will set to nil. This will remove something like +ipmi: {} from nodes.conf */ -func GetByName(node interface{}, name string) (string, error) { - valEntry := reflect.ValueOf(node) - entryField := valEntry.Elem().FieldByName(name) - if entryField == (reflect.Value{}) { - return "", fmt.Errorf("couldn't find field with name: %s", name) - } - if entryField.Type() != reflect.TypeOf(Entry{}) { - return "", fmt.Errorf("field %s is not of type node.Entry", name) - } - myEntry := entryField.Interface().(Entry) - return myEntry.Get(), nil +func (info *NodeConf) Flatten() { + recursiveFlatten(info) } /* -Check if the Netdev is empty, so has no values set +Flattens out a ProfileConf, which means if there are no explicit values in *IpmiConf +or *KernelConf, these pointer will set to nil. This will remove something like +ipmi: {} from nodes.conf */ -func ObjectIsEmpty(obj interface{}) bool { - if obj == nil { - return true - } - varType := reflect.TypeOf(obj) - varVal := reflect.ValueOf(obj) - if varType.Kind() == reflect.Ptr && !varVal.IsNil() { - return ObjectIsEmpty(varVal.Elem().Interface()) - } - if varVal.IsZero() { - return true - } - for i := 0; i < varType.NumField(); i++ { - if varType.Field(i).Type.Kind() == reflect.String && !varVal.Field(i).IsZero() { - val := varVal.Field(i).Interface().(string) - if val != "" { - return false +func (info *ProfileConf) Flatten() { + recursiveFlatten(info) +} + +// abstract flatten +func recursiveFlatten(strct interface{}) { + confType := reflect.TypeOf(strct) + confVal := reflect.ValueOf(strct) + for j := 0; j < confType.Elem().NumField(); j++ { + if confVal.Elem().Field(j).Type().Kind() == reflect.Ptr && !confVal.Elem().Field(j).IsNil() { + // iterate now over the ptr fields + setToNil := true + nestedType := reflect.TypeOf(confVal.Elem().Field(j).Interface()) + nestedVal := reflect.ValueOf(confVal.Elem().Field(j).Interface()) + for i := 0; i < nestedType.Elem().NumField(); i++ { + if nestedType.Elem().Field(i).Type.Kind() == reflect.String && + nestedVal.Elem().Field(i).Interface().(string) != "" && + nestedVal.Elem().Field(i).Interface().(string) != undef { + setToNil = false + } else if nestedType.Elem().Field(i).Type == reflect.TypeOf([]string{}) && + len(nestedVal.Elem().Field(i).Interface().([]string)) != 0 { + setToNil = false + } else if nestedType.Elem().Field(i).Type == reflect.TypeOf(map[string]string{}) && + len(nestedVal.Elem().Field(i).Interface().(map[string]string)) != 0 { + setToNil = false + } else if nestedType.Elem().Field(i).Type == reflect.TypeOf(net.IP{}) { + val := nestedVal.Elem().Field(i).Interface().(net.IP) + if len(val) != 0 && !val.IsUnspecified() { + setToNil = false + } + } } - } else if varType.Field(i).Type == reflect.TypeOf(map[string]string{}) { - if len(varVal.Field(i).Interface().(map[string]string)) != 0 { - return false + if setToNil { + confVal.Elem().Field(j).Set(reflect.Zero(confVal.Elem().Field(j).Type())) } - } else if varType.Field(i).Type.Kind() == reflect.Ptr { - if !ObjectIsEmpty(varVal.Field(i).Interface()) { - return false + } else if confType.Elem().Field(j).Anonymous { + recursiveFlatten(confVal.Elem().Field(j).Addr().Interface()) + } else if confType.Elem().Field(j).IsExported() && confType.Elem().Field(j).Type.Kind() == reflect.String { + if confVal.Elem().Field(j).Interface().(string) == undef { + confVal.Elem().Field(j).SetString("") } } } - return true +} + +/* +Create a string slice, where every element represents a yaml entry, used for node/profile edit +in order to get a summary of all available elements +*/ +func UnmarshalConf(obj interface{}, excludeList []string) (lines []string) { + objType := reflect.TypeOf(obj) + // now iterate of every field + for i := 0; i < objType.NumField(); i++ { + if objType.Field(i).Tag.Get("comment") != "" { + if ymlStr, ok := getYamlString(objType.Field(i), excludeList); ok { + lines = append(lines, ymlStr...) + } + } + if objType.Field(i).Type.Kind() == reflect.Ptr && objType.Field(i).Tag.Get("yaml") != "" { + typeLine := objType.Field(i).Tag.Get("yaml") + if len(strings.Split(typeLine, ",")) > 1 { + typeLine = strings.Split(typeLine, ",")[0] + ":" + } + lines = append(lines, typeLine) + nestedLine := UnmarshalConf(reflect.New(objType.Field(i).Type.Elem()).Elem().Interface(), excludeList) + for _, ln := range nestedLine { + lines = append(lines, " "+ln) + } + } else if objType.Field(i).Type.Kind() == reflect.Map && objType.Field(i).Type.Elem().Kind() == reflect.Ptr { + typeLine := objType.Field(i).Tag.Get("yaml") + if len(strings.Split(typeLine, ",")) > 1 { + typeLine = strings.Split(typeLine, ",")[0] + ":" + } + lines = append(lines, typeLine, " element:") + nestedLine := UnmarshalConf(reflect.New(objType.Field(i).Type.Elem().Elem()).Elem().Interface(), excludeList) + for _, ln := range nestedLine { + lines = append(lines, " "+ln) + } + } + } + return lines +} + +/* +Get the string of the yaml tag +*/ +func getYamlString(myType reflect.StructField, excludeList []string) ([]string, bool) { + ymlStr := myType.Tag.Get("yaml") + if len(strings.Split(ymlStr, ",")) > 1 { + ymlStr = strings.Split(ymlStr, ",")[0] + } + if util.InSlice(excludeList, ymlStr) { + return []string{""}, false + } else if myType.Tag.Get("comment") == "" && myType.Type.Kind() == reflect.String { + return []string{""}, false + } + if myType.Type.Kind() == reflect.String { + fieldType := myType.Tag.Get("type") + if fieldType == "" { + fieldType = "string" + } + ymlStr += ": " + fieldType + return []string{ymlStr}, true + } else if myType.Type == reflect.TypeOf([]string{}) { + return []string{ymlStr + ":", " - string"}, true + } else if myType.Type == reflect.TypeOf(map[string]string{}) { + return []string{ymlStr + ":", " key: value"}, true + } else if myType.Type.Kind() == reflect.Ptr { + return []string{ymlStr + ":"}, true + } + return []string{ymlStr}, true +} + +/* +Getters for unexported fields +*/ + +/* +Returns the id of the node/profile +*/ +func (node *NodeConf) Id() string { + return node.id +} + +/* +Returns if the node is a valid in the database +*/ +func (node *NodeConf) Valid() bool { + return node.valid +} + +/* +Check if the netdev is the primary one +*/ +func (dev *NetDevs) Primary() bool { + return dev.primary } diff --git a/internal/pkg/node/modifiers.go b/internal/pkg/node/modifiers.go index 69202c25..1f6b7d0c 100644 --- a/internal/pkg/node/modifiers.go +++ b/internal/pkg/node/modifiers.go @@ -9,37 +9,26 @@ import ( "github.com/warewulf/warewulf/internal/pkg/wwlog" ) -/**** - * - * NODE MODIFIERS - * -****/ - -func (config *NodeYaml) AddNode(nodeID string) (NodeInfo, error) { - var node NodeConf - var n NodeInfo - +/* +Add a node with the given ID and return a pointer to it +*/ +func (config *NodeYaml) AddNode(nodeID string) (*NodeConf, error) { + newNode := NewNode(nodeID) wwlog.Verbose("Adding new node: %s", nodeID) - if _, ok := config.Nodes[nodeID]; ok { - return n, errors.New("Nodename already exists: " + nodeID) + return nil, errors.New("nodename already exists: " + nodeID) + } else { + config.Nodes[nodeID] = &newNode } - - config.Nodes[nodeID] = &node - config.Nodes[nodeID].Profiles = []string{"default"} - config.Nodes[nodeID].NetDevs = make(map[string]*NetDevs) - n.Id.Set(nodeID) - n.Profiles.SetSlice([]string{"default"}) - n.NetDevs = make(map[string]*NetDevEntry) - n.Ipmi = new(IpmiEntry) - n.Kernel = new(KernelEntry) - - return n, nil + return &newNode, nil } +/* +delete node with the given id +*/ func (config *NodeYaml) DelNode(nodeID string) error { if _, ok := config.Nodes[nodeID]; !ok { - return errors.New("Nodename does not exist: " + nodeID) + return errors.New("nodename does not exist: " + nodeID) } wwlog.Verbose("Deleting node: %s", nodeID) @@ -49,79 +38,33 @@ func (config *NodeYaml) DelNode(nodeID string) error { } /* -update the node in the database +Add a node with the given ID and return a pointer to it */ -func (config *NodeYaml) NodeUpdate(node NodeInfo) error { - nodeID := node.Id.Get() - wwlog.Debug("updating node %s: %v", nodeID, node) - if _, ok := config.Nodes[nodeID]; !ok { - return errors.New("Nodename does not exist: " + nodeID) +func (config *NodeYaml) AddProfile(profileId string) (*ProfileConf, error) { + profile := NewProfile(profileId) + wwlog.Verbose("adding new profile: %s", profileId) + if _, ok := config.NodeProfiles[profileId]; ok { + return nil, errors.New("profile already exists: " + profileId) + } else { + config.NodeProfiles[profileId] = &profile } - // maps may have deleted elements which will not be updated - // so we delete the node and call GetRealFrom on an empty NodeConv - delete(config.Nodes, nodeID) - config.Nodes[nodeID] = new(NodeConf) - config.Nodes[nodeID].GetRealFrom(node) - return nil -} - -/**** - * - * PROFILE MODIFIERS - * -****/ - -func (config *NodeYaml) AddProfile(profileID string) (NodeInfo, error) { - var node NodeConf - var n NodeInfo - - wwlog.Verbose("Adding new profile: %s", profileID) - - if _, ok := config.NodeProfiles[profileID]; ok { - return n, errors.New("Profile name already exists: " + profileID) - } - - config.NodeProfiles[profileID] = &node - config.NodeProfiles[profileID].NetDevs = make(map[string]*NetDevs) - n.Id.Set(profileID) - n.NetDevs = make(map[string]*NetDevEntry) - - return n, nil -} - -func (config *NodeYaml) DelProfile(profileID string) error { - if _, ok := config.NodeProfiles[profileID]; !ok { - return errors.New("Profile does not exist: " + profileID) - } - - wwlog.Verbose("Deleting profile: %s", profileID) - delete(config.NodeProfiles, profileID) - - return nil + return &profile, nil } /* -Update the the config for the given profile so that it can unmarshalled. +delete node with the given id */ -func (config *NodeYaml) ProfileUpdate(profile NodeInfo) error { - profileID := profile.Id.Get() - - if _, ok := config.NodeProfiles[profileID]; !ok { - return errors.New("Profile name does not exist: " + profileID) +func (config *NodeYaml) DelProfile(nodeID string) error { + if _, ok := config.Nodes[nodeID]; !ok { + return errors.New("profile does not exist: " + nodeID) } - // maps may have deleted elements which will not be updated - // so we delete the profile and call GetRealFrom on an empty NodeConv - delete(config.NodeProfiles, profileID) - config.NodeProfiles[profileID] = new(NodeConf) - config.NodeProfiles[profileID].GetRealFrom(profile) + + wwlog.Verbose("deleting profile: %s", nodeID) + delete(config.Nodes, nodeID) + return nil } -/**** - * - * PERSISTENCE - * -****/ /* Write the the NodeYaml to disk. */ @@ -144,8 +87,10 @@ func (config *NodeYaml) Persist() error { return nil } -// Dump returns a YAML document representing the nodeDb -// instance. Passes through any errors generated by yaml.Marshal. +/* +Dump returns a YAML document representing the nodeDb +instance. Passes through any errors generated by yaml.Marshal. +*/ func (config *NodeYaml) Dump() ([]byte, error) { // flatten out profiles and nodes for _, val := range config.NodeProfiles { diff --git a/internal/pkg/node/transformers.go b/internal/pkg/node/transformers.go deleted file mode 100644 index adc9b513..00000000 --- a/internal/pkg/node/transformers.go +++ /dev/null @@ -1,397 +0,0 @@ -package node - -import ( - "fmt" - "reflect" - "strings" - - "github.com/warewulf/warewulf/internal/pkg/util" - "github.com/warewulf/warewulf/internal/pkg/wwlog" -) - -/* -Populates a NodeConf struct (the one which goes to disk) from a -NodeInfo (which just lives in memory), with the values from all -the underlying entries using GetReal, so just the explicit values -go do disk. -*/ -func (nodeConf *NodeConf) GetRealFrom(nodeInfo NodeInfo) { - recursiveGetter(&nodeInfo, nodeConf, (*Entry).GetReal, (*Entry).GetRealSlice) -} - -/* -Populates a NodeConf struct from a NodeInfo, with the combined -values from the underlying entries using Get. -*/ -func (nodeConf *NodeConf) GetFrom(nodeInfo NodeInfo) { - recursiveGetter(&nodeInfo, nodeConf, (*Entry).Get, (*Entry).GetSlice) -} - -/* -Abstract function which populates a NodeConf from the given NodeInfo -via getter functions. Calls recursive itself for nested structures. -Panics if the NodeConf has fields which are not type of string,[]string,map[string]*ptr -*/ -func recursiveGetter( - source, target interface{}, - getter func(*Entry) string, - getterSlice func(*Entry) []string) { - sourceValue := reflect.ValueOf(source) - targetType := reflect.TypeOf(target) - targetValue := reflect.ValueOf(target) - if targetValue.Elem().Kind() == reflect.Struct && sourceValue.Elem().Kind() == reflect.Struct { - for i := 0; i < targetType.Elem().NumField(); i++ { - sourceValueMatched := sourceValue.Elem().FieldByName(targetType.Elem().Field(i).Name) - if sourceValueMatched.IsValid() { - if sourceValueMatched.Type() == reflect.TypeOf(Entry{}) { - // get the fields which are part of the struct - switch targetValue.Elem().Field(i).Type() { - case reflect.TypeOf(""): - newValue := (targetValue.Elem().Field(i).Addr().Interface()).(*string) - source := sourceValueMatched.Interface().(Entry) - *newValue = getter(&source) - case reflect.TypeOf([]string{}): - newValue := (targetValue.Elem().Field(i).Addr().Interface()).(*[]string) - source := sourceValueMatched.Interface().(Entry) - *newValue = getterSlice(&source) - default: - panic(fmt.Errorf("can't convert an Entry to %s", targetValue.Elem().Field(i).Type())) - } - } else if sourceValueMatched.Kind() == reflect.Ptr { - // if we get a pointer, initialize if empty and then have a recursive call - if targetValue.Elem().Field(i).IsZero() { - targetValue.Elem().Field(i).Set(reflect.New(targetType.Elem().Field(i).Type.Elem())) - } - recursiveGetter(sourceValueMatched.Interface(), targetValue.Elem().Field(i).Interface(), getter, getterSlice) - } else if sourceValueMatched.Type().Kind() == reflect.Map { - if targetValue.Elem().Field(i).IsZero() { - targetValue.Elem().Field(i).Set(reflect.MakeMap(targetType.Elem().Field(i).Type)) - } - sourceIter := sourceValueMatched.MapRange() - if sourceValueMatched.Type() == reflect.TypeOf(map[string]*Entry{}) { - // go over a simple map with strings - for sourceIter.Next() { - if !targetValue.Elem().Field(i).MapIndex(sourceIter.Key()).IsValid() { - // Only write entries for which have real values. This matters for - // tags, as empty map elements can be created without this check - // The alternative was following check: - // if ((sourceIter.Value().Interface()).(*Entry)).GotReal() { - // but this one failed for tags in templates - str := getter((sourceIter.Value().Interface()).(*Entry)) - if str != "" { - targetValue.Elem().Field(i).SetMapIndex(sourceIter.Key(), reflect.ValueOf(str)) - } - } - } - } else { - // now the complicated map which contains pointers to objects - for sourceIter.Next() { - if !targetValue.Elem().Field(i).MapIndex(sourceIter.Key()).IsValid() { - newPtr := reflect.New(targetType.Elem().Field(i).Type.Elem().Elem()) - targetValue.Elem().Field(i).SetMapIndex(sourceIter.Key(), newPtr) - } - recursiveGetter(sourceIter.Value().Interface(), targetValue.Elem().Field(i).MapIndex(sourceIter.Key()).Interface(), getter, getterSlice) - - } - } - } - } - } - } -} - -/* -Populates all fields of NodeInfo with Set from the -values of NodeConf. -*/ -func (node *NodeInfo) SetFrom(n *NodeConf) { - setWrap := func(entr *Entry, val string, nameArg string) { - entr.Set(val) - } - setSliceWrap := func(entr *Entry, val []string, nameArg string) { - entr.SetSlice(val) - } - recursiveSetter(n, node, "", setWrap, setSliceWrap) -} - -/* -Populates all fields of NodeInfo with SetAlt from the -values of NodeConf. The string profileName is used to -determine from which source/NodeInfo the entry came -from. -*/ -func (node *NodeInfo) SetAltFrom(n *NodeConf, profileName string) { - recursiveSetter(n, node, profileName, (*Entry).SetAlt, (*Entry).SetAltSlice) -} - -/* -Populates all fields of NodeInfo with SetDefault from the -values of NodeConf. -*/ -func (node *NodeInfo) SetDefFrom(n *NodeConf) { - setWrap := func(entr *Entry, val string, nameArg string) { - entr.SetDefault(val) - } - setSliceWrap := func(entr *Entry, val []string, nameArg string) { - entr.SetDefaultSlice(val) - } - recursiveSetter(n, node, "", setWrap, setSliceWrap) -} - -func SetDefFrom(source, target interface{}) { - setWrap := func(entr *Entry, val string, nameArg string) { - entr.SetDefault(val) - } - setSliceWrap := func(entr *Entry, val []string, nameArg string) { - entr.SetDefaultSlice(val) - } - recursiveSetter(source, target, "", setWrap, setSliceWrap) - -} - -/* -Abstract function which populates a NodeInfo from a NodeConf via -setter functions. Panics if other type than string, []string *ptr is used in NodeConf. -*/ -func recursiveSetter(source, target interface{}, nameArg string, setter func(*Entry, string, string), - setterSlice func(*Entry, []string, string)) { - sourceValue := reflect.ValueOf(source) - targetType := reflect.TypeOf(target) - targetValue := reflect.ValueOf(target) - if targetValue.Elem().Kind() == reflect.Struct && sourceValue.Elem().Kind() == reflect.Struct { - for i := 0; i < targetType.Elem().NumField(); i++ { - sourceValueMatched := sourceValue.Elem().FieldByName(targetType.Elem().Field(i).Name) - if sourceValueMatched.IsValid() { - if targetValue.Elem().Field(i).Type() == reflect.TypeOf(Entry{}) { - // get the fields which are part of the struct - switch sourceValueMatched.Type() { - case reflect.TypeOf(""): - setter(targetValue.Elem().Field(i).Addr().Interface().(*Entry), sourceValueMatched.String(), nameArg) - case reflect.TypeOf([]string{}): - setterSlice(targetValue.Elem().Field(i).Addr().Interface().(*Entry), sourceValueMatched.Interface().([]string), nameArg) - default: - panic(fmt.Errorf("can't convert an Entry to %s", targetValue.Elem().Field(i).Type())) - } - } else if sourceValueMatched.Kind() == reflect.Ptr { - // if we get a pointer, initialize if empty and then have a recursive call - if targetValue.Elem().Field(i).IsZero() { - targetValue.Elem().Field(i).Set(reflect.New(targetType.Elem().Field(i).Type.Elem())) - } - recursiveSetter(sourceValueMatched.Interface(), targetValue.Elem().Field(i).Interface(), nameArg, setter, setterSlice) - } else if sourceValueMatched.Type().Kind() == reflect.Map { - if targetValue.Elem().Field(i).IsZero() { - targetValue.Elem().Field(i).Set(reflect.MakeMap(targetType.Elem().Field(i).Type)) - } - // delete a map element which is only in the target - if targetValue.Elem().Field(i).Len() > 0 && targetValue.Elem().Field(i).Len() < 0 { - sourceIter := sourceValueMatched.MapRange() - targetIter := targetValue.Elem().Field(i).MapRange() - for targetIter.Next() { - sameKey := false - for sourceIter.Next() { - if sourceIter.Key() == targetIter.Key() { - sameKey = true - } - } - if !sameKey { - targetValue.Elem().Field(i).SetMapIndex(targetIter.Key(), reflect.Value{}) - } - } - } - sourceIter := sourceValueMatched.MapRange() - if sourceValueMatched.Type().Elem() == reflect.TypeOf("") { - // go over a simple map with strings - for sourceIter.Next() { - if !targetValue.Elem().Field(i).MapIndex(sourceIter.Key()).IsValid() { - newEntr := new(Entry) - setter(newEntr, sourceIter.Value().String(), nameArg) - targetValue.Elem().Field(i).SetMapIndex(sourceIter.Key(), reflect.ValueOf(newEntr)) - } else { - // update the entry with latest value - if entry, ok := targetValue.Elem().Field(i).MapIndex(sourceIter.Key()).Interface().(*Entry); ok { - setter(entry, sourceIter.Value().String(), nameArg) - } - } - } - } else { - // now the complicated map which contains pointers to objects - for sourceIter.Next() { - if !targetValue.Elem().Field(i).MapIndex(sourceIter.Key()).IsValid() { - newPtr := reflect.New(targetType.Elem().Field(i).Type.Elem().Elem()) - targetValue.Elem().Field(i).SetMapIndex(sourceIter.Key(), newPtr) - } - recursiveSetter(sourceIter.Value().Interface(), targetValue.Elem().Field(i).MapIndex(sourceIter.Key()).Interface(), nameArg, setter, setterSlice) - - } - } - } - } - } - } - -} - -/* -Flattens out a NodeConf, which means if there are no explicit values in *IpmiConf -or *KernelConf, these pointer will set to nil. This will remove something like -ipmi: {} from nodes.conf -*/ -func (info *NodeConf) Flatten() { - recursiveFlatten(info) -} -func recursiveFlatten(strct interface{}) { - confType := reflect.TypeOf(strct) - confVal := reflect.ValueOf(strct) - for j := 0; j < confType.Elem().NumField(); j++ { - if confVal.Elem().Field(j).Type().Kind() == reflect.Ptr && !confVal.Elem().Field(j).IsNil() { - // iterate now over the ptr fields - setToNil := true - nestedType := reflect.TypeOf(confVal.Elem().Field(j).Interface()) - nestedVal := reflect.ValueOf(confVal.Elem().Field(j).Interface()) - for i := 0; i < nestedType.Elem().NumField(); i++ { - if nestedType.Elem().Field(i).Type.Kind() == reflect.String && - nestedVal.Elem().Field(i).Interface().(string) != "" { - setToNil = false - } else if nestedType.Elem().Field(i).Type == reflect.TypeOf([]string{}) && - len(nestedVal.Elem().Field(i).Interface().([]string)) != 0 { - setToNil = false - } else if nestedType.Elem().Field(i).Type == reflect.TypeOf(map[string]string{}) && - len(nestedVal.Elem().Field(i).Interface().(map[string]string)) != 0 { - setToNil = false - } - } - if setToNil { - confVal.Elem().Field(j).Set(reflect.Zero(confVal.Elem().Field(j).Type())) - } - } - } -} - -/* -Create a string slice, where every element represents a yaml entry, used for node/profile edit -in order to get a summary of all available elements -*/ -func UnmarshalConf(obj interface{}, excludeList []string) (lines []string) { - objType := reflect.TypeOf(obj) - // now iterate of every field - for i := 0; i < objType.NumField(); i++ { - if objType.Field(i).Tag.Get("comment") != "" { - if ymlStr, ok := getYamlString(objType.Field(i), excludeList); ok { - lines = append(lines, ymlStr...) - } - } - if objType.Field(i).Type.Kind() == reflect.Ptr && objType.Field(i).Tag.Get("yaml") != "" { - typeLine := objType.Field(i).Tag.Get("yaml") - if len(strings.Split(typeLine, ",")) > 1 { - typeLine = strings.Split(typeLine, ",")[0] + ":" - } - lines = append(lines, typeLine) - nestedLine := UnmarshalConf(reflect.New(objType.Field(i).Type.Elem()).Elem().Interface(), excludeList) - for _, ln := range nestedLine { - lines = append(lines, " "+ln) - } - } else if objType.Field(i).Type.Kind() == reflect.Map && objType.Field(i).Type.Elem().Kind() == reflect.Ptr { - typeLine := objType.Field(i).Tag.Get("yaml") - if len(strings.Split(typeLine, ",")) > 1 { - typeLine = strings.Split(typeLine, ",")[0] + ":" - } - lines = append(lines, typeLine, " element:") - nestedLine := UnmarshalConf(reflect.New(objType.Field(i).Type.Elem().Elem()).Elem().Interface(), excludeList) - for _, ln := range nestedLine { - lines = append(lines, " "+ln) - } - } - } - return lines -} - -/* -Get the string of the yaml tag -*/ - -func getYamlString(myType reflect.StructField, excludeList []string) ([]string, bool) { - ymlStr := myType.Tag.Get("yaml") - if len(strings.Split(ymlStr, ",")) > 1 { - ymlStr = strings.Split(ymlStr, ",")[0] - } - if util.InSlice(excludeList, ymlStr) { - return []string{""}, false - } else if myType.Tag.Get("comment") == "" && myType.Type.Kind() == reflect.String { - return []string{""}, false - } - if myType.Type.Kind() == reflect.String { - fieldType := myType.Tag.Get("type") - if fieldType == "" { - fieldType = "string" - } - ymlStr += ": " + fieldType - return []string{ymlStr}, true - } else if myType.Type == reflect.TypeOf([]string{}) { - return []string{ymlStr + ":", " - string"}, true - } else if myType.Type == reflect.TypeOf(map[string]string{}) { - return []string{ymlStr + ":", " key: value"}, true - } else if myType.Type.Kind() == reflect.Ptr { - return []string{ymlStr + ":"}, true - } - return []string{ymlStr}, true -} - -/* -Set the field of the NodeConf with the given lopt name, returns true if the -field was found. String slices must be comma separated. Network must have the form -net.$NETNAME.lopt or netname.$NETNAME.lopt -*/ -func (nodeConf *NodeConf) SetLopt(lopt string, value string) (found bool) { - found = false - nodeInfoType := reflect.TypeOf(nodeConf) - nodeInfoVal := reflect.ValueOf(nodeConf) - // try to find the normal fields, networks come later - for i := 0; i < nodeInfoVal.Elem().NumField(); i++ { - if nodeInfoType.Elem().Field(i).Tag.Get("lopt") == lopt { - if nodeInfoType.Elem().Field(i).Type.Kind() == reflect.String { - wwlog.Verbose("Found lopt %s mapping to %s, setting to %s\n", - lopt, nodeInfoType.Elem().Field(i).Name, value) - confVal := nodeInfoVal.Elem().Field(i).Addr().Interface().(*string) - *confVal = value - found = true - } else if nodeInfoType.Elem().Field(i).Type == reflect.TypeOf([]string{}) { - wwlog.Verbose("Found lopt %s mapping to %s, setting to %s\n", - lopt, nodeInfoType.Elem().Field(i).Name, value) - confVal := nodeInfoVal.Elem().Field(i).Addr().Interface().(*[]string) - *confVal = strings.Split(value, ",") - found = true - } - } - } - // check network - loptSlice := strings.Split(lopt, ".") - wwlog.Debug("Trying to get network out of %s\n", loptSlice) - if !found && len(loptSlice) == 3 && (loptSlice[0] == "net" || loptSlice[0] == "network" || loptSlice[0] == "netname") { - if nodeConf.NetDevs == nil { - nodeConf.NetDevs = make(map[string]*NetDevs) - } - if nodeConf.NetDevs[loptSlice[1]] == nil { - nodeConf.NetDevs[loptSlice[1]] = new(NetDevs) - } - netInfoType := reflect.TypeOf(nodeConf.NetDevs[loptSlice[1]]) - netInfoVal := reflect.ValueOf(nodeConf.NetDevs[loptSlice[1]]) - for i := 0; i < netInfoVal.Elem().NumField(); i++ { - if netInfoType.Elem().Field(i).Tag.Get("lopt") == loptSlice[2] { - if netInfoType.Elem().Field(i).Type.Kind() == reflect.String { - wwlog.Verbose("Found lopt %s for network %s mapping to %s, setting to %s\n", - lopt, loptSlice[1], netInfoType.Elem().Field(i).Name, value) - confVal := netInfoVal.Elem().Field(i).Addr().Interface().(*string) - *confVal = value - found = true - } else if netInfoType.Elem().Field(i).Type == reflect.TypeOf([]string{}) { - wwlog.Verbose("Found lopt %s for network %s mapping to %s, setting to %s\n", - lopt, loptSlice[1], netInfoType.Elem().Field(i).Name, value) - confVal := netInfoVal.Elem().Field(i).Addr().Interface().(*[]string) - *confVal = strings.Split(value, ",") - found = true - } - } - } - } - return found -} diff --git a/internal/pkg/node/util.go b/internal/pkg/node/util.go index 8e1273c3..213b797d 100644 --- a/internal/pkg/node/util.go +++ b/internal/pkg/node/util.go @@ -3,61 +3,93 @@ package node import ( "errors" "net" + "reflect" "strings" ) /* -get node by its hardware/MAC address, return error otherwise +Gets a node by its hardware(mac) address */ -func (config *NodeYaml) FindByHwaddr(hwa string) (NodeInfo, error) { +func (config *NodeYaml) FindByHwaddr(hwa string) (NodeConf, error) { if _, err := net.ParseMAC(hwa); err != nil { - return NodeInfo{}, errors.New("invalid hardware address: " + hwa) + return NodeConf{}, errors.New("invalid hardware address: " + hwa) } - - var ret NodeInfo - - n, _ := config.FindAllNodes() - - for _, node := range n { + nodeList, _ := config.FindAllNodes() + for _, node := range nodeList { for _, dev := range node.NetDevs { - if strings.EqualFold(dev.Hwaddr.Get(), hwa) { + if strings.EqualFold(dev.Hwaddr, hwa) { return node, nil } } } - return ret, errors.New("No nodes found with HW Addr: " + hwa) + return NodeConf{}, ErrNotFound } /* -get node by its ip address, return error otherwise +Find a node by its ip address */ -func (config *NodeYaml) FindByIpaddr(ipaddr string) (NodeInfo, error) { - if addr := net.ParseIP(ipaddr); addr == nil { - return NodeInfo{}, errors.New("invalid IP:" + ipaddr) - } else { - ipaddr = addr.String() +func (config *NodeYaml) FindByIpaddr(ipaddr string) (NodeConf, error) { + addr := net.ParseIP(ipaddr) + if addr == nil { + return NodeConf{}, errors.New("invalid IP:" + ipaddr) } - var ret NodeInfo - - n, _ := config.FindAllNodes() - - for _, node := range n { + nodeList, err := config.FindAllNodes() + if err != nil { + return NodeConf{}, err + } + for _, node := range nodeList { for _, dev := range node.NetDevs { - if dev.Ipaddr.Get() == ipaddr { + if dev.Ipaddr.Equal(addr) { return node, nil } } } - return ret, errors.New("No nodes found with IP Addr: " + ipaddr) + return NodeConf{}, ErrNotFound } -// Return just the node list as string slice -func (config *NodeYaml) NodeList() []string { - ret := make([]string, len(config.Nodes)) - for key := range config.Nodes { - ret = append(ret, key) +/* +Check if the Object is empty, has no valid values +*/ +func ObjectIsEmpty(obj interface{}) bool { + if obj == nil { + return true } - return ret + varType := reflect.TypeOf(obj) + varVal := reflect.ValueOf(obj) + if varType.Kind() == reflect.Ptr && !varVal.IsNil() { + return ObjectIsEmpty(varVal.Elem().Interface()) + } + if varVal.IsZero() { + return true + } + for i := 0; i < varType.NumField(); i++ { + if varType.Field(i).Type.Kind() == reflect.String && !varVal.Field(i).IsZero() { + val := varVal.Field(i).Interface().(string) + if val != "" { + return false + } + } else if varType.Field(i).Type == reflect.TypeOf(map[string]string{}) { + if len(varVal.Field(i).Interface().(map[string]string)) != 0 { + return false + } + } else if varType.Field(i).Type.Kind() == reflect.Ptr { + if !ObjectIsEmpty(varVal.Field(i).Interface()) { + return false + } + } else if varType.Field(i).Type == reflect.TypeOf(net.IP{}) { + val := varVal.Field(i).Interface().(net.IP) + if len(val) != 0 && !val.IsUnspecified() { + return false + } + } else if varType.Field(i).Type == reflect.TypeOf(net.IPMask{}) { + o, b := varVal.Field(i).Interface().(net.IPMask).Size() + if o == 0 && b == 0 { + return false + } + } + + } + return true } diff --git a/internal/pkg/oci/defaults.go b/internal/pkg/oci/defaults.go index 59a8f461..24643e5c 100644 --- a/internal/pkg/oci/defaults.go +++ b/internal/pkg/oci/defaults.go @@ -1,8 +1,9 @@ package oci import ( - warewulfconf "github.com/warewulf/warewulf/internal/pkg/config" "path/filepath" + + warewulfconf "github.com/warewulf/warewulf/internal/pkg/config" ) var defaultCachePath = filepath.Join(warewulfconf.Get().Warewulf.DataStore, "/container-cache/oci/") diff --git a/internal/pkg/overlay/config_test.go b/internal/pkg/overlay/config_test.go index 246188ac..e098136a 100644 --- a/internal/pkg/overlay/config_test.go +++ b/internal/pkg/overlay/config_test.go @@ -1,8 +1,9 @@ package overlay import ( - warewulfconf "github.com/warewulf/warewulf/internal/pkg/config" "testing" + + warewulfconf "github.com/warewulf/warewulf/internal/pkg/config" ) var overlayImageTests = []struct { diff --git a/internal/pkg/overlay/datastructure.go b/internal/pkg/overlay/datastructure.go index 947bf4ff..e807dae9 100644 --- a/internal/pkg/overlay/datastructure.go +++ b/internal/pkg/overlay/datastructure.go @@ -1,14 +1,14 @@ package overlay import ( - "net" + "bytes" + "encoding/gob" "os" "strconv" "time" warewulfconf "github.com/warewulf/warewulf/internal/pkg/config" "github.com/warewulf/warewulf/internal/pkg/node" - "github.com/warewulf/warewulf/internal/pkg/wwlog" ) /* @@ -35,31 +35,26 @@ type TemplateStruct struct { Warewulf warewulfconf.WarewulfConf Tftp warewulfconf.TFTPConf Paths warewulfconf.BuildConfig - AllNodes []node.NodeInfo + AllNodes []node.NodeConf node.NodeConf // backward compatiblity Container string - ThisNode *node.NodeInfo + ThisNode *node.NodeConf } /* Initialize an TemplateStruct with the given node.NodeInfo */ -func InitStruct(nodeInfo *node.NodeInfo) TemplateStruct { +func InitStruct(nodeData node.NodeConf) (TemplateStruct, error) { var tstruct TemplateStruct - tstruct.ThisNode = nodeInfo + hostname, _ := os.Hostname() + tstruct.BuildHost = hostname controller := warewulfconf.Get() nodeDB, err := node.New() if err != nil { - wwlog.Warn("Problems opening nodes.conf: %s", err) + return tstruct, err } - tstruct.AllNodes, err = nodeDB.FindAllNodes() - if err != nil { - wwlog.Warn("couldn't get all nodes: %s", err) - } - // init some convenience vars - tstruct.Id = nodeInfo.Id.Get() - tstruct.Hostname = nodeInfo.Id.Get() + tstruct.ThisNode = &nodeData tstruct.Nfs = *controller.NFS tstruct.Ssh = *controller.SSH tstruct.Dhcp = *controller.DHCP @@ -70,31 +65,30 @@ func InitStruct(nodeInfo *node.NodeInfo) TemplateStruct { tstruct.Ipaddr6 = controller.Ipaddr6 tstruct.Netmask = controller.Netmask tstruct.Network = controller.Network - netaddrStruct := net.IPNet{IP: net.ParseIP(controller.Network), Mask: net.IPMask(net.ParseIP(controller.Netmask))} - tstruct.NetworkCIDR = netaddrStruct.String() - if controller.Ipaddr6 != "" { - tstruct.Ipv6 = true - } else { - tstruct.Ipv6 = false + allNodes, err := nodeDB.FindAllNodes() + if err != nil { + return tstruct, err } - hostname, _ := os.Hostname() - tstruct.BuildHost = hostname + // init some convenience vars + tstruct.Id = nodeData.Id() + tstruct.Hostname = nodeData.Id() + // Backwards compatibility for templates using "Keys" + tstruct.AllNodes = allNodes dt := time.Now() tstruct.BuildTime = dt.Format("01-02-2006 15:04:05 MST") tstruct.BuildTimeUnix = strconv.FormatInt(dt.Unix(), 10) tstruct.NodeConf.Tags = map[string]string{} - tstruct.NodeConf.GetFrom(*nodeInfo) - // FIXME: Set ipCIDR address at this point, will fail with - // invalid ipv4 addr - for _, network := range tstruct.NodeConf.NetDevs { - ipCIDR := net.IPNet{ - IP: net.ParseIP(network.Ipaddr), - Mask: net.IPMask(net.ParseIP(network.Netmask))} - network.IpCIDR = ipCIDR.String() + var buf bytes.Buffer + enc := gob.NewEncoder(&buf) + dec := gob.NewDecoder(&buf) + err = enc.Encode(nodeData) + if err != nil { + return tstruct, err } - // backward compatibilty - tstruct.Container = tstruct.NodeConf.ContainerName - - return tstruct + err = dec.Decode(&tstruct) + if err != nil { + return tstruct, err + } + return tstruct, nil } diff --git a/internal/pkg/overlay/funcmap.go b/internal/pkg/overlay/funcmap.go index 0d3eb37c..751a97d0 100644 --- a/internal/pkg/overlay/funcmap.go +++ b/internal/pkg/overlay/funcmap.go @@ -104,7 +104,9 @@ func templateContainerFileInclude(containername string, filepath string) string return strings.TrimSuffix(string(content), "\n") } -func createIgnitionJson(node *node.NodeInfo) string { +// don't return an error as we use this function for template evaluation, +// so error will turn up there as the return string +func createIgnitionJson(node *node.NodeConf) string { conf, rep, err := node.GetConfig() if len(conf.Storage.Disks) == 0 && len(conf.Storage.Filesystems) == 0 { wwlog.Debug("no disks or filesystems present, don't create a json object") diff --git a/internal/pkg/overlay/overlay.go b/internal/pkg/overlay/overlay.go index 0b4322fa..35a46757 100644 --- a/internal/pkg/overlay/overlay.go +++ b/internal/pkg/overlay/overlay.go @@ -28,20 +28,20 @@ var ( /* Build all overlays for a node */ -func BuildAllOverlays(nodes []node.NodeInfo) error { +func BuildAllOverlays(nodes []node.NodeConf) error { for _, n := range nodes { - sysOverlays := n.SystemOverlay.GetSlice() - wwlog.Info("Building system overlays for %s: [%s]", n.Id.Get(), strings.Join(sysOverlays, ", ")) + sysOverlays := n.SystemOverlay + wwlog.Info("Building system overlays for %s: [%s]", n.Id(), strings.Join(sysOverlays, ", ")) err := BuildOverlay(n, "system", sysOverlays) if err != nil { - return errors.Wrapf(err, "could not build system overlays %v for node %s", sysOverlays, n.Id.Get()) + return errors.Wrapf(err, "could not build system overlays %v for node %s", sysOverlays, n.Id()) } - runOverlays := n.RuntimeOverlay.GetSlice() - wwlog.Info("Building runtime overlays for %s: [%s]", n.Id.Get(), strings.Join(runOverlays, ", ")) + runOverlays := n.RuntimeOverlay + wwlog.Info("Building runtime overlays for %s: [%s]", n.Id(), strings.Join(runOverlays, ", ")) err = BuildOverlay(n, "runtime", runOverlays) if err != nil { - return errors.Wrapf(err, "could not build runtime overlays %v for node %s", runOverlays, n.Id.Get()) + return errors.Wrapf(err, "could not build runtime overlays %v for node %s", runOverlays, n.Id()) } } @@ -50,16 +50,15 @@ func BuildAllOverlays(nodes []node.NodeInfo) error { // TODO: Add an Overlay Delete for both sourcedir and image -func BuildSpecificOverlays(nodes []node.NodeInfo, overlayNames []string) error { +func BuildSpecificOverlays(nodes []node.NodeConf, overlayNames []string) error { for _, n := range nodes { - wwlog.Info("Building overlay for %s: %v", n.Id.Get(), overlayNames) + wwlog.Info("Building overlay for %s: %v", n, overlayNames) for _, overlayName := range overlayNames { err := BuildOverlay(n, "", []string{overlayName}) if err != nil { - return errors.Wrapf(err, "could not build overlay %s for node %s", overlayName, n.Id.Get()) + return errors.Wrapf(err, "could not build overlay %s for node %s", overlayName, n.Id()) } } - } return nil } @@ -68,10 +67,8 @@ func BuildSpecificOverlays(nodes []node.NodeInfo, overlayNames []string) error { Build overlay for the host, so no argument needs to be given */ func BuildHostOverlay() error { - host := node.NewInfo() hostname, _ := os.Hostname() - host.Id.Set(hostname) - + hostData := node.NewNode(hostname) wwlog.Info("Building overlay for %s: host", hostname) hostdir := OverlaySourceDir("host") stats, err := os.Stat(hostdir) @@ -81,7 +78,7 @@ func BuildHostOverlay() error { if !(stats.Mode() == os.FileMode(0750|os.ModeDir) || stats.Mode() == os.FileMode(0700|os.ModeDir)) { wwlog.SecWarn("Permissions of host overlay dir %s are %s (750 is considered as secure)", hostdir, stats.Mode()) } - return BuildOverlayIndir(host, []string{"host"}, "/") + return BuildOverlayIndir(hostData, []string{"host"}, "/") } /* @@ -126,14 +123,14 @@ func OverlayInit(overlayName string) error { /* Build the given overlays for a node and create a Image for them */ -func BuildOverlay(nodeInfo node.NodeInfo, context string, overlayNames []string) error { +func BuildOverlay(nodeConf node.NodeConf, context string, overlayNames []string) error { if len(overlayNames) == 0 && context == "" { return nil } // create the dir where the overlay images will reside - name := fmt.Sprintf("overlay %s/%v", nodeInfo.Id.Get(), overlayNames) - overlayImage := OverlayImage(nodeInfo.Id.Get(), context, overlayNames) + name := fmt.Sprintf("overlay %s/%v", nodeConf.Id(), overlayNames) + overlayImage := OverlayImage(nodeConf.Id(), context, overlayNames) overlayImageDir := path.Dir(overlayImage) err := os.MkdirAll(overlayImageDir, 0750) @@ -151,7 +148,7 @@ func BuildOverlay(nodeInfo node.NodeInfo, context string, overlayNames []string) wwlog.Debug("Created temporary directory for %s: %s", name, buildDir) - err = BuildOverlayIndir(nodeInfo, overlayNames, buildDir) + err = BuildOverlayIndir(nodeConf, overlayNames, buildDir) if err != nil { return errors.Wrapf(err, "Failed to generate files for %s", name) } @@ -175,7 +172,7 @@ func BuildOverlay(nodeInfo node.NodeInfo, context string, overlayNames []string) Build the given overlays for a node in the given directory. If the given does not exists it will be created. */ -func BuildOverlayIndir(nodeInfo node.NodeInfo, overlayNames []string, outputDir string) error { +func BuildOverlayIndir(nodeData node.NodeConf, overlayNames []string, outputDir string) error { if len(overlayNames) == 0 { return nil } @@ -190,9 +187,9 @@ func BuildOverlayIndir(nodeInfo node.NodeInfo, overlayNames []string, outputDir // Temporarily set umask to 0000, so directories in the overlay retain permissions defer syscall.Umask(syscall.Umask(0)) - wwlog.Verbose("Processing node/overlay: %s/%s", nodeInfo.Id.Get(), strings.Join(overlayNames, "-")) + wwlog.Verbose("Processing node/overlay: %s/%s", nodeData.Id(), strings.Join(overlayNames, "-")) for _, overlayName := range overlayNames { - wwlog.Verbose("Building overlay %s for node %s in %s", overlayName, nodeInfo.Id.Get(), outputDir) + wwlog.Verbose("Building overlay %s for node %s in %s", overlayName, nodeData.Id(), outputDir) overlaySourceDir := OverlaySourceDir(overlayName) wwlog.Debug("Changing directory to OverlayDir: %s", overlaySourceDir) err := os.Chdir(overlaySourceDir) @@ -223,7 +220,10 @@ func BuildOverlayIndir(nodeInfo node.NodeInfo, overlayNames []string, outputDir wwlog.Debug("Created directory in overlay: %s", location) } else if filepath.Ext(location) == ".ww" { - tstruct := InitStruct(&nodeInfo) + tstruct, err := InitStruct(nodeData) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("failed to initial data for %s", nodeData.Id())) + } tstruct.BuildSource = path.Join(overlaySourceDir, location) wwlog.Verbose("Evaluating overlay template file: %s", location) destFile := strings.TrimSuffix(location, ".ww") diff --git a/internal/pkg/util/util.go b/internal/pkg/util/util.go index 60266d93..5b21426b 100644 --- a/internal/pkg/util/util.go +++ b/internal/pkg/util/util.go @@ -491,9 +491,8 @@ func SplitValidPaths(input, delim string) []string { return (ret) } -func IncrementIPv4(start string, inc uint) string { - ip_start := net.ParseIP(start) - ipv4 := ip_start.To4() +func IncrementIPv4(start net.IP, inc uint) net.IP { + ipv4 := start.To4() v4_int := uint(ipv4[0])<<24 + uint(ipv4[1])<<16 + uint(ipv4[2])<<8 + uint(ipv4[3]) v4_int += inc v4_o3 := byte(v4_int & 0xFF) @@ -501,7 +500,7 @@ func IncrementIPv4(start string, inc uint) string { v4_o1 := byte((v4_int >> 16) & 0xFF) v4_o0 := byte((v4_int >> 24) & 0xFF) ipv4_new := net.IPv4(v4_o0, v4_o1, v4_o2, v4_o3) - return ipv4_new.String() + return ipv4_new } /* diff --git a/internal/pkg/warewulfd/nodedb.go b/internal/pkg/warewulfd/nodedb.go index 7f0dfb1c..7d5371ff 100644 --- a/internal/pkg/warewulfd/nodedb.go +++ b/internal/pkg/warewulfd/nodedb.go @@ -12,7 +12,8 @@ import ( type nodeDB struct { lock sync.RWMutex - NodeInfo map[string]node.NodeInfo + NodeInfo map[string]node.NodeConf + yml node.NodeYaml } var ( @@ -25,55 +26,62 @@ func LoadNodeDB() error { return loadNodeDB() } -func loadNodeDB() error { - TmpMap := make(map[string]node.NodeInfo) +func loadNodeDB() (err error) { + TmpMap := make(map[string]node.NodeConf) - DB, err := node.New() + db.yml, err = node.New() if err != nil { - return err + return } - nodes, err := DB.FindAllNodes() + nodes, err := db.yml.FindAllNodes() if err != nil { return err } for _, n := range nodes { + if n.Discoverable { + continue + } for _, netdev := range n.NetDevs { - hwaddr := strings.ToLower(netdev.Hwaddr.Get()) + hwaddr := strings.ToLower(netdev.Hwaddr) TmpMap[hwaddr] = n } } db.NodeInfo = TmpMap - return nil } -func GetNode(val string) (node.NodeInfo, error) { +func GetNode(val string) (node node.NodeConf, err error) { db.lock.RLock() defer db.lock.RUnlock() return getNode(val) } -func getNode(val string) (node.NodeInfo, error) { +func getNode(val string) (node node.NodeConf, err error) { if _, ok := db.NodeInfo[val]; ok { return db.NodeInfo[val], nil } - var empty node.NodeInfo - return empty, errors.New("No node found") + return node, errors.New("No node found") } -func GetNodeOrSetDiscoverable(hwaddr string) (node.NodeInfo, error) { +func GetNodeOrSetDiscoverable(hwaddr string) (node.NodeConf, error) { db.lock.Lock() defer db.lock.Unlock() return getNodeOrSetDiscoverable(hwaddr) } -func getNodeOrSetDiscoverable(hwaddr string) (node.NodeInfo, error) { +func PersistDb() error { + db.lock.Lock() + defer db.lock.Unlock() + return db.yml.Persist() +} + +func getNodeOrSetDiscoverable(hwaddr string) (node.NodeConf, error) { // NOTE: since discoverable nodes will write an updated DB to file and then // reload, it is not enough to lock individual reads from the DB // to ensure the condition on which the node is updated is still satisfied @@ -89,30 +97,23 @@ func getNodeOrSetDiscoverable(hwaddr string) (node.NodeInfo, error) { wwlog.WarnExc(err, "%s (node not configured)", hwaddr) - config, err := node.New() - if err != nil { - return n, errors.Wrapf(err, "%s (failed to read node configuration file)", hwaddr) - } - - _n, netdev, err := config.FindDiscoverableNode() + nodeId, netdev, err := db.yml.FindDiscoverableNode() if err != nil { // NOTE: this is taken as there is no discoverable node, so return the // empty one return n, nil } - - _n.NetDevs[netdev].Hwaddr.Set(hwaddr) - _n.Discoverable.SetB(false) - - // NOTE: errors here should return the empty node if the state cannot - // be saved and re-loaded, since subsequent requests will be made on invalid - // assumption that the database is up to date. - err = config.NodeUpdate(_n) + discoverdNode, err := getNode(nodeId) if err != nil { - return n, errors.Wrapf(err, "%s (failed to set node configuration)", hwaddr) + return n, err } + // update data on disk and in memory + discoverdNode.NetDevs[netdev].Hwaddr = hwaddr + db.yml.Nodes[discoverdNode.Id()].NetDevs[netdev].Hwaddr = hwaddr + discoverdNode.Discoverable = false + db.yml.Nodes[discoverdNode.Id()].Discoverable = false - err = config.Persist() + err = PersistDb() if err != nil { return n, errors.Wrapf(err, "%s (failed to persist node configuration)", hwaddr) } @@ -129,5 +130,5 @@ func getNodeOrSetDiscoverable(hwaddr string) (node.NodeInfo, error) { wwlog.Serv("%s (node automatically configured)", hwaddr) // return the discovered node - return _n, nil + return discoverdNode, nil } diff --git a/internal/pkg/warewulfd/provision.go b/internal/pkg/warewulfd/provision.go index 51b8d60e..77da05ba 100644 --- a/internal/pkg/warewulfd/provision.go +++ b/internal/pkg/warewulfd/provision.go @@ -73,21 +73,21 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { // TODO: when module version is upgraded to go1.18, should be 'any' type var tmpl_data interface{} - node, err := GetNodeOrSetDiscoverable(rinfo.hwaddr) - if err != nil { + remoteNode, err := GetNodeOrSetDiscoverable(rinfo.hwaddr) + if err != nil && err != node.ErrNoUnconfigured { wwlog.ErrorExc(err, "") w.WriteHeader(http.StatusServiceUnavailable) return } - if node.AssetKey.Defined() && node.AssetKey.Get() != rinfo.assetkey { + if remoteNode.AssetKey != "" && remoteNode.AssetKey != rinfo.assetkey { w.WriteHeader(http.StatusUnauthorized) - wwlog.Denied("Incorrect asset key for node: %s", node.Id.Get()) - updateStatus(node.Id.Get(), status_stage, "BAD_ASSET", rinfo.ipaddr) + wwlog.Denied("Incorrect asset key for node: %s", remoteNode.Id()) + updateStatus(remoteNode.Id(), status_stage, "BAD_ASSET", rinfo.ipaddr) return } - if !node.Id.Defined() { + if !remoteNode.Valid() { wwlog.Error("%s (unknown/unconfigured node)", rinfo.hwaddr) if rinfo.stage == "ipxe" { stage_file = path.Join(conf.Paths.Sysconfdir, "/warewulf/ipxe/unconfigured.ipxe") @@ -96,45 +96,44 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { } } else if rinfo.stage == "ipxe" { - stage_file = path.Join(conf.Paths.Sysconfdir, "warewulf/ipxe/"+node.Ipxe.Get()+".ipxe") - tstruct := overlay.InitStruct(&node) + stage_file = path.Join(conf.Paths.Sysconfdir, "warewulf/ipxe/"+remoteNode.Ipxe+".ipxe") tmpl_data = templateVars{ - Id: node.Id.Get(), - Cluster: node.ClusterName.Get(), - Fqdn: node.Id.Get(), + Id: remoteNode.Id(), + Cluster: remoteNode.ClusterName, + Fqdn: remoteNode.Id(), Ipaddr: conf.Ipaddr, Port: strconv.Itoa(conf.Warewulf.Port), - Hostname: node.Id.Get(), + Hostname: remoteNode.Id(), Hwaddr: rinfo.hwaddr, - ContainerName: node.ContainerName.Get(), - KernelArgs: node.Kernel.Args.Get(), - KernelOverride: node.Kernel.Override.Get(), - NetDevs: tstruct.NetDevs, - Tags: tstruct.Tags} + ContainerName: remoteNode.ContainerName, + KernelArgs: remoteNode.Kernel.Args, + KernelOverride: remoteNode.Kernel.Override, + NetDevs: remoteNode.NetDevs, + Tags: remoteNode.Tags} } else if rinfo.stage == "kernel" { - if node.Kernel.Override.Defined() { - stage_file = kernel.KernelImage(node.Kernel.Override.Get()) - } else if node.ContainerName.Defined() { - stage_file, _, err = kernel.FindKernel(container.RootFsDir(node.ContainerName.Get())) + if remoteNode.Kernel.Override != "" { + stage_file = kernel.KernelImage(remoteNode.Kernel.Override) + } else if remoteNode.ContainerName != "" { + stage_file, _, err = kernel.FindKernel(container.RootFsDir(remoteNode.ContainerName)) if err != nil { - wwlog.Error("No kernel found for container %s: %s", node.ContainerName.Get(), err) + wwlog.Error("No kernel found for container %s: %s", remoteNode.ContainerName, err) } } else { - wwlog.Warn("No kernel version set for node %s", node.Id.Get()) + wwlog.Warn("No kernel version set for node %s", remoteNode.Id()) } } else if rinfo.stage == "kmods" { - if node.Kernel.Override.Defined() { - stage_file = kernel.KmodsImage(node.Kernel.Override.Get()) + if remoteNode.Kernel.Override != "" { + stage_file = kernel.KmodsImage(remoteNode.Kernel.Override) } else { - wwlog.Warn("No kernel override modules set for node %s", node.Id.Get()) + wwlog.Warn("No kernel override modules set for node %s", remoteNode.Id()) } } else if rinfo.stage == "container" { - if node.ContainerName.Defined() { - stage_file = container.ImageFile(node.ContainerName.Get()) + if remoteNode.ContainerName != "" { + stage_file = container.ImageFile(remoteNode.ContainerName) } else { - wwlog.Warn("No container set for node %s", node.Id.Get()) + wwlog.Warn("No container set for node %s", remoteNode.Id()) } } else if rinfo.stage == "system" || rinfo.stage == "runtime" { @@ -147,7 +146,7 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { context = rinfo.stage } stage_file, err = getOverlayFile( - node, + remoteNode, context, request_overlays, conf.Warewulf.AutobuildOverlays) @@ -164,7 +163,7 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { } } else if rinfo.stage == "efiboot" { wwlog.Debug("requested method: %s", req.Method) - containerName := node.ContainerName.Get() + containerName := remoteNode.ContainerName switch rinfo.efifile { case "shim.efi": stage_file = container.ShimFind(containerName) @@ -182,20 +181,17 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { } case "grub.cfg": stage_file = path.Join(conf.Paths.Sysconfdir, "warewulf/grub/grub.cfg.ww") - tstruct := overlay.InitStruct(&node) tmpl_data = templateVars{ - Id: node.Id.Get(), - Cluster: node.ClusterName.Get(), - Fqdn: node.Id.Get(), + Id: remoteNode.Id(), + Cluster: remoteNode.ClusterName, + Fqdn: remoteNode.Id(), Ipaddr: conf.Ipaddr, Port: strconv.Itoa(conf.Warewulf.Port), - Hostname: node.Id.Get(), + Hostname: remoteNode.Id(), Hwaddr: rinfo.hwaddr, - ContainerName: node.ContainerName.Get(), - KernelArgs: node.Kernel.Args.Get(), - KernelOverride: node.Kernel.Override.Get(), - NetDevs: tstruct.NetDevs, - Tags: tstruct.Tags} + ContainerName: remoteNode.ContainerName, + KernelArgs: remoteNode.Kernel.Args, + KernelOverride: remoteNode.Kernel.Override} if stage_file == "" { wwlog.ErrorExc(fmt.Errorf("could't find grub.cfg template"), containerName) w.WriteHeader(http.StatusNotFound) @@ -205,36 +201,36 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { wwlog.ErrorExc(fmt.Errorf("could't find efiboot file: %s", rinfo.efifile), "") } } else if rinfo.stage == "shim" { - if node.ContainerName.Defined() { - stage_file = container.ShimFind(node.ContainerName.Get()) + if remoteNode.ContainerName != "" { + stage_file = container.ShimFind(remoteNode.ContainerName) if stage_file == "" { - wwlog.Error("No kernel found for container %s", node.ContainerName.Get()) + wwlog.Error("No kernel found for container %s", remoteNode.ContainerName) } } else { - wwlog.Warn("No container set for this %s", node.Id.Get()) + wwlog.Warn("No container set for this %s", remoteNode.Id()) } } else if rinfo.stage == "grub" { - if node.ContainerName.Defined() { - stage_file = container.GrubFind(node.ContainerName.Get()) + if remoteNode.ContainerName != "" { + stage_file = container.GrubFind(remoteNode.ContainerName) if stage_file == "" { - wwlog.Error("No grub found for container %s", node.ContainerName.Get()) + wwlog.Error("No grub found for container %s", remoteNode.ContainerName) } } else { - wwlog.Warn("No conainer set for node %s", node.Id.Get()) + wwlog.Warn("No conainer set for node %s", remoteNode.Id()) } } else if rinfo.stage == "initramfs" { - if node.ContainerName.Defined() { - _, kver, err := kernel.FindKernel(container.RootFsDir(node.ContainerName.Get())) + if remoteNode.ContainerName != "" { + _, kver, err := kernel.FindKernel(container.RootFsDir(remoteNode.ContainerName)) if err != nil { - wwlog.Error("No kernel found for initramfs for container %s: %s", node.ContainerName.Get(), err) + wwlog.Error("No kernel found for initramfs for container %s: %s", remoteNode.ContainerName, err) } - stage_file, err = container.InitramfsBootPath(node.ContainerName.Get(), kver) + stage_file, err = container.InitramfsBootPath(remoteNode.ContainerName, kver) if err != nil { - wwlog.Error("No initramfs found for container %s: %s", node.ContainerName.Get(), err) + wwlog.Error("No initramfs found for container %s: %s", remoteNode.ContainerName, err) } } else { - wwlog.Warn("No container set for node %s", node.Id.Get()) + wwlog.Warn("No container set for node %s", remoteNode.Id()) } } @@ -278,7 +274,7 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { wwlog.ErrorExc(err, "") } - wwlog.Send("%15s: %s", node.Id.Get(), stage_file) + wwlog.Send("%15s: %s", remoteNode.Id(), stage_file) } else { if rinfo.compress == "gz" { @@ -296,24 +292,24 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusNotFound) } - err = sendFile(w, req, stage_file, node.Id.Get()) + err = sendFile(w, req, stage_file, remoteNode.Id()) if err != nil { wwlog.ErrorExc(err, "") return } } - updateStatus(node.Id.Get(), status_stage, path.Base(stage_file), rinfo.ipaddr) + updateStatus(remoteNode.Id(), status_stage, path.Base(stage_file), rinfo.ipaddr) } else if stage_file == "" { w.WriteHeader(http.StatusBadRequest) wwlog.Error("No resource selected") - updateStatus(node.Id.Get(), status_stage, "BAD_REQUEST", rinfo.ipaddr) + updateStatus(remoteNode.Id(), status_stage, "BAD_REQUEST", rinfo.ipaddr) } else { w.WriteHeader(http.StatusNotFound) wwlog.Error("Not found: %s", stage_file) - updateStatus(node.Id.Get(), status_stage, "NOT_FOUND", rinfo.ipaddr) + updateStatus(remoteNode.Id(), status_stage, "NOT_FOUND", rinfo.ipaddr) } } diff --git a/internal/pkg/warewulfd/provision_test.go b/internal/pkg/warewulfd/provision_test.go index 4ef4437a..2b1e6637 100644 --- a/internal/pkg/warewulfd/provision_test.go +++ b/internal/pkg/warewulfd/provision_test.go @@ -10,6 +10,8 @@ import ( "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" + warewulfconf "github.com/warewulf/warewulf/internal/pkg/config" "github.com/warewulf/warewulf/internal/pkg/node" "github.com/warewulf/warewulf/internal/pkg/wwlog" diff --git a/internal/pkg/warewulfd/status.go b/internal/pkg/warewulfd/status.go index 3d30f01c..c54abcd6 100644 --- a/internal/pkg/warewulfd/status.go +++ b/internal/pkg/warewulfd/status.go @@ -49,11 +49,11 @@ func LoadNodeStatus() error { } for _, n := range nodes { - if _, ok := statusDB.Nodes[n.Id.Get()]; !ok { - newDB.Nodes[n.Id.Get()] = &NodeStatus{} - newDB.Nodes[n.Id.Get()].NodeName = n.Id.Get() + if _, ok := statusDB.Nodes[n.Id()]; !ok { + newDB.Nodes[n.Id()] = &NodeStatus{} + newDB.Nodes[n.Id()].NodeName = n.Id() } else { - newDB.Nodes[n.Id.Get()] = statusDB.Nodes[n.Id.Get()] + newDB.Nodes[n.Id()] = statusDB.Nodes[n.Id()] } } diff --git a/internal/pkg/warewulfd/util.go b/internal/pkg/warewulfd/util.go index d955f772..24008622 100644 --- a/internal/pkg/warewulfd/util.go +++ b/internal/pkg/warewulfd/util.go @@ -44,13 +44,9 @@ func sendFile( return nil } -func getOverlayFile( - n node.NodeInfo, - context string, - stage_overlays []string, - autobuild bool) (stage_file string, err error) { +func getOverlayFile(n node.NodeConf, context string, stage_overlays []string, autobuild bool) (stage_file string, err error) { - stage_file = overlay.OverlayImage(n.Id.Get(), context, stage_overlays) + stage_file = overlay.OverlayImage(n.Id(), context, stage_overlays) err = nil build := !util.IsFile(stage_file) wwlog.Verbose("stage file: %s", stage_file) @@ -64,13 +60,13 @@ func getOverlayFile( if build { if len(stage_overlays) > 0 { - err = overlay.BuildSpecificOverlays([]node.NodeInfo{n}, stage_overlays) + err = overlay.BuildSpecificOverlays([]node.NodeConf{n}, stage_overlays) } else { - err = overlay.BuildAllOverlays([]node.NodeInfo{n}) + err = overlay.BuildAllOverlays([]node.NodeConf{n}) } if err != nil { wwlog.Error("Failed to build overlay: %s, %s, %s\n%s", - n.Id.Get(), stage_overlays, stage_file, err) + n.Id(), stage_overlays, stage_file, err) } }