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 <cgoll@suse.com>
This commit is contained in:
Christian Goll
2023-12-15 15:35:26 +01:00
committed by Jonathon Anderson
parent dc8fb2d6f2
commit 28a7b9fe84
27 changed files with 967 additions and 1801 deletions

View File

@@ -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

1
go.mod
View File

@@ -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

1
go.sum
View File

@@ -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=

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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"`
}

View File

@@ -0,0 +1,6 @@
package node
import "errors"
var ErrNotFound = errors.New("node/profile not found")
var ErrNoUnconfigured = errors.New("no unconfigured node")

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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/")

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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")

View File

@@ -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")

View File

@@ -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
}
/*

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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"

View File

@@ -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()]
}
}

View File

@@ -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)
}
}